Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 22 additions & 37 deletions nginx-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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.
For WordPress multisite with subdomain configuration, the container's `VIRTUAL_HOST` contains both `domain.com` and `*.domain.com`. The `*.domain.com` entry gets its own server block, and a single `_wildcard.` htpasswd file protects it.

#### Naming Convention

Expand All @@ -39,52 +39,34 @@ 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.)
This file applies only to hosts that literally start with `*.`, i.e. the `*.domain.com` server block (all subdomains like `blog.domain.com`, `shop.domain.com` that are served by it). It does not apply to `domain.com` itself, which uses its exact file `/etc/nginx/htpasswd/domain.com`, and it never applies to a separately configured host such as a different site on `shop.domain.com`.

There are no label-counting or multi-level TLD heuristics: `*.domain.co.in` maps to `_wildcard.domain.co.in` and `*.ms.dev.example.com` maps to `_wildcard.ms.dev.example.com`.

#### Lookup Order

The template checks for htpasswd files in this order:
For each host, the template checks for htpasswd files in this order:

1. **Exact match**: `/etc/nginx/htpasswd/<host>` (e.g. `domain.com`)
2. **Wildcard**: `/etc/nginx/htpasswd/_wildcard.<X>`, only when the host is `*.<X>`
3. **Default**: `/etc/nginx/htpasswd/default`

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`
| Host | Files checked |
|------|---------------|
| `example.com` | `example.com`, then `default` |
| `*.example.com` | `*.example.com`, then `_wildcard.example.com`, then `default` |
| `shop.example.com` (its own `VIRTUAL_HOST`) | `shop.example.com`, then `default` |
| `*.domain.co.in` | `*.domain.co.in`, then `_wildcard.domain.co.in`, then `default` |

#### Example Setup

```bash
# Create wildcard htpasswd for WordPress multisite
# Protect a WordPress subdomain multisite (VIRTUAL_HOST=example.com,*.example.com)
htpasswd -c /etc/nginx/htpasswd/example.com admin
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.
```
When auth is enabled, the ACL include follows the same mapping: a `*.<X>` host uses `/etc/nginx/vhost.d/_wildcard.<X>_acl` (see below).

---

Expand All @@ -96,6 +78,9 @@ Create ACL files to restrict access by IP:
# Per-domain ACL
/etc/nginx/vhost.d/example.com_acl

# ACL for a *.example.com host
/etc/nginx/vhost.d/_wildcard.example.com_acl

# Default ACL for all sites
/etc/nginx/vhost.d/default_acl
```
Expand Down Expand Up @@ -165,5 +150,5 @@ services:
image: wordpress
environment:
- VIRTUAL_HOST=example.com,*.example.com
# HTTP auth via /etc/nginx/htpasswd/_wildcard.example.com
# HTTP auth via /etc/nginx/htpasswd/example.com and /etc/nginx/htpasswd/_wildcard.example.com
```
160 changes: 33 additions & 127 deletions nginx-proxy/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@

{{ end }}

{{ define "acl" }}
{{ $aclKey := . }}
{{ if hasPrefix "*." . }}
{{ $aclKey = printf "_wildcard.%s" (trimPrefix "*." .) }}
{{ end }}
{{ if (exists (printf "/etc/nginx/vhost.d/%s_acl" $aclKey)) }}
include {{ printf "/etc/nginx/vhost.d/%s_acl" $aclKey }};
{{ else if (exists "/etc/nginx/vhost.d/default_acl") }}
include /etc/nginx/vhost.d/default_acl;
{{ end }}
{{ end }}

{{ define "location" }}
location {{ .Path }} {
{{ if eq .Proto "uwsgi" }}
Expand All @@ -35,117 +47,27 @@
proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}/;
{{ end }}

{{/* _wildcard.X only covers the literal *.X host; X itself uses its exact file. */}}
{{ $authFile := "" }}
{{ $realm := printf "Restricted %s" .Host }}
{{ if eq .Path "/ee-admin/mailhog/" }}
{{ if (exists (printf "/etc/nginx/htpasswd/default_admin_tools")) }}
auth_basic "Restricted {{ .Host }} Mailhog";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/default_admin_tools") }};
{{ 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 }}
{{ $realm = printf "%s Mailhog" $realm }}
{{ if (exists "/etc/nginx/htpasswd/default_admin_tools") }}
{{ $authFile = "/etc/nginx/htpasswd/default_admin_tools" }}
{{ else if (exists "/etc/nginx/htpasswd/default") }}
auth_basic "Restricted {{ .Host }} Mailhog";
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 }}
{{ $authFile = "/etc/nginx/htpasswd/default" }}
{{ end }}
{{ else if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }}
auth_basic "Restricted {{ .Host }}";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }};
{{ 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 }}
{{/*
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 }}
{{ $authFile = printf "/etc/nginx/htpasswd/%s" .Host }}
{{ else if (and (hasPrefix "*." .Host) (exists (printf "/etc/nginx/htpasswd/_wildcard.%s" (trimPrefix "*." .Host)))) }}
{{ $authFile = printf "/etc/nginx/htpasswd/_wildcard.%s" (trimPrefix "*." .Host) }}
{{ else if (exists "/etc/nginx/htpasswd/default") }}
{{ $authFile = "/etc/nginx/htpasswd/default" }}
{{ end }}
{{ if $authFile }}
auth_basic "{{ $realm }}";
auth_basic_user_file {{ $authFile }};
{{ template "acl" .Host }}
{{ end }}

{{ if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }}
Expand Down Expand Up @@ -479,19 +401,11 @@ server {
{{ if (exists (printf "/etc/nginx/htpasswd/default_admin_tools")) }}
auth_basic "Restricted {{ $host }} Admin Tools";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/default_admin_tools") }};
{{ 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 }}
{{ template "acl" $host }}
{{ else if (exists "/etc/nginx/htpasswd/default") }}
auth_basic "Restricted {{ $host }} Admin Tools";
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 }}
{{ template "acl" $host }}
{{ end }}
}
{{ end }}
Expand Down Expand Up @@ -535,19 +449,11 @@ server {
{{ if (exists (printf "/etc/nginx/htpasswd/default_admin_tools")) }}
auth_basic "Restricted {{ $host }} Admin Tools";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/default_admin_tools") }};
{{ 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 }}
{{ template "acl" $host }}
{{ else if (exists "/etc/nginx/htpasswd/default") }}
auth_basic "Restricted {{ $host }} Admin Tools";
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 }}
{{ template "acl" $host }}
{{ end }}
}
{{ end }}
Expand Down
Loading