mirror of
https://github.com/thib8956/nginx-proxy
synced 2025-08-23 07:51:56 +00:00
feat: support ACME challenges for unknown virtual hosts
Currently any ACME challenge for unknown virtual host returns 503. This is inconvenient because if the user does not use wildcard certificates, then the user must match the configuration of certificate renewal script to what virtual hosts are enabled at the time. This must be done automatically, because due to short certificate lifetime the renewal script runs automatically. Additionally, enabling a previously disabled virtual host forces certificate renewal. Accordingly, it's worthwhile supporting unknown virtual hosts for the purposes of passing ACME challenges. This is done by introducing a global ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST variable to control this.
This commit is contained in:
committed by
Nicolas Duchon
parent
72210064f0
commit
4c8f22ebcc
@@ -459,6 +459,8 @@ By default nginx-proxy generates location blocks to handle ACME HTTP Challenge.
|
|||||||
- `false`: do not handle ACME HTTP Challenge at all.
|
- `false`: do not handle ACME HTTP Challenge at all.
|
||||||
- `legacy`: legacy behavior for compatibility with older (<= `2.3`) versions of acme-companion, only handle ACME HTTP challenge when there is a certificate for the domain and `HTTPS_METHOD=redirect`.
|
- `legacy`: legacy behavior for compatibility with older (<= `2.3`) versions of acme-companion, only handle ACME HTTP challenge when there is a certificate for the domain and `HTTPS_METHOD=redirect`.
|
||||||
|
|
||||||
|
By default, nginx-proxy does not handle ACME HTTP Challenges for unknown virtual hosts. This may happen in cases when a container is not running at the time of the renewal. To enable handling of unknown virtual hosts, set `ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST` environment variable to `true` on the nginx-proxy container.
|
||||||
|
|
||||||
### Diffie-Hellman Groups
|
### Diffie-Hellman Groups
|
||||||
|
|
||||||
[RFC7919 groups](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A) with key lengths of 2048, 3072, and 4096 bits are [provided by `nginx-proxy`](https://github.com/nginx-proxy/nginx-proxy/dhparam). The ENV `DHPARAM_BITS` can be set to `2048` or `3072` to change from the default 4096-bit key. The DH key file will be located in the container at `/etc/nginx/dhparam/dhparam.pem`. Mounting a different `dhparam.pem` file at that location will override the RFC7919 key.
|
[RFC7919 groups](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A) with key lengths of 2048, 3072, and 4096 bits are [provided by `nginx-proxy`](https://github.com/nginx-proxy/nginx-proxy/dhparam). The ENV `DHPARAM_BITS` can be set to `2048` or `3072` to change from the default 4096-bit key. The DH key file will be located in the container at `/etc/nginx/dhparam/dhparam.pem`. Mounting a different `dhparam.pem` file at that location will override the RFC7919 key.
|
||||||
|
11
nginx.tmpl
11
nginx.tmpl
@@ -28,6 +28,7 @@
|
|||||||
{{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }}
|
{{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }}
|
||||||
{{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }}
|
{{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }}
|
||||||
{{- $_ := set $config "acme_http_challenge" ($globals.Env.ACME_HTTP_CHALLENGE_LOCATION | default "true") }}
|
{{- $_ := set $config "acme_http_challenge" ($globals.Env.ACME_HTTP_CHALLENGE_LOCATION | default "true") }}
|
||||||
|
{{- $_ := set $config "acme_http_challenge_accept_unknown_host" ($globals.Env.ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST | default "false" | parseBool) }}
|
||||||
{{- $_ := set $config "enable_http2" ($globals.Env.ENABLE_HTTP2 | default "true") }}
|
{{- $_ := set $config "enable_http2" ($globals.Env.ENABLE_HTTP2 | default "true") }}
|
||||||
{{- $_ := set $config "enable_http3" ($globals.Env.ENABLE_HTTP3 | default "false") }}
|
{{- $_ := set $config "enable_http3" ($globals.Env.ENABLE_HTTP3 | default "false") }}
|
||||||
{{- $_ := set $config "enable_http_on_missing_cert" ($globals.Env.ENABLE_HTTP_ON_MISSING_CERT | default "true") }}
|
{{- $_ := set $config "enable_http_on_missing_cert" ($globals.Env.ENABLE_HTTP_ON_MISSING_CERT | default "true") }}
|
||||||
@@ -861,6 +862,16 @@ server {
|
|||||||
ssl_reject_handshake on;
|
ssl_reject_handshake on;
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if $globals.config.acme_http_challenge_accept_unknown_host }}
|
||||||
|
location ^~ /.well-known/acme-challenge/ {
|
||||||
|
auth_basic off;
|
||||||
|
allow all;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri =404;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{- if (exists "/usr/share/nginx/html/errors/50x.html") }}
|
{{- if (exists "/usr/share/nginx/html/errors/50x.html") }}
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
location /50x.html {
|
location /50x.html {
|
||||||
|
@@ -0,0 +1,34 @@
|
|||||||
|
def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
def test_redirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 301
|
||||||
|
|
||||||
|
def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web3.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web4.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
def test_unknown_domain_acme_challenge_location_default_enabled(docker_compose, nginxproxy, acme_challenge_path):
|
||||||
|
r = nginxproxy.get(
|
||||||
|
f"http://web-unknown.nginx-proxy.tld/{acme_challenge_path}",
|
||||||
|
allow_redirects=False
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
@@ -0,0 +1,40 @@
|
|||||||
|
services:
|
||||||
|
nginx-proxy:
|
||||||
|
environment:
|
||||||
|
ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST: "true"
|
||||||
|
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: "web1.nginx-proxy.tld"
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST: "web2.nginx-proxy.tld"
|
||||||
|
ACME_HTTP_CHALLENGE_LOCATION: "false"
|
||||||
|
|
||||||
|
web3:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: "web3.nginx-proxy.tld"
|
||||||
|
HTTPS_METHOD: noredirect
|
||||||
|
|
||||||
|
web4:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "84"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "84"
|
||||||
|
VIRTUAL_HOST: "web4.nginx-proxy.tld"
|
||||||
|
HTTPS_METHOD: noredirect
|
||||||
|
ACME_HTTP_CHALLENGE_LOCATION: "false"
|
Reference in New Issue
Block a user