diff --git a/nginx-proxy/README.md b/nginx-proxy/README.md new file mode 100644 index 0000000..c07ef4e --- /dev/null +++ b/nginx-proxy/README.md @@ -0,0 +1,168 @@ +# Nginx Proxy + +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. + +## Features + +- Automatic reverse proxy configuration via Docker container labels +- SSL/TLS support with automatic certificate detection +- HTTP Basic Authentication support +- Wildcard HTTP Auth for WordPress Multisite +- Custom vhost configurations +- Access Control Lists (ACL) + +--- + +## HTTP Basic Authentication + +### Standard Authentication + +Create htpasswd files in `/etc/nginx/htpasswd/` to enable HTTP auth: + +```bash +# For a specific domain +htpasswd -c /etc/nginx/htpasswd/example.com username + +# Default auth for all sites without specific htpasswd +htpasswd -c /etc/nginx/htpasswd/default username +``` + +### Wildcard Authentication (WordPress Multisite) + +For WordPress multisite with subdomain configuration, you can use a single htpasswd file to protect both the main domain and all subdomains. + +#### Naming Convention + +Use the `_wildcard.` prefix: + +``` +/etc/nginx/htpasswd/_wildcard.domain.com +``` + +This file will apply HTTP auth to: +- `domain.com` (main domain) +- `*.domain.com` (all subdomains like `blog.domain.com`, `shop.domain.com`, etc.) + +#### Lookup Order + +The template checks for htpasswd files in this order: + +1. **Exact match**: `/etc/nginx/htpasswd/blog.domain.com` +2. **Wildcard (3 parts)**: `/etc/nginx/htpasswd/_wildcard.domain.co.in` (for 4+ part domains only) +3. **Wildcard (2 parts)**: `/etc/nginx/htpasswd/_wildcard.example.com` (for 2-3 part domains, or fallback) +4. **Default**: `/etc/nginx/htpasswd/default` + +#### Example Setup + +```bash +# Create wildcard htpasswd for WordPress multisite +htpasswd -c /etc/nginx/htpasswd/_wildcard.example.com admin + +# This protects: example.com, blog.example.com, shop.example.com, etc. + +# Optional: Override for a specific subdomain +htpasswd -c /etc/nginx/htpasswd/api.example.com api_user +``` + +#### Multi-level TLDs + +Multi-level TLDs (e.g., `.co.in`, `.com.au`) are fully supported: + +| Host | Wildcard File Checked | +|------|----------------------| +| `blog.domain.co.in` (4 parts) | `_wildcard.domain.co.in` first, then `_wildcard.co.in` | +| `domain.co.in` (3 parts) | `_wildcard.co.in` | +| `blog.example.com` (3 parts) | `_wildcard.example.com` | +| `example.com` (2 parts) | `_wildcard.example.com` | + +```bash +# For domain.co.in multisite (multi-level TLD) +htpasswd -c /etc/nginx/htpasswd/_wildcard.domain.co.in admin + +# This will protect: +# - domain.co.in +# - blog.domain.co.in +# - shop.domain.co.in +# - etc. +``` + +--- + +## Access Control Lists (ACL) + +Create ACL files to restrict access by IP: + +```bash +# Per-domain ACL +/etc/nginx/vhost.d/example.com_acl + +# Default ACL for all sites +/etc/nginx/vhost.d/default_acl +``` + +Example ACL content: +```nginx +allow 192.168.1.0/24; +allow 10.0.0.0/8; +deny all; +``` + +--- + +## Custom Vhost Configuration + +### Per-domain configuration + +```bash +# Main vhost config +/etc/nginx/vhost.d/example.com + +# Location-specific config +/etc/nginx/vhost.d/example.com_location +``` + +### Default configuration + +```bash +/etc/nginx/vhost.d/default +/etc/nginx/vhost.d/default_location +``` + +--- + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `VIRTUAL_HOST` | Comma-separated list of domains | - | +| `VIRTUAL_PORT` | Port to proxy to | `80` | +| `VIRTUAL_PROTO` | Protocol (`http`, `https`, `uwsgi`, `fastcgi`) | `http` | +| `HTTPS_METHOD` | `redirect`, `noredirect`, `nohttps` | `redirect` | +| `SSL_POLICY` | SSL/TLS policy | `Mozilla-Modern` | +| `HSTS` | HSTS header value | `max-age=31536000` | +| `CERT_NAME` | Custom certificate name | auto-detected | +| `NETWORK_ACCESS` | `external` or `internal` | `external` | + +--- + +## Docker Compose Example + +```yaml +services: + nginx-proxy: + image: your-nginx-proxy-image + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs:/etc/nginx/certs:ro + - ./htpasswd:/etc/nginx/htpasswd:ro + - ./vhost.d:/etc/nginx/vhost.d:ro + + wordpress-multisite: + image: wordpress + environment: + - VIRTUAL_HOST=example.com,*.example.com + # HTTP auth via /etc/nginx/htpasswd/_wildcard.example.com +``` diff --git a/nginx-proxy/nginx.tmpl b/nginx-proxy/nginx.tmpl index 5ac62da..e12ba51 100644 --- a/nginx-proxy/nginx.tmpl +++ b/nginx-proxy/nginx.tmpl @@ -61,13 +61,90 @@ {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} include /etc/nginx/vhost.d/default_acl; {{ end }} - {{ else if (exists "/etc/nginx/htpasswd/default") }} - auth_basic "Restricted {{ .Host }}"; - auth_basic_user_file /etc/nginx/htpasswd/default; - {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} - include /etc/nginx/vhost.d/default_acl; + {{/* + Wildcard htpasswd support for WordPress Multisite. + Naming convention: _wildcard.domain.com applies to domain.com AND *.domain.com + Supports multi-level TLDs: _wildcard.domain.co.in works for domain.co.in AND *.domain.co.in + + Lookup order (after exact match check on line 56): + - For 4+ part domains: checks _wildcard.{last-3-parts}, then _wildcard.{last-2-parts}, then default + - For 2-3 part domains: checks _wildcard.{last-2-parts}, then falls back to default + - For single-part hostnames: uses default only + + Note: Uses sprig's splitList and sub functions (available in docker-gen 0.7.4+) + */}} + {{ else }} + {{ $hostParts := splitList "." .Host }} + {{ $partsLen := len $hostParts }} + {{/* For 4+ part domains, check last 3 parts first (e.g., _wildcard.domain.co.in for blog.domain.co.in) */}} + {{ if ge $partsLen 4 }} + {{ $idx3 := sub $partsLen 3 }} + {{ $idx2 := sub $partsLen 2 }} + {{ $idx1 := sub $partsLen 1 }} + {{ $baseDomain3 := printf "%s.%s.%s" (index $hostParts $idx3) (index $hostParts $idx2) (index $hostParts $idx1) }} + {{ $wildcardHtpasswd3 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain3 }} + {{ if (exists $wildcardHtpasswd3) }} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file {{ ($wildcardHtpasswd3) }}; + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} + {{ else }} + {{/* Fallback: check last 2 parts (e.g., _wildcard.co.in for blog.domain.co.in) */}} + {{ $baseDomain2 := printf "%s.%s" (index $hostParts $idx2) (index $hostParts $idx1) }} + {{ $wildcardHtpasswd2 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain2 }} + {{ if (exists $wildcardHtpasswd2) }} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file {{ ($wildcardHtpasswd2) }}; + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} + {{ else if (exists "/etc/nginx/htpasswd/default") }} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file /etc/nginx/htpasswd/default; + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} + {{ end }} + {{ end }} + {{ else if ge $partsLen 2 }} + {{/* For 2-3 part domains, check last 2 parts (e.g., _wildcard.example.com for blog.example.com or example.com) */}} + {{ $idx2 := sub $partsLen 2 }} + {{ $idx1 := sub $partsLen 1 }} + {{ $baseDomain2 := printf "%s.%s" (index $hostParts $idx2) (index $hostParts $idx1) }} + {{ $wildcardHtpasswd2 := printf "/etc/nginx/htpasswd/_wildcard.%s" $baseDomain2 }} + {{ if (exists $wildcardHtpasswd2) }} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file {{ ($wildcardHtpasswd2) }}; + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} + {{ else if (exists "/etc/nginx/htpasswd/default") }} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file /etc/nginx/htpasswd/default; + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} + {{ end }} + {{ else if (exists "/etc/nginx/htpasswd/default") }} + {{/* Single-part hostname - use default */}} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file /etc/nginx/htpasswd/default; + {{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_acl" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_acl") }} + include /etc/nginx/vhost.d/default_acl; + {{ end }} {{ end }} {{ end }} @@ -146,8 +223,8 @@ map $scheme $proxy_x_forwarded_ssl { gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; log_format vhost '$host $remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent"'; + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; {{ if $.Env.RESOLVERS }} resolver {{ $.Env.RESOLVERS }}; @@ -190,14 +267,14 @@ server { {{ end }} root /etc/nginx/html; - + # Custom error page for 503 error_page 503 /default.html; - + location / { return 503; } - + # Serve the error page without redirect location = /default.html { root /etc/nginx/html; @@ -219,14 +296,14 @@ server { {{ end }} root /etc/nginx/html; - + # Custom error page for 503 error_page 503 /default.html; - + location / { return 503; } - + # Serve the error page without redirect location = /default.html { root /etc/nginx/html;