Skip to content

Commit 409962c

Browse files
authored
Merge pull request #298 from mrrobot47/feat/http-auth
feat(nginx-proxy): Add wildcard HTTP auth support for WordPress multisite
2 parents 0656885 + fe131a0 commit 409962c

File tree

2 files changed

+260
-15
lines changed

2 files changed

+260
-15
lines changed

nginx-proxy/README.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Nginx Proxy
2+
3+
A custom nginx-proxy image based on [jwilder/nginx-proxy](https://github.com/nginx-proxy/nginx-proxy) with additional features for WordPress and EasyEngine environments.
4+
5+
## Features
6+
7+
- Automatic reverse proxy configuration via Docker container labels
8+
- SSL/TLS support with automatic certificate detection
9+
- HTTP Basic Authentication support
10+
- Wildcard HTTP Auth for WordPress Multisite
11+
- Custom vhost configurations
12+
- Access Control Lists (ACL)
13+
14+
---
15+
16+
## HTTP Basic Authentication
17+
18+
### Standard Authentication
19+
20+
Create htpasswd files in `/etc/nginx/htpasswd/` to enable HTTP auth:
21+
22+
```bash
23+
# For a specific domain
24+
htpasswd -c /etc/nginx/htpasswd/example.com username
25+
26+
# Default auth for all sites without specific htpasswd
27+
htpasswd -c /etc/nginx/htpasswd/default username
28+
```
29+
30+
### Wildcard Authentication (WordPress Multisite)
31+
32+
For WordPress multisite with subdomain configuration, you can use a single htpasswd file to protect both the main domain and all subdomains.
33+
34+
#### Naming Convention
35+
36+
Use the `_wildcard.` prefix:
37+
38+
```
39+
/etc/nginx/htpasswd/_wildcard.domain.com
40+
```
41+
42+
This file will apply HTTP auth to:
43+
- `domain.com` (main domain)
44+
- `*.domain.com` (all subdomains like `blog.domain.com`, `shop.domain.com`, etc.)
45+
46+
#### Lookup Order
47+
48+
The template checks for htpasswd files in this order:
49+
50+
1. **Exact match**: `/etc/nginx/htpasswd/blog.domain.com`
51+
2. **Wildcard (3 parts)**: `/etc/nginx/htpasswd/_wildcard.domain.co.in` (for 4+ part domains only)
52+
3. **Wildcard (2 parts)**: `/etc/nginx/htpasswd/_wildcard.example.com` (for 2-3 part domains, or fallback)
53+
4. **Default**: `/etc/nginx/htpasswd/default`
54+
55+
#### Example Setup
56+
57+
```bash
58+
# Create wildcard htpasswd for WordPress multisite
59+
htpasswd -c /etc/nginx/htpasswd/_wildcard.example.com admin
60+
61+
# This protects: example.com, blog.example.com, shop.example.com, etc.
62+
63+
# Optional: Override for a specific subdomain
64+
htpasswd -c /etc/nginx/htpasswd/api.example.com api_user
65+
```
66+
67+
#### Multi-level TLDs
68+
69+
Multi-level TLDs (e.g., `.co.in`, `.com.au`) are fully supported:
70+
71+
| Host | Wildcard File Checked |
72+
|------|----------------------|
73+
| `blog.domain.co.in` (4 parts) | `_wildcard.domain.co.in` first, then `_wildcard.co.in` |
74+
| `domain.co.in` (3 parts) | `_wildcard.co.in` |
75+
| `blog.example.com` (3 parts) | `_wildcard.example.com` |
76+
| `example.com` (2 parts) | `_wildcard.example.com` |
77+
78+
```bash
79+
# For domain.co.in multisite (multi-level TLD)
80+
htpasswd -c /etc/nginx/htpasswd/_wildcard.domain.co.in admin
81+
82+
# This will protect:
83+
# - domain.co.in
84+
# - blog.domain.co.in
85+
# - shop.domain.co.in
86+
# - etc.
87+
```
88+
89+
---
90+
91+
## Access Control Lists (ACL)
92+
93+
Create ACL files to restrict access by IP:
94+
95+
```bash
96+
# Per-domain ACL
97+
/etc/nginx/vhost.d/example.com_acl
98+
99+
# Default ACL for all sites
100+
/etc/nginx/vhost.d/default_acl
101+
```
102+
103+
Example ACL content:
104+
```nginx
105+
allow 192.168.1.0/24;
106+
allow 10.0.0.0/8;
107+
deny all;
108+
```
109+
110+
---
111+
112+
## Custom Vhost Configuration
113+
114+
### Per-domain configuration
115+
116+
```bash
117+
# Main vhost config
118+
/etc/nginx/vhost.d/example.com
119+
120+
# Location-specific config
121+
/etc/nginx/vhost.d/example.com_location
122+
```
123+
124+
### Default configuration
125+
126+
```bash
127+
/etc/nginx/vhost.d/default
128+
/etc/nginx/vhost.d/default_location
129+
```
130+
131+
---
132+
133+
## Environment Variables
134+
135+
| Variable | Description | Default |
136+
|----------|-------------|---------|
137+
| `VIRTUAL_HOST` | Comma-separated list of domains | - |
138+
| `VIRTUAL_PORT` | Port to proxy to | `80` |
139+
| `VIRTUAL_PROTO` | Protocol (`http`, `https`, `uwsgi`, `fastcgi`) | `http` |
140+
| `HTTPS_METHOD` | `redirect`, `noredirect`, `nohttps` | `redirect` |
141+
| `SSL_POLICY` | SSL/TLS policy | `Mozilla-Modern` |
142+
| `HSTS` | HSTS header value | `max-age=31536000` |
143+
| `CERT_NAME` | Custom certificate name | auto-detected |
144+
| `NETWORK_ACCESS` | `external` or `internal` | `external` |
145+
146+
---
147+
148+
## Docker Compose Example
149+
150+
```yaml
151+
services:
152+
nginx-proxy:
153+
image: your-nginx-proxy-image
154+
ports:
155+
- "80:80"
156+
- "443:443"
157+
volumes:
158+
- /var/run/docker.sock:/tmp/docker.sock:ro
159+
- ./certs:/etc/nginx/certs:ro
160+
- ./htpasswd:/etc/nginx/htpasswd:ro
161+
- ./vhost.d:/etc/nginx/vhost.d:ro
162+
163+
wordpress-multisite:
164+
image: wordpress
165+
environment:
166+
- VIRTUAL_HOST=example.com,*.example.com
167+
# HTTP auth via /etc/nginx/htpasswd/_wildcard.example.com
168+
```

nginx-proxy/nginx.tmpl

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,90 @@
6161
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
6262
include /etc/nginx/vhost.d/default_acl;
6363
{{ end }}
64-
{{ else if (exists "/etc/nginx/htpasswd/default") }}
65-
auth_basic "Restricted {{ .Host }}";
66-
auth_basic_user_file /etc/nginx/htpasswd/default;
67-
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
68-
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
69-
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
70-
include /etc/nginx/vhost.d/default_acl;
64+
{{/*
65+
Wildcard htpasswd support for WordPress Multisite.
66+
Naming convention: _wildcard.domain.com applies to domain.com AND *.domain.com
67+
Supports multi-level TLDs: _wildcard.domain.co.in works for domain.co.in AND *.domain.co.in
68+
69+
Lookup order (after exact match check on line 56):
70+
- For 4+ part domains: checks _wildcard.{last-3-parts}, then _wildcard.{last-2-parts}, then default
71+
- For 2-3 part domains: checks _wildcard.{last-2-parts}, then falls back to default
72+
- For single-part hostnames: uses default only
73+
74+
Note: Uses sprig's splitList and sub functions (available in docker-gen 0.7.4+)
75+
*/}}
76+
{{ else }}
77+
{{ $hostParts := splitList "." .Host }}
78+
{{ $partsLen := len $hostParts }}
79+
{{/* For 4+ part domains, check last 3 parts first (e.g., _wildcard.domain.co.in for blog.domain.co.in) */}}
80+
{{ if ge $partsLen 4 }}
81+
{{ $idx3 := sub $partsLen 3 }}
82+
{{ $idx2 := sub $partsLen 2 }}
83+
{{ $idx1 := sub $partsLen 1 }}
84+
{{ $baseDomain3 := printf "%s.%s.%s" (index $hostParts $idx3) (index $hostParts $idx2) (index $hostParts $idx1) }}
85+
{{ $wildcardHtpasswd3 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain3 }}
86+
{{ if (exists $wildcardHtpasswd3) }}
87+
auth_basic "Restricted {{ .Host }}";
88+
auth_basic_user_file {{ ($wildcardHtpasswd3) }};
89+
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
90+
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
91+
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
92+
include /etc/nginx/vhost.d/default_acl;
93+
{{ end }}
94+
{{ else }}
95+
{{/* Fallback: check last 2 parts (e.g., _wildcard.co.in for blog.domain.co.in) */}}
96+
{{ $baseDomain2 := printf "%s.%s" (index $hostParts $idx2) (index $hostParts $idx1) }}
97+
{{ $wildcardHtpasswd2 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain2 }}
98+
{{ if (exists $wildcardHtpasswd2) }}
99+
auth_basic "Restricted {{ .Host }}";
100+
auth_basic_user_file {{ ($wildcardHtpasswd2) }};
101+
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
102+
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
103+
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
104+
include /etc/nginx/vhost.d/default_acl;
105+
{{ end }}
106+
{{ else if (exists "/etc/nginx/htpasswd/default") }}
107+
auth_basic "Restricted {{ .Host }}";
108+
auth_basic_user_file /etc/nginx/htpasswd/default;
109+
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
110+
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
111+
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
112+
include /etc/nginx/vhost.d/default_acl;
113+
{{ end }}
114+
{{ end }}
115+
{{ end }}
116+
{{ else if ge $partsLen 2 }}
117+
{{/* For 2-3 part domains, check last 2 parts (e.g., _wildcard.example.com for blog.example.com or example.com) */}}
118+
{{ $idx2 := sub $partsLen 2 }}
119+
{{ $idx1 := sub $partsLen 1 }}
120+
{{ $baseDomain2 := printf "%s.%s" (index $hostParts $idx2) (index $hostParts $idx1) }}
121+
{{ $wildcardHtpasswd2 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain2 }}
122+
{{ if (exists $wildcardHtpasswd2) }}
123+
auth_basic "Restricted {{ .Host }}";
124+
auth_basic_user_file {{ ($wildcardHtpasswd2) }};
125+
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
126+
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
127+
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
128+
include /etc/nginx/vhost.d/default_acl;
129+
{{ end }}
130+
{{ else if (exists "/etc/nginx/htpasswd/default") }}
131+
auth_basic "Restricted {{ .Host }}";
132+
auth_basic_user_file /etc/nginx/htpasswd/default;
133+
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
134+
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
135+
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
136+
include /etc/nginx/vhost.d/default_acl;
137+
{{ end }}
138+
{{ end }}
139+
{{ else if (exists "/etc/nginx/htpasswd/default") }}
140+
{{/* Single-part hostname - use default */}}
141+
auth_basic "Restricted {{ .Host }}";
142+
auth_basic_user_file /etc/nginx/htpasswd/default;
143+
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }}
144+
include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}};
145+
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
146+
include /etc/nginx/vhost.d/default_acl;
147+
{{ end }}
71148
{{ end }}
72149
{{ end }}
73150

@@ -146,8 +223,8 @@ map $scheme $proxy_x_forwarded_ssl {
146223
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
147224

148225
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
149-
'"$request" $status $body_bytes_sent '
150-
'"$http_referer" "$http_user_agent"';
226+
'"$request" $status $body_bytes_sent '
227+
'"$http_referer" "$http_user_agent"';
151228

152229
{{ if $.Env.RESOLVERS }}
153230
resolver {{ $.Env.RESOLVERS }};
@@ -190,14 +267,14 @@ server {
190267
{{ end }}
191268

192269
root /etc/nginx/html;
193-
270+
194271
# Custom error page for 503
195272
error_page 503 /default.html;
196-
273+
197274
location / {
198275
return 503;
199276
}
200-
277+
201278
# Serve the error page without redirect
202279
location = /default.html {
203280
root /etc/nginx/html;
@@ -219,14 +296,14 @@ server {
219296
{{ end }}
220297

221298
root /etc/nginx/html;
222-
299+
223300
# Custom error page for 503
224301
error_page 503 /default.html;
225-
302+
226303
location / {
227304
return 503;
228305
}
229-
306+
230307
# Serve the error page without redirect
231308
location = /default.html {
232309
root /etc/nginx/html;

0 commit comments

Comments
 (0)