diff --git a/docs/README.md b/docs/README.md index ca1d56c..4e6788d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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. - `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 [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. diff --git a/nginx.tmpl b/nginx.tmpl index dd1c444..afc9f16 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -28,6 +28,7 @@ {{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }} {{- $_ := 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_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_http3" ($globals.Env.ENABLE_HTTP3 | default "false") }} {{- $_ := 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; {{- 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") }} error_page 500 502 503 504 /50x.html; location /50x.html { diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py new file mode 100644 index 0000000..8643b4b --- /dev/null +++ b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py @@ -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 diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml new file mode 100644 index 0000000..25965e4 --- /dev/null +++ b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml @@ -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" diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py index ae12fa6..5588167 100644 --- a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py +++ b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py @@ -25,3 +25,10 @@ def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, allow_redirects=False ) assert r.status_code == 200 + +def test_unknown_domain_acme_challenge_location_disabled(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 == 503 diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py index 88cb07d..80eea97 100644 --- a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py +++ b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py @@ -25,3 +25,10 @@ def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, 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 == 503 diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py index ed9f25a..495ad83 100644 --- a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py +++ b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py @@ -11,3 +11,10 @@ def test_noredirect_acme_challenge_location_legacy(docker_compose, nginxproxy, a allow_redirects=False ) assert r.status_code == 404 + +def test_unknown_domain_acme_challenge_location_legacy(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 == 503