├── test ├── test_dockergen │ ├── .gitignore │ ├── test_dockergen_v2.yml │ ├── test_dockergen_v3.yml │ ├── test_dockergen_v2.py │ └── test_dockergen_v3.py ├── test_virtual-path │ ├── foo.conf │ ├── bar.conf │ ├── host.conf │ ├── path.conf │ ├── default.conf │ ├── alternate.conf │ ├── test_forwarding.yml │ ├── test_forwarding.py │ ├── test_virtual_paths.yml │ ├── test_location_precedence.yml │ ├── test_location_precedence.py │ ├── test_custom_conf.yml │ ├── test_custom_conf.py │ └── test_virtual_paths.py ├── stress_tests │ ├── test_deleted_cert │ │ ├── tmp_certs │ │ │ └── .gitignore │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── certs │ │ │ ├── web.nginx-proxy.key │ │ │ └── web.nginx-proxy.crt │ │ └── test_restart_while_missing_cert.py │ ├── README.md │ └── test_unreachable_network │ │ ├── docker-compose.yml │ │ ├── test_unreachable_net.py │ │ └── README.md ├── test_custom │ ├── my_custom_proxy_settings.conf │ ├── my_custom_proxy_settings_bar.conf │ ├── test_defaults.yml │ ├── test_per-vhost.yml │ ├── test_proxy-wide.yml │ ├── test_location-per-vhost.yml │ ├── test_defaults-location.yml │ ├── test_per-vhost.py │ ├── test_defaults.py │ ├── test_proxy-wide.py │ ├── test_location-per-vhost.py │ └── test_defaults-location.py ├── test_events.yml ├── requirements │ ├── python-requirements.txt │ ├── Dockerfile-nginx-proxy-tester │ ├── web │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ └── webserver.py │ └── README.md ├── pytest.ini ├── test_default-host.py ├── test_multiple-ports │ ├── test_VIRTUAL_PORT.py │ ├── test_default-80.py │ ├── test_single-port-not-80.py │ ├── test_single-port-not-80.yml │ ├── test_default-80.yml │ ├── test_VIRTUAL_PORT.yml │ ├── test_VIRTUAL_PORT-single-different-from-single-port.yml │ └── test_VIRTUAL_PORT-single-different-from-single-port.py ├── test_upstream-name │ ├── test_predictable-name.py │ ├── test_predictable-name.yml │ ├── test_sha1-name.yml │ └── test_sha1-name.py ├── test_server-down │ ├── test_no-server-down.yml │ ├── test_server-down.yml │ ├── test_server-down.py │ ├── test_no-server-down.py │ ├── test_load-balancing.py │ └── test_load-balancing.yml ├── test_multiple-hosts.yml ├── test_internal │ ├── network_internal.conf │ ├── test_internal-per-vhost.yml │ ├── test_internal-per-vhost.py │ ├── test_internal-per-vpath.py │ └── test_internal-per-vpath.yml ├── test_http_port.yml ├── test_ssl │ ├── wildcard_cert_and_nohttps │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── test_wildcard_cert_nohttps.py │ │ └── certs │ │ │ ├── default.key │ │ │ ├── web.nginx-proxy.tld.key │ │ │ ├── default.crt │ │ │ └── web.nginx-proxy.tld.crt │ ├── test_nohttps.yml │ ├── test_wildcard.yml │ ├── test_nohttp.yml │ ├── test_noredirect.yml │ ├── test_https_port.yml │ ├── certs │ │ ├── web2.nginx-proxy.tld.dhparam.pem │ │ ├── nginx-proxy.tld.key │ │ ├── web2.nginx-proxy.tld.key │ │ ├── web3.nginx-proxy.tld.key │ │ ├── nginx-proxy.tld.crt │ │ ├── web2.nginx-proxy.tld.crt │ │ └── web3.nginx-proxy.tld.crt │ ├── test_nohttps.py │ ├── test_virtual_path.yml │ ├── test_nohttp.py │ ├── test_https_port.py │ ├── test_virtual_path.py │ ├── test_noredirect.py │ ├── test_hsts.yml │ ├── test_wildcard.py │ ├── test_hsts.py │ ├── test_dhparam.yml │ └── test_dhparam.py ├── test_composev2.yml ├── test_http_port.py ├── test_composev2.py ├── test_default-host.yml ├── test_DOCKER_HOST_unix_socket.yml ├── test_headers │ ├── test_http.yml │ ├── test_https.yml │ ├── certs │ │ ├── web.nginx-proxy.tld.key │ │ ├── web-server-tokens-off.nginx-proxy.tld.key │ │ ├── web.nginx-proxy.tld.crt │ │ └── web-server-tokens-off.nginx-proxy.tld.crt │ ├── test_http.py │ └── test_https.py ├── test_debug │ ├── test_server-debug-flag.py │ ├── test_server-debug-flag.yml │ ├── test_proxy-debug-flag.yml │ └── test_proxy-debug-flag.py ├── test_multiple-networks.py ├── test_raw-ip-vhost.py ├── test_DOCKER_HOST_unix_socket.py ├── test_multiple-hosts.py ├── test_multiple-networks.yml ├── test_nominal.yml ├── test_ipv6.yml ├── test_wildcard_host.yml ├── test_nominal.py ├── test_raw-ip-vhost.yml ├── test_ipv6.py ├── test_wildcard_host.py ├── certs │ ├── ca-root.crt │ ├── ca-root.key │ ├── README.md │ └── create_server_certificate.sh ├── pytest.sh ├── test_events.py └── README.md ├── .gitignore ├── app ├── Procfile ├── dhparam │ ├── ffdhe2048.pem │ ├── ffdhe3072.pem │ └── ffdhe4096.pem └── docker-entrypoint.sh ├── .dockerignore ├── network_internal.conf ├── docker-compose.yml ├── .github ├── dependabot.yml ├── workflows │ ├── test.yml │ └── dockerhub.yml └── ISSUE_TEMPLATE.md ├── Makefile ├── docker-compose-separate-containers.yml ├── LICENSE ├── Dockerfile.alpine └── Dockerfile /test/test_dockergen/.gitignore: -------------------------------------------------------------------------------- 1 | nginx.tmpl -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/ 2 | **/.cache/ 3 | .idea/ 4 | -------------------------------------------------------------------------------- /test/test_virtual-path/foo.conf: -------------------------------------------------------------------------------- 1 | add_header X-test f00; -------------------------------------------------------------------------------- /test/test_virtual-path/bar.conf: -------------------------------------------------------------------------------- 1 | add_header X-test bar; 2 | -------------------------------------------------------------------------------- /test/test_virtual-path/host.conf: -------------------------------------------------------------------------------- 1 | add_header X-test-host true; 2 | -------------------------------------------------------------------------------- /test/test_virtual-path/path.conf: -------------------------------------------------------------------------------- 1 | add_header X-test-path true; 2 | -------------------------------------------------------------------------------- /test/stress_tests/test_deleted_cert/tmp_certs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /test/test_custom/my_custom_proxy_settings.conf: -------------------------------------------------------------------------------- 1 | add_header X-test f00; -------------------------------------------------------------------------------- /test/test_virtual-path/default.conf: -------------------------------------------------------------------------------- 1 | add_header X-test-default true; 2 | -------------------------------------------------------------------------------- /test/test_virtual-path/alternate.conf: -------------------------------------------------------------------------------- 1 | rewrite ^/(web3|alt)/(.*) /$2 break; 2 | -------------------------------------------------------------------------------- /test/test_custom/my_custom_proxy_settings_bar.conf: -------------------------------------------------------------------------------- 1 | add_header X-test bar; 2 | -------------------------------------------------------------------------------- /test/stress_tests/README.md: -------------------------------------------------------------------------------- 1 | This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy. -------------------------------------------------------------------------------- /app/Procfile: -------------------------------------------------------------------------------- 1 | dockergen: docker-gen -watch -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf 2 | nginx: nginx 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | test 4 | .dockerignore 5 | .gitignore 6 | *.yml 7 | Dockerfile* 8 | Makefile 9 | README.md 10 | -------------------------------------------------------------------------------- /test/test_events.yml: -------------------------------------------------------------------------------- 1 | nginxproxy: 2 | image: nginxproxy/nginx-proxy:test 3 | volumes: 4 | - /var/run/docker.sock:/tmp/docker.sock:ro 5 | -------------------------------------------------------------------------------- /test/requirements/python-requirements.txt: -------------------------------------------------------------------------------- 1 | backoff==1.11.1 2 | docker-compose==1.29.2 3 | docker==5.0.3 4 | pytest==7.1.2 5 | requests==2.27.1 6 | -------------------------------------------------------------------------------- /test/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | # disable the creation of the `.cache` folders 3 | addopts = -p no:cacheprovider --ignore=requirements --ignore=certs -r s -v 4 | markers = 5 | incremental: mark a test as incremental. -------------------------------------------------------------------------------- /network_internal.conf: -------------------------------------------------------------------------------- 1 | # Only allow traffic from internal clients 2 | allow 127.0.0.0/8; 3 | allow 10.0.0.0/8; 4 | allow 192.168.0.0/16; 5 | allow 172.16.0.0/12; 6 | allow fc00::/7; # IPv6 local address range 7 | deny all; 8 | -------------------------------------------------------------------------------- /test/test_default-host.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_fallback_on_default(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://unknown.nginx-proxy.tld/port") 6 | assert r.status_code == 200 7 | assert r.text == "answer from port 81\n" -------------------------------------------------------------------------------- /test/requirements/Dockerfile-nginx-proxy-tester: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | ENV PYTEST_RUNNING_IN_CONTAINER=1 4 | 5 | COPY python-requirements.txt /requirements.txt 6 | RUN pip install -r /requirements.txt 7 | 8 | WORKDIR /test 9 | ENTRYPOINT ["pytest"] 10 | -------------------------------------------------------------------------------- /test/requirements/web/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable 2 | 3 | FROM python:3 4 | COPY ./webserver.py / 5 | COPY ./entrypoint.sh / 6 | WORKDIR /opt 7 | ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] 8 | 9 | -------------------------------------------------------------------------------- /test/test_multiple-ports/test_VIRTUAL_PORT.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_answer_is_served_from_chosen_port(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 6 | assert r.status_code == 200 7 | assert "answer from port 90\n" in r.text 8 | -------------------------------------------------------------------------------- /test/test_multiple-ports/test_default-80.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 6 | assert r.status_code == 200 7 | assert "answer from port 80\n" in r.text 8 | -------------------------------------------------------------------------------- /test/test_upstream-name/test_predictable-name.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import re 3 | 4 | 5 | def test_predictable_upstream_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): 6 | conf = nginxproxy.get_conf().decode('ASCII') 7 | assert re.search(r"upstream web\.nginx-proxy\.tld \{", conf) 8 | -------------------------------------------------------------------------------- /test/test_multiple-ports/test_single-port-not-80.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_answer_is_served_from_exposed_port_even_if_not_80(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 6 | assert r.status_code == 200 7 | assert "answer from port 81\n" in r.text 8 | -------------------------------------------------------------------------------- /test/test_server-down/test_no-server-down.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: web.nginx-proxy.tld 8 | 9 | sut: 10 | image: nginxproxy/nginx-proxy:test 11 | volumes: 12 | - /var/run/docker.sock:/tmp/docker.sock:ro 13 | -------------------------------------------------------------------------------- /test/test_multiple-hosts.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: webA.nginx-proxy.tld,webB.nginx-proxy.tld 8 | 9 | 10 | sut: 11 | image: nginxproxy/nginx-proxy:test 12 | volumes: 13 | - /var/run/docker.sock:/tmp/docker.sock:ro 14 | -------------------------------------------------------------------------------- /test/test_internal/network_internal.conf: -------------------------------------------------------------------------------- 1 | # Only allow traffic from internal clients 2 | allow 127.0.0.0/8; 3 | allow 10.0.0.0/8; 4 | allow 192.168.0.0/16; 5 | allow 172.16.0.0/12; 6 | allow fc00::/7; # IPv6 local address range 7 | deny all; 8 | 9 | # Dummy header for testing 10 | add_header X-network internal; 11 | 12 | -------------------------------------------------------------------------------- /test/test_server-down/test_server-down.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: web.nginx-proxy.tld 8 | net: "none" 9 | 10 | sut: 11 | image: nginxproxy/nginx-proxy:test 12 | volumes: 13 | - /var/run/docker.sock:/tmp/docker.sock:ro 14 | -------------------------------------------------------------------------------- /test/test_http_port.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "*.nginx-proxy.tld" 8 | 9 | sut: 10 | image: nginxproxy/nginx-proxy:test 11 | volumes: 12 | - /var/run/docker.sock:/tmp/docker.sock:ro 13 | environment: 14 | HTTP_PORT: 8080 -------------------------------------------------------------------------------- /test/test_multiple-ports/test_single-port-not-80.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "web.nginx-proxy.tld" 8 | 9 | 10 | sut: 11 | image: nginxproxy/nginx-proxy:test 12 | volumes: 13 | - /var/run/docker.sock:/tmp/docker.sock:ro 14 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/README.md: -------------------------------------------------------------------------------- 1 | In this scenario, we have a wildcard certificate for `*.web.nginx-proxy.tld` and 3 web containers: 2 | - 1.web.nginx-proxy.tld 3 | - 2.web.nginx-proxy.tld 4 | - 3.web.nginx-proxy.tld 5 | 6 | We want web containers 1 and 2 to support SSL, but 3 should not (using `HTTPS_METHOD=nohttps`) -------------------------------------------------------------------------------- /test/test_multiple-ports/test_default-80.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "80" 5 | - "81" 6 | environment: 7 | WEB_PORTS: "80 81" 8 | VIRTUAL_HOST: "web.nginx-proxy.tld" 9 | 10 | sut: 11 | image: nginxproxy/nginx-proxy:test 12 | volumes: 13 | - /var/run/docker.sock:/tmp/docker.sock:ro 14 | -------------------------------------------------------------------------------- /test/test_server-down/test_server-down.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_web_has_server_down(docker_compose, nginxproxy): 4 | conf = nginxproxy.get_conf().decode('ASCII') 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 6 | assert r.status_code in [502, 503] 7 | assert conf.count("server 127.0.0.1 down;") == 1 8 | -------------------------------------------------------------------------------- /test/test_ssl/test_nohttps.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "83" 5 | environment: 6 | WEB_PORTS: "83" 7 | VIRTUAL_HOST: "web.nginx-proxy.tld" 8 | HTTPS_METHOD: nohttps 9 | 10 | 11 | sut: 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | -------------------------------------------------------------------------------- /test/test_ssl/test_wildcard.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "*.nginx-proxy.tld" 8 | 9 | sut: 10 | image: nginxproxy/nginx-proxy:test 11 | volumes: 12 | - /var/run/docker.sock:/tmp/docker.sock:ro 13 | - ./certs:/etc/nginx/certs:ro 14 | -------------------------------------------------------------------------------- /test/stress_tests/test_deleted_cert/README.md: -------------------------------------------------------------------------------- 1 | Test the behavior of nginx-proxy when restarted after deleting a certificate file is was using. 2 | 3 | 1. nginx-proxy is created with a virtual host having a certificate 4 | 1. while nginx-proxy is running, the certificate file is deleted 5 | 1. nginx-proxy is then restarted (without removing the container) 6 | -------------------------------------------------------------------------------- /test/test_composev2.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx-proxy: 4 | image: nginxproxy/nginx-proxy:test 5 | volumes: 6 | - /var/run/docker.sock:/tmp/docker.sock:ro 7 | 8 | web: 9 | image: web 10 | expose: 11 | - "81" 12 | environment: 13 | WEB_PORTS: 81 14 | VIRTUAL_HOST: web.nginx-proxy.local 15 | -------------------------------------------------------------------------------- /test/test_http_port.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize("subdomain", ["foo", "bar"]) 5 | def test_web1_http_custom_port(docker_compose, nginxproxy, subdomain): 6 | r = nginxproxy.get("http://%s.nginx-proxy.tld:8080/port" % subdomain, allow_redirects=False) 7 | assert r.status_code == 200 8 | assert "answer from port 81\n" in r.text -------------------------------------------------------------------------------- /test/test_multiple-ports/test_VIRTUAL_PORT.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "80" 5 | - "90" 6 | environment: 7 | WEB_PORTS: "80 90" 8 | VIRTUAL_HOST: "web.nginx-proxy.tld" 9 | VIRTUAL_PORT: 90 10 | 11 | sut: 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx-proxy: 4 | image: nginxproxy/nginx-proxy 5 | container_name: nginx-proxy 6 | ports: 7 | - "80:80" 8 | volumes: 9 | - /var/run/docker.sock:/tmp/docker.sock:ro 10 | 11 | whoami: 12 | image: jwilder/whoami 13 | environment: 14 | - VIRTUAL_HOST=whoami.local 15 | -------------------------------------------------------------------------------- /test/requirements/web/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | 4 | trap '[ ${#PIDS[@]} -gt 0 ] && kill -TERM ${PIDS[@]}' TERM 5 | declare -a PIDS 6 | 7 | for port in $WEB_PORTS; do 8 | echo starting a web server listening on port $port; 9 | /webserver.py $port & 10 | PIDS+=($!) 11 | done 12 | 13 | wait ${PIDS[@]} 14 | trap - TERM 15 | wait ${PIDS[@]} 16 | -------------------------------------------------------------------------------- /test/test_ssl/test_nohttp.yml: -------------------------------------------------------------------------------- 1 | web2: 2 | image: web 3 | expose: 4 | - "82" 5 | environment: 6 | WEB_PORTS: "82" 7 | VIRTUAL_HOST: "web2.nginx-proxy.tld" 8 | HTTPS_METHOD: nohttp 9 | 10 | 11 | sut: 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | - ./certs:/etc/nginx/certs:ro 16 | -------------------------------------------------------------------------------- /test/test_upstream-name/test_predictable-name.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | web: 5 | image: web 6 | expose: 7 | - "80" 8 | environment: 9 | WEB_PORTS: 80 10 | VIRTUAL_HOST: web.nginx-proxy.tld 11 | 12 | sut: 13 | image: nginxproxy/nginx-proxy:test 14 | volumes: 15 | - /var/run/docker.sock:/tmp/docker.sock:ro 16 | -------------------------------------------------------------------------------- /test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "web.nginx-proxy.tld" 8 | VIRTUAL_PORT: "90" 9 | 10 | 11 | sut: 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | -------------------------------------------------------------------------------- /test/test_server-down/test_no-server-down.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_web_has_no_server_down(docker_compose, nginxproxy): 4 | conf = nginxproxy.get_conf().decode('ASCII') 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 6 | assert r.status_code == 200 7 | assert r.text == "answer from port 81\n" 8 | assert conf.count("server 127.0.0.1 down;") == 0 9 | -------------------------------------------------------------------------------- /test/test_ssl/test_noredirect.yml: -------------------------------------------------------------------------------- 1 | web3: 2 | image: web 3 | expose: 4 | - "83" 5 | environment: 6 | WEB_PORTS: "83" 7 | VIRTUAL_HOST: "web3.nginx-proxy.tld" 8 | HTTPS_METHOD: noredirect 9 | 10 | 11 | sut: 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | - ./certs:/etc/nginx/certs:ro 16 | -------------------------------------------------------------------------------- /test/test_ssl/test_https_port.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "*.nginx-proxy.tld" 8 | 9 | sut: 10 | image: nginxproxy/nginx-proxy:test 11 | volumes: 12 | - /var/run/docker.sock:/tmp/docker.sock:ro 13 | - ./certs:/etc/nginx/certs:ro 14 | environment: 15 | HTTP_PORT: 8080 16 | HTTPS_PORT: 8443 -------------------------------------------------------------------------------- /test/test_composev2.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_unknown_virtual_host(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | 7 | def test_forwards_to_whoami(docker_compose, nginxproxy): 8 | r = nginxproxy.get("http://web.nginx-proxy.local/port") 9 | assert r.status_code == 200 10 | assert r.text == "answer from port 81\n" 11 | -------------------------------------------------------------------------------- /test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import re 3 | 4 | 5 | def test_answer_is_served_from_virtual_port_which_is_ureachable(docker_compose, nginxproxy): 6 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 7 | assert r.status_code == 502 8 | assert re.search(r"\n\s+server \d+\.\d+\.\d+\.\d+:90;\n", nginxproxy.get_conf().decode('ASCII')) 9 | -------------------------------------------------------------------------------- /test/test_server-down/test_load-balancing.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_web_has_no_server_down(docker_compose, nginxproxy): 4 | conf = nginxproxy.get_conf().decode('ASCII') 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 6 | assert r.status_code == 200 7 | assert (r.text == "answer from port 81\n") or (r.text == "answer from port 82\n") 8 | assert conf.count("server 127.0.0.1 down;") == 0 9 | -------------------------------------------------------------------------------- /test/test_upstream-name/test_sha1-name.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | web: 5 | image: web 6 | expose: 7 | - "80" 8 | environment: 9 | WEB_PORTS: 80 10 | VIRTUAL_HOST: web.nginx-proxy.tld 11 | 12 | sut: 13 | image: nginxproxy/nginx-proxy:test 14 | volumes: 15 | - /var/run/docker.sock:/tmp/docker.sock:ro 16 | environment: 17 | SHA1_UPSTREAM_NAME: "true" 18 | -------------------------------------------------------------------------------- /test/stress_tests/test_deleted_cert/docker-compose.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: web.nginx-proxy 8 | 9 | 10 | reverseproxy: 11 | image: nginxproxy/nginx-proxy:test 12 | container_name: reverseproxy 13 | environment: 14 | DEBUG: "true" 15 | volumes: 16 | - /var/run/docker.sock:/tmp/docker.sock:ro 17 | - ./tmp_certs:/etc/nginx/certs:ro -------------------------------------------------------------------------------- /test/test_default-host.yml: -------------------------------------------------------------------------------- 1 | # GIVEN a webserver with VIRTUAL_HOST set to web1.tld 2 | web1: 3 | image: web 4 | expose: 5 | - "81" 6 | environment: 7 | WEB_PORTS: 81 8 | VIRTUAL_HOST: web1.tld 9 | 10 | 11 | # WHEN nginx-proxy runs with DEFAULT_HOST set to web1.tld 12 | sut: 13 | image: nginxproxy/nginx-proxy:test 14 | volumes: 15 | - /var/run/docker.sock:/tmp/docker.sock:ro 16 | environment: 17 | DEFAULT_HOST: web1.tld 18 | -------------------------------------------------------------------------------- /app/dhparam/ffdhe2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== 8 | -----END DH PARAMETERS----- -------------------------------------------------------------------------------- /test/test_virtual-path/test_forwarding.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "www.nginx-proxy.tld" 8 | VIRTUAL_PATH: "/web1/" 9 | VIRTUAL_DEST: "/" 10 | 11 | sut: 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | - ./certs:/etc/nginx/certs:ro 16 | environment: 17 | - DEFAULT_ROOT=301 http://$$host/web1$$request_uri 18 | -------------------------------------------------------------------------------- /test/test_ssl/certs/web2.nginx-proxy.tld.dhparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== 8 | -----END DH PARAMETERS----- -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for Docker 5 | - package-ecosystem: "docker" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | labels: 10 | - "type/build" 11 | - "scope/dockerfile" 12 | 13 | # Maintain Python dependencies (test suite) 14 | - package-ecosystem: "pip" 15 | directory: "/test/requirements" 16 | schedule: 17 | interval: "daily" 18 | labels: 19 | - "type/ci" 20 | -------------------------------------------------------------------------------- /test/test_DOCKER_HOST_unix_socket.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: web1.nginx-proxy.tld 8 | 9 | web2: 10 | image: web 11 | expose: 12 | - "82" 13 | environment: 14 | WEB_PORTS: 82 15 | VIRTUAL_HOST: web2.nginx-proxy.tld 16 | 17 | 18 | sut: 19 | image: nginxproxy/nginx-proxy:test 20 | volumes: 21 | - /var/run/docker.sock:/f00.sock:ro 22 | environment: 23 | DOCKER_HOST: unix:///f00.sock 24 | -------------------------------------------------------------------------------- /test/test_headers/test_http.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "80" 5 | environment: 6 | WEB_PORTS: 80 7 | VIRTUAL_HOST: web.nginx-proxy.tld 8 | 9 | web-server-tokens-off: 10 | image: web 11 | expose: 12 | - "80" 13 | environment: 14 | WEB_PORTS: 80 15 | VIRTUAL_HOST: web-server-tokens-off.nginx-proxy.tld 16 | SERVER_TOKENS: "off" 17 | 18 | 19 | sut: 20 | image: nginxproxy/nginx-proxy:test 21 | volumes: 22 | - /var/run/docker.sock:/tmp/docker.sock:ro 23 | -------------------------------------------------------------------------------- /test/test_debug/test_server-debug-flag.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import re 3 | 4 | def test_debug_info_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): 5 | conf = nginxproxy.get_conf().decode('ASCII') 6 | assert re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \}\]", conf) or \ 7 | re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \}\]", conf) 8 | assert conf.count("# Exposed ports: [{") == 1 9 | -------------------------------------------------------------------------------- /test/test_ssl/test_nohttps.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from requests import ConnectionError 3 | 4 | def test_http_is_forwarded(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port", allow_redirects=False) 6 | assert r.status_code == 200 7 | assert "answer from port 83\n" in r.text 8 | 9 | 10 | def test_https_is_disabled(docker_compose, nginxproxy): 11 | with pytest.raises(ConnectionError): 12 | nginxproxy.get("https://web.nginx-proxy.tld/", allow_redirects=False) 13 | -------------------------------------------------------------------------------- /test/test_upstream-name/test_sha1-name.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import re 3 | 4 | 5 | def test_sha1_upstream_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): 6 | conf = nginxproxy.get_conf().decode('ASCII') 7 | assert re.search(r"upstream 3e837201a6255962094cd6d8f61e22b07d3cc8ed \{", conf) 8 | 9 | def test_sha1_upstream_forwards_correctly(docker_compose, nginxproxy): 10 | r = nginxproxy.get("http://web.nginx-proxy.tld/port") 11 | assert r.status_code == 200 12 | assert r.text == "answer from port 80\n" 13 | -------------------------------------------------------------------------------- /test/test_internal/test_internal-per-vhost.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: web1.nginx-proxy.local 8 | NETWORK_ACCESS: internal 9 | 10 | web2: 11 | image: web 12 | expose: 13 | - "82" 14 | environment: 15 | WEB_PORTS: 82 16 | VIRTUAL_HOST: web2.nginx-proxy.local 17 | 18 | sut: 19 | image: nginxproxy/nginx-proxy:test 20 | volumes: 21 | - /var/run/docker.sock:/tmp/docker.sock:ro 22 | - ./network_internal.conf:/etc/nginx/network_internal.conf:ro 23 | 24 | -------------------------------------------------------------------------------- /test/test_debug/test_server-debug-flag.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "80" 5 | - "81" 6 | environment: 7 | WEB_PORTS: "80 81" 8 | VIRTUAL_HOST: "web1.nginx-proxy.tld" 9 | VIRTUAL_PORT: "82" 10 | DEBUG: "true" 11 | 12 | web2: 13 | image: web 14 | expose: 15 | - "82" 16 | - "83" 17 | environment: 18 | WEB_PORTS: "82 83" 19 | VIRTUAL_HOST: "web2.nginx-proxy.tld" 20 | VIRTUAL_PORT: "82" 21 | 22 | sut: 23 | image: nginxproxy/nginx-proxy:test 24 | volumes: 25 | - /var/run/docker.sock:/tmp/docker.sock:ro 26 | -------------------------------------------------------------------------------- /test/test_custom/test_defaults.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx-proxy: 4 | image: nginxproxy/nginx-proxy:test 5 | volumes: 6 | - /var/run/docker.sock:/tmp/docker.sock:ro 7 | - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro 8 | 9 | web1: 10 | image: web 11 | expose: 12 | - "81" 13 | environment: 14 | WEB_PORTS: 81 15 | VIRTUAL_HOST: web1.nginx-proxy.local 16 | 17 | web2: 18 | image: web 19 | expose: 20 | - "82" 21 | environment: 22 | WEB_PORTS: 82 23 | VIRTUAL_HOST: web2.nginx-proxy.local 24 | -------------------------------------------------------------------------------- /test/test_debug/test_proxy-debug-flag.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "80" 5 | - "81" 6 | environment: 7 | WEB_PORTS: "80 81" 8 | VIRTUAL_HOST: "web1.nginx-proxy.tld" 9 | VIRTUAL_PORT: "82" 10 | 11 | web2: 12 | image: web 13 | expose: 14 | - "82" 15 | - "83" 16 | environment: 17 | WEB_PORTS: "82 83" 18 | VIRTUAL_HOST: "web2.nginx-proxy.tld" 19 | VIRTUAL_PORT: "82" 20 | 21 | sut: 22 | image: nginxproxy/nginx-proxy:test 23 | volumes: 24 | - /var/run/docker.sock:/tmp/docker.sock:ro 25 | environment: 26 | DEBUG: "true" 27 | -------------------------------------------------------------------------------- /test/test_custom/test_per-vhost.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx-proxy: 4 | image: nginxproxy/nginx-proxy:test 5 | volumes: 6 | - /var/run/docker.sock:/tmp/docker.sock:ro 7 | - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local:ro 8 | 9 | web1: 10 | image: web 11 | expose: 12 | - "81" 13 | environment: 14 | WEB_PORTS: 81 15 | VIRTUAL_HOST: web1.nginx-proxy.local 16 | 17 | web2: 18 | image: web 19 | expose: 20 | - "82" 21 | environment: 22 | WEB_PORTS: 82 23 | VIRTUAL_HOST: web2.nginx-proxy.local 24 | -------------------------------------------------------------------------------- /test/test_ssl/test_virtual_path.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "www.nginx-proxy.tld" 8 | VIRTUAL_PATH: "/web1/" 9 | VIRTUAL_DEST: "/" 10 | 11 | web2: 12 | image: web 13 | expose: 14 | - "82" 15 | environment: 16 | WEB_PORTS: "82" 17 | VIRTUAL_HOST: "www.nginx-proxy.tld" 18 | VIRTUAL_PATH: "/web2/" 19 | VIRTUAL_DEST: "/" 20 | 21 | sut: 22 | image: nginxproxy/nginx-proxy:test 23 | volumes: 24 | - /var/run/docker.sock:/tmp/docker.sock:ro 25 | - ./certs:/etc/nginx/certs:ro 26 | 27 | -------------------------------------------------------------------------------- /test/test_custom/test_proxy-wide.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx-proxy: 4 | image: nginxproxy/nginx-proxy:test 5 | volumes: 6 | - /var/run/docker.sock:/tmp/docker.sock:ro 7 | - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro 8 | 9 | web1: 10 | image: web 11 | expose: 12 | - "81" 13 | environment: 14 | WEB_PORTS: 81 15 | VIRTUAL_HOST: web1.nginx-proxy.local 16 | 17 | web2: 18 | image: web 19 | expose: 20 | - "82" 21 | environment: 22 | WEB_PORTS: 82 23 | VIRTUAL_HOST: web2.nginx-proxy.local 24 | -------------------------------------------------------------------------------- /test/test_internal/test_internal-per-vhost.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_network_web1(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 5 | assert r.status_code == 200 6 | assert r.text == "answer from port 81\n" 7 | assert "X-network" in r.headers 8 | assert "internal" == r.headers["X-network"] 9 | 10 | def test_network_web2(docker_compose, nginxproxy): 11 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 12 | assert r.status_code == 200 13 | assert r.text == "answer from port 82\n" 14 | assert "X-network" not in r.headers 15 | -------------------------------------------------------------------------------- /test/test_internal/test_internal-per-vpath.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_network_web1(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy.local/web1/port") 5 | assert r.status_code == 200 6 | assert r.text == "answer from port 81\n" 7 | assert "X-network" in r.headers 8 | assert "internal" == r.headers["X-network"] 9 | 10 | def test_network_web2(docker_compose, nginxproxy): 11 | r = nginxproxy.get("http://nginx-proxy.local/web2/port") 12 | assert r.status_code == 200 13 | assert r.text == "answer from port 82\n" 14 | assert "X-network" not in r.headers 15 | -------------------------------------------------------------------------------- /test/test_server-down/test_load-balancing.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: web.nginx-proxy.tld 8 | 9 | web2: 10 | image: web 11 | expose: 12 | - "82" 13 | environment: 14 | WEB_PORTS: 83 15 | VIRTUAL_HOST: web.nginx-proxy.tld 16 | 17 | web3: 18 | image: web 19 | expose: 20 | - "83" 21 | environment: 22 | WEB_PORTS: 83 23 | VIRTUAL_HOST: web.nginx-proxy.tld 24 | net: "none" 25 | 26 | sut: 27 | image: nginxproxy/nginx-proxy:test 28 | volumes: 29 | - /var/run/docker.sock:/tmp/docker.sock:ro 30 | -------------------------------------------------------------------------------- /test/test_multiple-networks.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_unknown_virtual_host(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | 7 | def test_forwards_to_web1(docker_compose, nginxproxy): 8 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 9 | assert r.status_code == 200 10 | assert r.text == "answer from port 81\n" 11 | 12 | def test_forwards_to_web2(docker_compose, nginxproxy): 13 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 14 | assert r.status_code == 200 15 | assert r.text == "answer from port 82\n" -------------------------------------------------------------------------------- /test/test_custom/test_location-per-vhost.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx-proxy: 4 | image: nginxproxy/nginx-proxy:test 5 | volumes: 6 | - /var/run/docker.sock:/tmp/docker.sock:ro 7 | - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local_location:ro 8 | 9 | web1: 10 | image: web 11 | expose: 12 | - "81" 13 | environment: 14 | WEB_PORTS: 81 15 | VIRTUAL_HOST: web1.nginx-proxy.local 16 | 17 | web2: 18 | image: web 19 | expose: 20 | - "82" 21 | environment: 22 | WEB_PORTS: 82 23 | VIRTUAL_HOST: web2.nginx-proxy.local 24 | -------------------------------------------------------------------------------- /test/test_raw-ip-vhost.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_raw_ipv4_vhost_forwards_to_web1(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://172.20.0.4") 6 | assert r.status_code == 200 7 | web1_container = docker_compose.containers.get("web1") 8 | assert r.text == f"I'm {web1_container.id[:12]}\n" 9 | 10 | 11 | def test_raw_ipv6_vhost_forwards_to_web2(docker_compose, nginxproxy): 12 | r = nginxproxy.get("http://[fd00::4]", ipv6=True) 13 | assert r.status_code == 200 14 | web2_container = docker_compose.containers.get("web2") 15 | assert r.text == f"I'm {web2_container.id[:12]}\n" 16 | -------------------------------------------------------------------------------- /test/test_DOCKER_HOST_unix_socket.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_unknown_virtual_host(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/port") 5 | assert r.status_code == 503 6 | 7 | def test_forwards_to_web1(docker_compose, nginxproxy): 8 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port") 9 | assert r.status_code == 200 10 | assert r.text == "answer from port 81\n" 11 | 12 | def test_forwards_to_web2(docker_compose, nginxproxy): 13 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port") 14 | assert r.status_code == 200 15 | assert r.text == "answer from port 82\n" 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SILENT : 2 | .PHONY : test-debian test-alpine test 3 | 4 | 5 | build-webserver: 6 | docker build -t web test/requirements/web 7 | 8 | build-nginx-proxy-test-debian: 9 | docker build --build-arg NGINX_PROXY_VERSION="test" -t nginxproxy/nginx-proxy:test . 10 | 11 | build-nginx-proxy-test-alpine: 12 | docker build --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test . 13 | 14 | test-debian: build-webserver build-nginx-proxy-test-debian 15 | test/pytest.sh 16 | 17 | test-alpine: build-webserver build-nginx-proxy-test-alpine 18 | test/pytest.sh 19 | 20 | test: test-debian test-alpine 21 | -------------------------------------------------------------------------------- /app/dhparam/ffdhe3072.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 8 | 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 9 | nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu 10 | N///////////AgEC 11 | -----END DH PARAMETERS----- -------------------------------------------------------------------------------- /test/test_multiple-hosts.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://unknown.nginx-proxy.tld/port") 6 | assert r.status_code == 503 7 | 8 | def test_webA_is_forwarded(docker_compose, nginxproxy): 9 | r = nginxproxy.get("http://webA.nginx-proxy.tld/port") 10 | assert r.status_code == 200 11 | assert r.text == "answer from port 81\n" 12 | 13 | def test_webB_is_forwarded(docker_compose, nginxproxy): 14 | r = nginxproxy.get("http://webB.nginx-proxy.tld/port") 15 | assert r.status_code == 200 16 | assert r.text == "answer from port 81\n" 17 | -------------------------------------------------------------------------------- /test/test_internal/test_internal-per-vpath.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: 81 7 | VIRTUAL_HOST: nginx-proxy.local 8 | VIRTUAL_PATH: /web1/ 9 | VIRTUAL_DEST: / 10 | NETWORK_ACCESS: internal 11 | 12 | web2: 13 | image: web 14 | expose: 15 | - "82" 16 | environment: 17 | WEB_PORTS: 82 18 | VIRTUAL_HOST: nginx-proxy.local 19 | VIRTUAL_PATH: /web2/ 20 | VIRTUAL_DEST: / 21 | 22 | sut: 23 | image: nginxproxy/nginx-proxy:test 24 | volumes: 25 | - /var/run/docker.sock:/tmp/docker.sock:ro 26 | - ./network_internal.conf:/etc/nginx/network_internal.conf:ro 27 | 28 | -------------------------------------------------------------------------------- /docker-compose-separate-containers.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx: 4 | image: nginx 5 | container_name: nginx 6 | ports: 7 | - "80:80" 8 | volumes: 9 | - /etc/nginx/conf.d 10 | 11 | dockergen: 12 | image: nginxproxy/docker-gen 13 | command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl 14 | /etc/nginx/conf.d/default.conf 15 | volumes_from: 16 | - nginx 17 | volumes: 18 | - /var/run/docker.sock:/tmp/docker.sock:ro 19 | - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl 20 | 21 | whoami: 22 | image: jwilder/whoami 23 | environment: 24 | - VIRTUAL_HOST=whoami.local 25 | -------------------------------------------------------------------------------- /test/test_multiple-networks.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | networks: 4 | net1: {} 5 | net2: {} 6 | 7 | services: 8 | nginx-proxy: 9 | image: nginxproxy/nginx-proxy:test 10 | volumes: 11 | - /var/run/docker.sock:/tmp/docker.sock:ro 12 | networks: 13 | - net1 14 | - net2 15 | 16 | web1: 17 | image: web 18 | expose: 19 | - "81" 20 | environment: 21 | WEB_PORTS: 81 22 | VIRTUAL_HOST: web1.nginx-proxy.local 23 | networks: 24 | - net1 25 | 26 | web2: 27 | image: web 28 | expose: 29 | - "82" 30 | environment: 31 | WEB_PORTS: 82 32 | VIRTUAL_HOST: web2.nginx-proxy.local 33 | networks: 34 | - net2 35 | -------------------------------------------------------------------------------- /test/test_dockergen/test_dockergen_v2.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | nginx: 5 | image: nginx 6 | container_name: nginx 7 | volumes: 8 | - /etc/nginx/conf.d 9 | 10 | dockergen: 11 | image: nginxproxy/docker-gen 12 | command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf 13 | volumes_from: 14 | - nginx 15 | volumes: 16 | - /var/run/docker.sock:/tmp/docker.sock:ro 17 | - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl 18 | 19 | web: 20 | image: web 21 | container_name: whoami 22 | expose: 23 | - "80" 24 | environment: 25 | WEB_PORTS: 80 26 | VIRTUAL_HOST: whoami.nginx.container.docker 27 | -------------------------------------------------------------------------------- /test/stress_tests/test_unreachable_network/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | networks: 4 | netA: 5 | netB: 6 | 7 | services: 8 | reverseproxy: 9 | container_name: reverseproxy 10 | networks: 11 | - netA 12 | image: nginxproxy/nginx-proxy:test 13 | volumes: 14 | - /var/run/docker.sock:/tmp/docker.sock:ro 15 | 16 | webA: 17 | networks: 18 | - netA 19 | image: web 20 | expose: 21 | - 81 22 | environment: 23 | WEB_PORTS: 81 24 | VIRTUAL_HOST: webA.nginx-proxy 25 | 26 | webB: 27 | networks: 28 | - netB 29 | image: web 30 | expose: 31 | - 82 32 | environment: 33 | WEB_PORTS: 82 34 | VIRTUAL_HOST: webB.nginx-proxy 35 | 36 | -------------------------------------------------------------------------------- /test/test_nominal.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | networks: 4 | net1: 5 | enable_ipv6: true 6 | ipam: 7 | config: 8 | - subnet: fd00:1::/80 9 | 10 | services: 11 | web1: 12 | image: web 13 | expose: 14 | - "81" 15 | environment: 16 | WEB_PORTS: 81 17 | VIRTUAL_HOST: web1.nginx-proxy.tld 18 | networks: 19 | - net1 20 | 21 | web2: 22 | image: web 23 | expose: 24 | - "82" 25 | environment: 26 | WEB_PORTS: 82 27 | VIRTUAL_HOST: web2.nginx-proxy.tld 28 | networks: 29 | - net1 30 | 31 | 32 | sut: 33 | image: nginxproxy/nginx-proxy:test 34 | volumes: 35 | - /var/run/docker.sock:/tmp/docker.sock:ro 36 | networks: 37 | - net1 38 | -------------------------------------------------------------------------------- /test/test_ssl/test_nohttp.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_web2_http_is_not_forwarded(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://web2.nginx-proxy.tld/", allow_redirects=False) 6 | assert r.status_code == 503 7 | 8 | 9 | def test_web2_https_is_forwarded(docker_compose, nginxproxy): 10 | r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False) 11 | assert r.status_code == 200 12 | assert "answer from port 82\n" in r.text 13 | 14 | 15 | def test_web2_HSTS_policy_is_active(docker_compose, nginxproxy): 16 | r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False) 17 | assert "answer from port 82\n" in r.text 18 | assert "Strict-Transport-Security" in r.headers 19 | -------------------------------------------------------------------------------- /test/test_ssl/test_https_port.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | @pytest.mark.parametrize("subdomain", ["foo", "bar"]) 4 | def test_web1_http_redirects_to_https(docker_compose, nginxproxy, subdomain): 5 | r = nginxproxy.get("http://%s.nginx-proxy.tld:8080/" % subdomain, allow_redirects=False) 6 | assert r.status_code == 301 7 | assert "Location" in r.headers 8 | assert "https://%s.nginx-proxy.tld:8443/" % subdomain == r.headers['Location'] 9 | 10 | @pytest.mark.parametrize("subdomain", ["foo", "bar"]) 11 | def test_web1_https_is_forwarded(docker_compose, nginxproxy, subdomain): 12 | r = nginxproxy.get("https://%s.nginx-proxy.tld:8443/port" % subdomain, allow_redirects=False) 13 | assert r.status_code == 200 14 | assert "answer from port 81\n" in r.text -------------------------------------------------------------------------------- /test/test_dockergen/test_dockergen_v3.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | nginx: 4 | image: nginx 5 | container_name: nginx 6 | volumes: 7 | - nginx_conf:/etc/nginx/conf.d 8 | 9 | dockergen: 10 | image: nginxproxy/docker-gen 11 | command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf 12 | volumes: 13 | - /var/run/docker.sock:/tmp/docker.sock:ro 14 | - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl 15 | - nginx_conf:/etc/nginx/conf.d 16 | 17 | web: 18 | image: web 19 | container_name: whoami 20 | expose: 21 | - "80" 22 | environment: 23 | WEB_PORTS: 80 24 | VIRTUAL_HOST: whoami.nginx.container.docker 25 | 26 | volumes: 27 | nginx_conf: {} 28 | -------------------------------------------------------------------------------- /test/test_ssl/test_virtual_path.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | @pytest.mark.parametrize("path", ["web1", "web2"]) 4 | def test_web1_http_redirects_to_https(docker_compose, nginxproxy, path): 5 | r = nginxproxy.get("http://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False) 6 | assert r.status_code == 301 7 | assert "Location" in r.headers 8 | assert "https://www.nginx-proxy.tld/%s/port" % path == r.headers['Location'] 9 | 10 | @pytest.mark.parametrize("path,port", [("web1", 81), ("web2", 82)]) 11 | def test_web1_https_is_forwarded(docker_compose, nginxproxy, path, port): 12 | r = nginxproxy.get("https://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False) 13 | assert r.status_code == 200 14 | assert "answer from port %d\n" % port in r.text 15 | 16 | -------------------------------------------------------------------------------- /test/test_custom/test_defaults-location.yml: -------------------------------------------------------------------------------- 1 | nginx-proxy: 2 | image: nginxproxy/nginx-proxy:test 3 | volumes: 4 | - /var/run/docker.sock:/tmp/docker.sock:ro 5 | - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro 6 | - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.local_location:ro 7 | 8 | web1: 9 | image: web 10 | expose: 11 | - "81" 12 | environment: 13 | WEB_PORTS: 81 14 | VIRTUAL_HOST: web1.nginx-proxy.local 15 | 16 | web2: 17 | image: web 18 | expose: 19 | - "82" 20 | environment: 21 | WEB_PORTS: 82 22 | VIRTUAL_HOST: web2.nginx-proxy.local 23 | 24 | web3: 25 | image: web 26 | expose: 27 | - "83" 28 | environment: 29 | WEB_PORTS: 83 30 | VIRTUAL_HOST: web3.nginx-proxy.local 31 | -------------------------------------------------------------------------------- /test/test_ipv6.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | networks: 4 | net1: 5 | enable_ipv6: true 6 | ipam: 7 | config: 8 | - subnet: fd00:1::/80 9 | 10 | services: 11 | web1: 12 | image: web 13 | expose: 14 | - "81" 15 | environment: 16 | WEB_PORTS: 81 17 | VIRTUAL_HOST: web1.nginx-proxy.tld 18 | networks: 19 | - net1 20 | 21 | web2: 22 | image: web 23 | expose: 24 | - "82" 25 | environment: 26 | WEB_PORTS: 82 27 | VIRTUAL_HOST: web2.nginx-proxy.tld 28 | networks: 29 | - net1 30 | 31 | 32 | sut: 33 | image: nginxproxy/nginx-proxy:test 34 | volumes: 35 | - /var/run/docker.sock:/tmp/docker.sock:ro 36 | environment: 37 | ENABLE_IPV6: "true" 38 | networks: 39 | - net1 40 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | proxy: 6 | image: nginxproxy/nginx-proxy:test 7 | volumes: 8 | - /var/run/docker.sock:/tmp/docker.sock:ro 9 | - ./certs:/etc/nginx/certs:ro 10 | 11 | web1: 12 | image: web 13 | expose: 14 | - "81" 15 | environment: 16 | WEB_PORTS: "81" 17 | VIRTUAL_HOST: "1.web.nginx-proxy.tld" 18 | web2: 19 | image: web 20 | expose: 21 | - "82" 22 | environment: 23 | WEB_PORTS: "82" 24 | VIRTUAL_HOST: "2.web.nginx-proxy.tld" 25 | 26 | web3_nohttps: 27 | image: web 28 | expose: 29 | - "83" 30 | environment: 31 | WEB_PORTS: "83" 32 | VIRTUAL_HOST: "3.web.nginx-proxy.tld" 33 | HTTPS_METHOD: nohttps 34 | -------------------------------------------------------------------------------- /test/test_ssl/test_noredirect.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_web3_http_is_forwarded(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://web3.nginx-proxy.tld/port", allow_redirects=False) 6 | assert r.status_code == 200 7 | assert "answer from port 83\n" in r.text 8 | 9 | 10 | def test_web3_https_is_forwarded(docker_compose, nginxproxy): 11 | r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False) 12 | assert r.status_code == 200 13 | assert "answer from port 83\n" in r.text 14 | 15 | 16 | def test_web2_HSTS_policy_is_inactive(docker_compose, nginxproxy): 17 | r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False) 18 | assert "answer from port 83\n" in r.text 19 | assert "Strict-Transport-Security" not in r.headers -------------------------------------------------------------------------------- /app/dhparam/ffdhe4096.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DH PARAMETERS----- 2 | MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz 3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a 4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi 6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD 7 | ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3 8 | 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32 9 | nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e 10 | 8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx 11 | iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K 12 | zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI= 13 | -----END DH PARAMETERS----- -------------------------------------------------------------------------------- /test/test_virtual-path/test_forwarding.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_root_redirects_to_web1(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=False) 5 | assert r.status_code == 301 6 | assert "Location" in r.headers 7 | assert "http://www.nginx-proxy.tld/web1/port" == r.headers['Location'] 8 | 9 | def test_direct_access(docker_compose, nginxproxy): 10 | r = nginxproxy.get("http://www.nginx-proxy.tld/web1/port", allow_redirects=False) 11 | assert r.status_code == 200 12 | assert "answer from port 81\n" in r.text 13 | 14 | def test_root_is_forwarded(docker_compose, nginxproxy): 15 | r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=True) 16 | assert r.status_code == 200 17 | assert "answer from port 81\n" in r.text 18 | 19 | -------------------------------------------------------------------------------- /test/test_custom/test_per-vhost.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | assert "X-test" not in r.headers 7 | 8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): 9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 10 | assert r.status_code == 200 11 | assert r.text == "answer from port 81\n" 12 | assert "X-test" in r.headers 13 | assert "f00" == r.headers["X-test"] 14 | 15 | def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): 16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 17 | assert r.status_code == 200 18 | assert r.text == "answer from port 82\n" 19 | assert "X-test" not in r.headers 20 | -------------------------------------------------------------------------------- /test/test_wildcard_host.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "*.nginx-proxy.test" 8 | 9 | web2: 10 | image: web 11 | expose: 12 | - "82" 13 | environment: 14 | WEB_PORTS: "82" 15 | VIRTUAL_HOST: "test.nginx-proxy.*" 16 | 17 | web3: 18 | image: web 19 | expose: 20 | - "83" 21 | environment: 22 | WEB_PORTS: "83" 23 | VIRTUAL_HOST: ~^web3\..*\.nginx-proxy\.regexp 24 | 25 | web4: 26 | image: web 27 | expose: 28 | - "84" 29 | environment: 30 | WEB_PORTS: "84" 31 | VIRTUAL_HOST: ~^web4\..*\.nginx-proxy\.regexp$$ # we need to double the `$` because of docker-compose variable interpolation 32 | 33 | 34 | sut: 35 | image: nginxproxy/nginx-proxy:test 36 | volumes: 37 | - /var/run/docker.sock:/tmp/docker.sock:ro 38 | -------------------------------------------------------------------------------- /test/test_custom/test_defaults.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | assert "X-test" not in r.headers 7 | 8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): 9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 10 | assert r.status_code == 200 11 | assert r.text == "answer from port 81\n" 12 | assert "X-test" in r.headers 13 | assert "f00" == r.headers["X-test"] 14 | 15 | def test_custom_conf_applies_to_web2(docker_compose, nginxproxy): 16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 17 | assert r.status_code == 200 18 | assert r.text == "answer from port 82\n" 19 | assert "X-test" in r.headers 20 | assert "f00" == r.headers["X-test"] 21 | -------------------------------------------------------------------------------- /test/test_debug/test_proxy-debug-flag.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import re 3 | 4 | def test_debug_info_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): 5 | conf = nginxproxy.get_conf().decode('ASCII') 6 | assert re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \}\]", conf) or \ 7 | re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \}\]", conf) 8 | assert re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+82\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+83\s+tcp \}\]", conf) or \ 9 | re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+83\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+82\s+tcp \}\]", conf) 10 | assert "# Default virtual port: 80" in conf 11 | assert "# VIRTUAL_PORT: 82" in conf 12 | assert conf.count("# /!\\ Virtual port not exposed") == 1 13 | -------------------------------------------------------------------------------- /test/test_custom/test_proxy-wide.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | assert "X-test" not in r.headers 7 | 8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): 9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 10 | assert r.status_code == 200 11 | assert r.text == "answer from port 81\n" 12 | assert "X-test" in r.headers 13 | assert "f00" == r.headers["X-test"] 14 | 15 | def test_custom_conf_applies_to_web2(docker_compose, nginxproxy): 16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 17 | assert r.status_code == 200 18 | assert r.text == "answer from port 82\n" 19 | assert "X-test" in r.headers 20 | assert "f00" == r.headers["X-test"] 21 | -------------------------------------------------------------------------------- /test/test_virtual-path/test_virtual_paths.yml: -------------------------------------------------------------------------------- 1 | 2 | foo: 3 | image: web 4 | expose: 5 | - "42" 6 | environment: 7 | WEB_PORTS: "42" 8 | VIRTUAL_HOST: "foo.nginx-proxy.test" 9 | 10 | web1: 11 | image: web 12 | expose: 13 | - "81" 14 | environment: 15 | WEB_PORTS: "81" 16 | VIRTUAL_HOST: "nginx-proxy.test" 17 | VIRTUAL_PATH: "/web1/" 18 | VIRTUAL_DEST: "/" 19 | 20 | web2: 21 | image: web 22 | expose: 23 | - "82" 24 | environment: 25 | WEB_PORTS: "82" 26 | VIRTUAL_HOST: "nginx-proxy.test" 27 | VIRTUAL_PATH: "/web2/" 28 | VIRTUAL_DEST: "/" 29 | 30 | web3: 31 | image: web 32 | expose: 33 | - "83" 34 | environment: 35 | WEB_PORTS: "83" 36 | VIRTUAL_HOST: "nginx-proxy.test" 37 | VIRTUAL_PATH: "/" 38 | 39 | sut: 40 | image: nginxproxy/nginx-proxy:test 41 | volumes: 42 | - /var/run/docker.sock:/tmp/docker.sock:ro 43 | -------------------------------------------------------------------------------- /test/test_ssl/test_hsts.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "web1.nginx-proxy.tld" 8 | 9 | web2: 10 | image: web 11 | expose: 12 | - "81" 13 | environment: 14 | WEB_PORTS: "81" 15 | VIRTUAL_HOST: "web2.nginx-proxy.tld" 16 | HSTS: "off" 17 | 18 | web3: 19 | image: web 20 | expose: 21 | - "81" 22 | environment: 23 | WEB_PORTS: "81" 24 | VIRTUAL_HOST: "web3.nginx-proxy.tld" 25 | HSTS: "max-age=86400; includeSubDomains; preload" 26 | 27 | web4: 28 | image: web 29 | expose: 30 | - "81" 31 | environment: 32 | WEB_PORTS: "81" 33 | VIRTUAL_HOST: "web4.nginx-proxy.tld" 34 | HSTS: "off" 35 | HTTPS_METHOD: "noredirect" 36 | 37 | sut: 38 | image: nginxproxy/nginx-proxy:test 39 | volumes: 40 | - /var/run/docker.sock:/tmp/docker.sock:ro 41 | - ./certs:/etc/nginx/certs:ro 42 | -------------------------------------------------------------------------------- /test/test_virtual-path/test_location_precedence.yml: -------------------------------------------------------------------------------- 1 | web1: 2 | image: web 3 | expose: 4 | - "81" 5 | environment: 6 | WEB_PORTS: "81" 7 | VIRTUAL_HOST: "foo.nginx-proxy.test" 8 | VIRTUAL_PATH: "/web1/" 9 | VIRTUAL_DEST: "/" 10 | 11 | web2: 12 | image: web 13 | expose: 14 | - "82" 15 | environment: 16 | WEB_PORTS: "82" 17 | VIRTUAL_HOST: "bar.nginx-proxy.test" 18 | VIRTUAL_PATH: "/web2/" 19 | VIRTUAL_DEST: "/" 20 | 21 | web3: 22 | image: web 23 | expose: 24 | - "83" 25 | environment: 26 | WEB_PORTS: "83" 27 | VIRTUAL_HOST: "bar.nginx-proxy.test" 28 | VIRTUAL_PATH: "/web3/" 29 | VIRTUAL_DEST: "/" 30 | 31 | sut: 32 | image: nginxproxy/nginx-proxy:test 33 | volumes: 34 | - /var/run/docker.sock:/tmp/docker.sock:ro 35 | - ./default.conf:/etc/nginx/vhost.d/default_location:ro 36 | - ./host.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_location:ro 37 | - ./path.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_99f2db0ed8aa95dbb5b87fca79c7eff2ff6bb8bd_location:ro 38 | -------------------------------------------------------------------------------- /test/test_custom/test_location-per-vhost.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | assert "X-test" not in r.headers 7 | 8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): 9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 10 | assert r.status_code == 200 11 | assert r.text == "answer from port 81\n" 12 | assert "X-test" in r.headers 13 | assert "f00" == r.headers["X-test"] 14 | 15 | def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): 16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 17 | assert r.status_code == 200 18 | assert r.text == "answer from port 82\n" 19 | assert "X-test" not in r.headers 20 | 21 | def test_custom_block_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): 22 | assert b"include /etc/nginx/vhost.d/web1.nginx-proxy.local_location;" in nginxproxy.get_conf() -------------------------------------------------------------------------------- /test/test_nominal.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from requests import ConnectionError 3 | 4 | 5 | def test_unknown_virtual_host(docker_compose, nginxproxy): 6 | r = nginxproxy.get("http://nginx-proxy/port") 7 | assert r.status_code == 503 8 | 9 | 10 | def test_forwards_to_web1(docker_compose, nginxproxy): 11 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port") 12 | assert r.status_code == 200 13 | assert r.text == "answer from port 81\n" 14 | 15 | 16 | def test_forwards_to_web2(docker_compose, nginxproxy): 17 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port") 18 | assert r.status_code == 200 19 | assert r.text == "answer from port 82\n" 20 | 21 | 22 | def test_ipv6_is_disabled_by_default(docker_compose, nginxproxy): 23 | with pytest.raises(ConnectionError): 24 | nginxproxy.get("http://nginx-proxy/port", ipv6=True) 25 | 26 | 27 | def test_container_version_is_displayed(docker_compose, nginxproxy): 28 | conf = nginxproxy.get_conf().decode('ASCII') 29 | assert "# nginx-proxy version : test" in conf 30 | -------------------------------------------------------------------------------- /test/test_headers/test_https.yml: -------------------------------------------------------------------------------- 1 | web: 2 | image: web 3 | expose: 4 | - "80" 5 | environment: 6 | WEB_PORTS: 80 7 | VIRTUAL_HOST: web.nginx-proxy.tld 8 | 9 | web-server-tokens-off: 10 | image: web 11 | expose: 12 | - "80" 13 | environment: 14 | WEB_PORTS: 80 15 | VIRTUAL_HOST: web-server-tokens-off.nginx-proxy.tld 16 | SERVER_TOKENS: "off" 17 | 18 | 19 | sut: 20 | image: nginxproxy/nginx-proxy:test 21 | volumes: 22 | - /var/run/docker.sock:/tmp/docker.sock:ro 23 | - ./certs/web.nginx-proxy.tld.crt:/etc/nginx/certs/default.crt:ro 24 | - ./certs/web.nginx-proxy.tld.key:/etc/nginx/certs/default.key:ro 25 | - ./certs/web.nginx-proxy.tld.crt:/etc/nginx/certs/web.nginx-proxy.tld.crt:ro 26 | - ./certs/web.nginx-proxy.tld.key:/etc/nginx/certs/web.nginx-proxy.tld.key:ro 27 | - ./certs/web-server-tokens-off.nginx-proxy.tld.crt:/etc/nginx/certs/web-server-tokens-off.nginx-proxy.tld.crt:ro 28 | - ./certs/web-server-tokens-off.nginx-proxy.tld.key:/etc/nginx/certs/web-server-tokens-off.nginx-proxy.tld.key:ro 29 | -------------------------------------------------------------------------------- /test/test_raw-ip-vhost.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | networks: 4 | net1: 5 | enable_ipv6: true 6 | ipam: 7 | config: 8 | - subnet: 172.20.0.0/16 9 | - subnet: fd00::/80 10 | 11 | services: 12 | web1: 13 | container_name: web1 14 | image: web 15 | expose: 16 | - "81" 17 | environment: 18 | WEB_PORTS: 81 19 | VIRTUAL_HOST: "172.20.0.4" 20 | networks: 21 | net1: 22 | ipv4_address: 172.20.0.2 23 | ipv6_address: fd00::2 24 | 25 | web2: 26 | container_name: web2 27 | image: web 28 | expose: 29 | - "82" 30 | environment: 31 | WEB_PORTS: 82 32 | VIRTUAL_HOST: "[fd00::4]" 33 | networks: 34 | net1: 35 | ipv4_address: 172.20.0.3 36 | ipv6_address: fd00::3 37 | 38 | sut: 39 | image: nginxproxy/nginx-proxy:test 40 | environment: 41 | ENABLE_IPV6: "true" 42 | volumes: 43 | - /var/run/docker.sock:/tmp/docker.sock:ro 44 | networks: 45 | net1: 46 | ipv4_address: 172.20.0.4 47 | ipv6_address: fd00::4 48 | -------------------------------------------------------------------------------- /test/test_ssl/test_wildcard.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize("subdomain", ["foo", "bar"]) 5 | def test_web1_http_redirects_to_https(docker_compose, nginxproxy, subdomain): 6 | r = nginxproxy.get(f"http://{subdomain}.nginx-proxy.tld/", allow_redirects=False) 7 | assert r.status_code == 301 8 | assert "Location" in r.headers 9 | assert f"https://{subdomain}.nginx-proxy.tld/" == r.headers['Location'] 10 | 11 | 12 | @pytest.mark.parametrize("subdomain", ["foo", "bar"]) 13 | def test_web1_https_is_forwarded(docker_compose, nginxproxy, subdomain): 14 | r = nginxproxy.get(f"https://{subdomain}.nginx-proxy.tld/port", allow_redirects=False) 15 | assert r.status_code == 200 16 | assert "answer from port 81\n" in r.text 17 | 18 | 19 | @pytest.mark.parametrize("subdomain", ["foo", "bar"]) 20 | def test_web1_HSTS_policy_is_active(docker_compose, nginxproxy, subdomain): 21 | r = nginxproxy.get(f"https://{subdomain}.nginx-proxy.tld/port", allow_redirects=False) 22 | assert "answer from port 81\n" in r.text 23 | assert "Strict-Transport-Security" in r.headers 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths-ignore: 7 | - 'LICENSE' 8 | - '**.md' 9 | pull_request: 10 | paths-ignore: 11 | - 'LICENSE' 12 | - '**.md' 13 | 14 | jobs: 15 | unit: 16 | name: Unit Tests 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | base_docker_image: [alpine, debian] 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Set up Python 3.9 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: 3.9 30 | 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | pip install -r python-requirements.txt 35 | working-directory: test/requirements 36 | 37 | - name: Build Docker web server image 38 | run: make build-webserver 39 | 40 | - name: Build Docker nginx proxy test image 41 | run: make build-nginx-proxy-test-${{ matrix.base_docker_image }} 42 | 43 | - name: Run tests 44 | run: pytest 45 | working-directory: test 46 | -------------------------------------------------------------------------------- /test/test_virtual-path/test_location_precedence.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_location_precedence_case1(docker_compose, nginxproxy): 4 | r = nginxproxy.get(f"http://foo.nginx-proxy.test/web1/port") 5 | assert r.status_code == 200 6 | 7 | assert "X-test-default" in r.headers 8 | assert "X-test-host" not in r.headers 9 | assert "X-test-path" not in r.headers 10 | 11 | assert r.headers["X-test-default"] == "true" 12 | 13 | def test_location_precedence_case2(docker_compose, nginxproxy): 14 | r = nginxproxy.get(f"http://bar.nginx-proxy.test/web2/port") 15 | assert r.status_code == 200 16 | 17 | assert "X-test-default" not in r.headers 18 | assert "X-test-host" in r.headers 19 | assert "X-test-path" not in r.headers 20 | 21 | assert r.headers["X-test-host"] == "true" 22 | 23 | def test_location_precedence_case3(docker_compose, nginxproxy): 24 | r = nginxproxy.get(f"http://bar.nginx-proxy.test/web3/port") 25 | assert r.status_code == 200 26 | 27 | assert "X-test-default" not in r.headers 28 | assert "X-test-host" not in r.headers 29 | assert "X-test-path" in r.headers 30 | 31 | assert r.headers["X-test-path"] == "true" 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2020 Jason Wilder 4 | Copyright (c) 2021-2022 Nicolas Duchon 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/test_custom/test_defaults-location.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy/") 5 | assert r.status_code == 503 6 | assert "X-test" not in r.headers 7 | 8 | def test_custom_default_conf_applies_to_web1(docker_compose, nginxproxy): 9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port") 10 | assert r.status_code == 200 11 | assert r.text == "answer from port 81\n" 12 | assert "X-test" in r.headers 13 | assert "f00" == r.headers["X-test"] 14 | 15 | def test_custom_default_conf_applies_to_web2(docker_compose, nginxproxy): 16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port") 17 | assert r.status_code == 200 18 | assert r.text == "answer from port 82\n" 19 | assert "X-test" in r.headers 20 | assert "f00" == r.headers["X-test"] 21 | 22 | 23 | def test_custom_default_conf_is_overriden_for_web3(docker_compose, nginxproxy): 24 | r = nginxproxy.get("http://web3.nginx-proxy.local/port") 25 | assert r.status_code == 200 26 | assert r.text == "answer from port 83\n" 27 | assert "X-test" in r.headers 28 | assert "bar" == r.headers["X-test"] 29 | -------------------------------------------------------------------------------- /test/test_virtual-path/test_custom_conf.yml: -------------------------------------------------------------------------------- 1 | 2 | foo: 3 | image: web 4 | expose: 5 | - "42" 6 | environment: 7 | WEB_PORTS: "42" 8 | VIRTUAL_HOST: "foo.nginx-proxy.test" 9 | 10 | web1: 11 | image: web 12 | expose: 13 | - "81" 14 | environment: 15 | WEB_PORTS: "81" 16 | VIRTUAL_HOST: "nginx-proxy.test" 17 | VIRTUAL_PATH: "/web1/" 18 | VIRTUAL_DEST: "/" 19 | 20 | web2: 21 | image: web 22 | expose: 23 | - "82" 24 | environment: 25 | WEB_PORTS: "82" 26 | VIRTUAL_HOST: "nginx-proxy.test" 27 | VIRTUAL_PATH: "/web2/" 28 | VIRTUAL_DEST: "/" 29 | 30 | web3: 31 | image: web 32 | expose: 33 | - "83" 34 | environment: 35 | WEB_PORTS: "83" 36 | VIRTUAL_HOST: "nginx-proxy.test" 37 | VIRTUAL_PATH: "~ ^/(web3|alt)/" 38 | 39 | sut: 40 | image: nginxproxy/nginx-proxy:test 41 | environment: 42 | DEFAULT_ROOT: 418 43 | volumes: 44 | - /var/run/docker.sock:/tmp/docker.sock:ro 45 | - ./foo.conf:/etc/nginx/vhost.d/foo.nginx-proxy.test:ro 46 | - ./bar.conf:/etc/nginx/vhost.d/nginx-proxy.test_918d687a929083edd0c7224ee2293e0e7c062ab4_location:ro 47 | - ./alternate.conf:/etc/nginx/vhost.d/nginx-proxy.test_7fb22b74bbdf907425dbbad18e4462565cada230_location:ro 48 | 49 | -------------------------------------------------------------------------------- /test/test_ipv6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_unknown_virtual_host_ipv4(docker_compose, nginxproxy): 5 | r = nginxproxy.get("http://nginx-proxy/port") 6 | assert r.status_code == 503 7 | 8 | 9 | def test_forwards_to_web1_ipv4(docker_compose, nginxproxy): 10 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port") 11 | assert r.status_code == 200 12 | assert r.text == "answer from port 81\n" 13 | 14 | 15 | def test_forwards_to_web2_ipv4(docker_compose, nginxproxy): 16 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port") 17 | assert r.status_code == 200 18 | assert r.text == "answer from port 82\n" 19 | 20 | 21 | def test_unknown_virtual_host_ipv6(docker_compose, nginxproxy): 22 | r = nginxproxy.get("http://nginx-proxy/port", ipv6=True) 23 | assert r.status_code == 503 24 | 25 | 26 | def test_forwards_to_web1_ipv6(docker_compose, nginxproxy): 27 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True) 28 | assert r.status_code == 200 29 | assert r.text == "answer from port 81\n" 30 | 31 | 32 | def test_forwards_to_web2_ipv6(docker_compose, nginxproxy): 33 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port", ipv6=True) 34 | assert r.status_code == 200 35 | assert r.text == "answer from port 82\n" 36 | -------------------------------------------------------------------------------- /test/test_wildcard_host.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize("host,expected_port", [ 5 | ("f00.nginx-proxy.test", 81), 6 | ("bar.nginx-proxy.test", 81), 7 | ("test.nginx-proxy.f00", 82), 8 | ("test.nginx-proxy.bar", 82), 9 | ("web3.123.nginx-proxy.regexp", 83), 10 | ("web3.ABC.nginx-proxy.regexp", 83), 11 | ("web3.123.ABC.nginx-proxy.regexp", 83), 12 | ("web3.123-ABC.nginx-proxy.regexp", 83), 13 | ("web3.whatever.nginx-proxy.regexp-to-infinity-and-beyond", 83), 14 | ("web4.123.nginx-proxy.regexp", 84), 15 | ("web4.ABC.nginx-proxy.regexp", 84), 16 | ("web4.123.ABC.nginx-proxy.regexp", 84), 17 | ("web4.123-ABC.nginx-proxy.regexp", 84), 18 | ("web4.whatever.nginx-proxy.regexp", 84), 19 | ]) 20 | def test_wildcard_prefix(docker_compose, nginxproxy, host, expected_port): 21 | r = nginxproxy.get(f"http://{host}/port") 22 | assert r.status_code == 200 23 | assert r.text == f"answer from port {expected_port}\n" 24 | 25 | 26 | @pytest.mark.parametrize("host", [ 27 | "unexpected.nginx-proxy.tld", 28 | "web4.whatever.nginx-proxy.regexp-to-infinity-and-beyond" 29 | ]) 30 | def test_non_matching_host_is_503(docker_compose, nginxproxy, host): 31 | r = nginxproxy.get(f"http://{host}/port") 32 | assert r.status_code == 503, r.text 33 | -------------------------------------------------------------------------------- /test/certs/ca-root.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDZDCCAkygAwIBAgIJAJmW6Ju6iJNNMA0GCSqGSIb3DQEBCwUAMD8xHzAdBgNV 3 | BAoMFm5naW54LXByb3h5IHRlc3Qgc3VpdGUxHDAaBgNVBAMME3d3dy5uZ2lueC1w 4 | cm94eS50bGQwHhcNMTcwMTEwMDAwODUxWhcNMjcwMTA4MDAwODUxWjA/MR8wHQYD 5 | VQQKDBZuZ2lueC1wcm94eSB0ZXN0IHN1aXRlMRwwGgYDVQQDDBN3d3cubmdpbngt 6 | cHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndjE3OPr 7 | 48hIOQigk/HejrowsQDLNfkkc6vej0J983rJitGTgBfxqq27fOPfqhE5bi1M5JDk 8 | KkrOrSitxCJLgpq+4Ls9/RXg8skZFHRAQbNwuKBehaDkPdamJ0i3dv6e4kZy41oI 9 | RqxQ/MKdminC4LShFZvPoKeh9ae7w1MgB2/4E68LO66bYiHlLNL7ENViSHhLyCmt 10 | qIE7kdV9jgn2NuVJ37m6/6SNQ3GBiIjEW+ooRQ3HEVKBCismcwq80+BD5VS/yW18 11 | KqX8m4sBM+IgZbcOqrV+APMbGvd8iNJgQSSQC/r0Wscgt7UeggVYKDazjDSPvLUE 12 | FUN5wEmydkP2AQIDAQABo2MwYTAdBgNVHQ4EFgQUJL59pHomt+8dUNxv8HgrYjKf 13 | OA8wHwYDVR0jBBgwFoAUJL59pHomt+8dUNxv8HgrYjKfOA8wDwYDVR0TAQH/BAUw 14 | AwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBABALxY96YqsZ 15 | CL2crzY0FIGhfjLE7P3mtUGklUpFu7xyI6mGUyL1nJYSnHB5IEV6QLhVVUE/CojI 16 | crXorQWBDkx26AgCt/eIOdvPYC0JDeXiIhH6sld3yH7JGwGqJkfXaUUfUkuwMae7 17 | mMIEG9e6vfSh/YNTRxs0KBjBcXHHl5K+Dz4h9r14OqnQFqVFZaR6T6td44tDDNhn 18 | beW8iIfCWRqDsnvIcJzLa2QR4onmJSw5DaSeFFaKefhdHEzEBZntLfyFbjRYHT/O 19 | +BRdewhg6rSDkGLcL8n/ZnRLOa+xmegjQ/Op94OmWO3TfXOITJAtkaO2YVZoyek8 20 | T6ckVovq4zU= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /test/stress_tests/test_unreachable_network/test_unreachable_net.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | import pytest 4 | import requests 5 | 6 | 7 | def test_default_nginx_welcome_page_should_not_be_served(docker_compose, nginxproxy): 8 | r = nginxproxy.get("http://whatever.nginx-proxy/", allow_redirects=False) 9 | assert "Welcome to nginx!" not in r.text 10 | 11 | 12 | def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): 13 | r = nginxproxy.get("http://unknown.nginx-proxy/", allow_redirects=False) 14 | assert r.status_code == 503 15 | 16 | 17 | def test_http_web_a_is_forwarded(docker_compose, nginxproxy): 18 | r = nginxproxy.get("http://webA.nginx-proxy/port", allow_redirects=False) 19 | assert r.status_code == 200 20 | assert "answer from port 81\n" == r.text 21 | 22 | 23 | def test_http_web_b_gets_an_error(docker_compose, nginxproxy): 24 | r = nginxproxy.get("http://webB.nginx-proxy/", allow_redirects=False) 25 | assert "Welcome to nginx!" not in r.text 26 | with pytest.raises(requests.exceptions.HTTPError): 27 | r.raise_for_status() 28 | 29 | 30 | def test_reverseproxy_survive_restart(docker_compose): 31 | docker_compose.containers.get("reverseproxy").restart() 32 | sleep(2) # give time for the container to initialize 33 | assert docker_compose.containers.get("reverseproxy").status == "running" 34 | -------------------------------------------------------------------------------- /test/pytest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ############################################################################### 3 | # # 4 | # This script is meant to run the test suite from a Docker container. # 5 | # # 6 | # This is usefull when you want to run the test suite from Mac or # 7 | # Docker Toolbox. # 8 | # # 9 | ############################################################################### 10 | 11 | # Returns the absolute directory path to this script 12 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 13 | ARGS=("$@") 14 | 15 | # check requirements 16 | echo "> Building nginx-proxy-tester image..." 17 | docker build -t nginx-proxy-tester -f "${DIR}/requirements/Dockerfile-nginx-proxy-tester" "${DIR}/requirements" 18 | 19 | # run the nginx-proxy-tester container setting the correct value for the working dir in order for 20 | # docker-compose to work properly when run from within that container. 21 | exec docker run --rm -it --name "nginx-proxy-pytest" \ 22 | --volume "/var/run/docker.sock:/var/run/docker.sock" \ 23 | --volume "${DIR}:${DIR}" \ 24 | --workdir "${DIR}" \ 25 | nginx-proxy-tester "${ARGS[@]}" -------------------------------------------------------------------------------- /test/requirements/web/webserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, sys, re 4 | import http.server 5 | import socketserver 6 | 7 | class Handler(http.server.SimpleHTTPRequestHandler): 8 | def do_GET(self): 9 | 10 | response_body = "" 11 | response_code = 200 12 | 13 | if self.path == "/headers": 14 | response_body += self.headers.as_string() 15 | elif self.path == "/port": 16 | response_body += f"answer from port {PORT}\n" 17 | elif re.match("/status/(\d+)", self.path): 18 | result = re.match("/status/(\d+)", self.path) 19 | response_code = int(result.group(1)) 20 | response_body += f"answer with response code {response_code}\n" 21 | elif self.path == "/": 22 | response_body += f"I'm {os.environ['HOSTNAME']}\n" 23 | else: 24 | response_body += "No route for this path!\n" 25 | response_code = 404 26 | 27 | self.send_response(response_code) 28 | self.send_header("Content-Type", "text/plain") 29 | self.end_headers() 30 | 31 | if (len(response_body)): 32 | self.wfile.write(response_body.encode()) 33 | 34 | if __name__ == '__main__': 35 | PORT = int(sys.argv[1]) 36 | socketserver.TCPServer.allow_reuse_address = True 37 | httpd = socketserver.TCPServer(('0.0.0.0', PORT), Handler) 38 | httpd.serve_forever() 39 | -------------------------------------------------------------------------------- /test/test_virtual-path/test_custom_conf.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def test_default_root_response(docker_compose, nginxproxy): 4 | r = nginxproxy.get("http://nginx-proxy.test/") 5 | assert r.status_code == 418 6 | 7 | @pytest.mark.parametrize("stub,header", [ 8 | ("nginx-proxy.test/web1", "bar"), 9 | ("foo.nginx-proxy.test", "f00"), 10 | ]) 11 | def test_custom_applies(docker_compose, nginxproxy, stub, header): 12 | r = nginxproxy.get(f"http://{stub}/port") 13 | assert r.status_code == 200 14 | assert "X-test" in r.headers 15 | assert header == r.headers["X-test"] 16 | 17 | @pytest.mark.parametrize("stub,code", [ 18 | ("nginx-proxy.test/foo", 418), 19 | ("nginx-proxy.test/web2", 200), 20 | ("nginx-proxy.test/web3", 200), 21 | ("bar.nginx-proxy.test", 503), 22 | ]) 23 | def test_custom_does_not_apply(docker_compose, nginxproxy, stub, code): 24 | r = nginxproxy.get(f"http://{stub}/port") 25 | assert r.status_code == code 26 | assert "X-test" not in r.headers 27 | 28 | @pytest.mark.parametrize("stub,port", [ 29 | ("nginx-proxy.test/web1", 81), 30 | ("nginx-proxy.test/web2", 82), 31 | ("nginx-proxy.test/web3", 83), 32 | ("nginx-proxy.test/alt", 83), 33 | ]) 34 | def test_alternate(docker_compose, nginxproxy, stub, port): 35 | r = nginxproxy.get(f"http://{stub}/port") 36 | assert r.status_code == 200 37 | assert r.text == f"answer from port {port}\n" 38 | 39 | -------------------------------------------------------------------------------- /test/test_dockergen/test_dockergen_v2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import docker 3 | import logging 4 | import pytest 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def nginx_tmpl(): 9 | """ 10 | pytest fixture which extracts the the nginx config template from 11 | the nginxproxy/nginx-proxy:test image 12 | """ 13 | script_dir = os.path.dirname(__file__) 14 | logging.info("extracting nginx.tmpl from nginxproxy/nginx-proxy:test") 15 | docker_client = docker.from_env() 16 | print( 17 | docker_client.containers.run( 18 | image="nginxproxy/nginx-proxy:test", 19 | remove=True, 20 | volumes=["{current_dir}:{current_dir}".format(current_dir=script_dir)], 21 | entrypoint="sh", 22 | command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format( 23 | current_dir=script_dir 24 | ), 25 | stderr=True, 26 | ) 27 | ) 28 | yield 29 | logging.info("removing nginx.tmpl") 30 | os.remove(os.path.join(script_dir, "nginx.tmpl")) 31 | 32 | 33 | def test_unknown_virtual_host_is_503(nginx_tmpl, docker_compose, nginxproxy): 34 | r = nginxproxy.get("http://unknown.nginx.container.docker/") 35 | assert r.status_code == 503 36 | 37 | 38 | def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy): 39 | r = nginxproxy.get("http://whoami.nginx.container.docker/") 40 | assert r.status_code == 200 41 | whoami_container = docker_compose.containers.get("whoami") 42 | assert r.text == f"I'm {whoami_container.id[:12]}\n" 43 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/test_wildcard_cert_nohttps.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ssl import CertificateError 3 | from requests.exceptions import SSLError 4 | 5 | 6 | @pytest.mark.parametrize("subdomain,should_redirect_to_https", [ 7 | (1, True), 8 | (2, True), 9 | (3, False), 10 | ]) 11 | def test_http_redirects_to_https(docker_compose, nginxproxy, subdomain, should_redirect_to_https): 12 | r = nginxproxy.get(f"http://{subdomain}.web.nginx-proxy.tld/port") 13 | if should_redirect_to_https: 14 | assert len(r.history) > 0 15 | assert r.history[0].is_redirect 16 | assert r.history[0].headers.get("Location") == f"https://{subdomain}.web.nginx-proxy.tld/port" 17 | assert f"answer from port 8{subdomain}\n" == r.text 18 | 19 | 20 | @pytest.mark.parametrize("subdomain", [1, 2]) 21 | def test_https_get_served(docker_compose, nginxproxy, subdomain): 22 | r = nginxproxy.get(f"https://{subdomain}.web.nginx-proxy.tld/port", allow_redirects=False) 23 | assert r.status_code == 200 24 | assert f"answer from port 8{subdomain}\n" == r.text 25 | 26 | @pytest.mark.filterwarnings('ignore::urllib3.exceptions.InsecureRequestWarning') 27 | def test_web3_https_is_500_and_SSL_validation_fails(docker_compose, nginxproxy): 28 | with pytest.raises( (CertificateError, SSLError) ) as excinfo: 29 | nginxproxy.get("https://3.web.nginx-proxy.tld/port") 30 | assert """hostname '3.web.nginx-proxy.tld' doesn't match 'nginx-proxy.tld'""" in str(excinfo.value) 31 | 32 | r = nginxproxy.get("https://3.web.nginx-proxy.tld/port", verify=False) 33 | assert r.status_code == 500 34 | -------------------------------------------------------------------------------- /test/certs/ca-root.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAndjE3OPr48hIOQigk/HejrowsQDLNfkkc6vej0J983rJitGT 3 | gBfxqq27fOPfqhE5bi1M5JDkKkrOrSitxCJLgpq+4Ls9/RXg8skZFHRAQbNwuKBe 4 | haDkPdamJ0i3dv6e4kZy41oIRqxQ/MKdminC4LShFZvPoKeh9ae7w1MgB2/4E68L 5 | O66bYiHlLNL7ENViSHhLyCmtqIE7kdV9jgn2NuVJ37m6/6SNQ3GBiIjEW+ooRQ3H 6 | EVKBCismcwq80+BD5VS/yW18KqX8m4sBM+IgZbcOqrV+APMbGvd8iNJgQSSQC/r0 7 | Wscgt7UeggVYKDazjDSPvLUEFUN5wEmydkP2AQIDAQABAoIBAQCDM4zetix6lx1B 8 | GuSuVFrTc/vJBInkgQRFiVRi67fZS/R+CJl73WsonWO7+YUNzWdZJxpE2hJs/OUx 9 | lSBqaL8u/gUuszRhS3BBHdpU4BQRCF/ndpVaqVNN+z78ZDrrE9Vo63nPdCRw6gYf 10 | MnzhiVjMghdq6Kn6NZwvno45WrzCsIbrrQ4zU+S2PhG8MTA53jzqqQ8mUSJX0lAl 11 | 6b9+1aWA0d0Jnk3M3doaFU/Dlnz3n6kkx0AdqNe8bdsFrPfwsrF+dwGx04SGgLmK 12 | V2OjIDFYYGtiHp3PJ9IYIA32ij+UloSDDZ2BxXkma8Zilw04ytY5l8tlk2ZDWTD9 13 | U2MXxjmBAoGBAMmmI19I/asTPjljlqzrOsrdRkklJvnCHgy/yw9u3nMfkJ0lLGAp 14 | mZoCqJIEsAqlLGM5bOjKy3KQ3n2SBX3mz7/RajnpJRTnNLeJIPAAXHN9TDyKcWRo 15 | Los6xHN7YMSLYKs4HMihXp9Yu4Ms88/8nO/01nufjN0rTgFnWdL0WfxJAoGBAMhk 16 | Qm92ukMmbrXSrV0WF+eFooHwgPmUWZ1oZY5ZHmO3FCuSBHiICGrWKmdbcG6H5zmZ 17 | oFZ0unsvk2Yjl+/+tntxr/dwp6Q+chsqkLms8GE76NWEO8qn4hQNywkFgpKlPci3 18 | n5IqpuQ2DpJ1PAQkwgZD/5rSscNidNMezXO5Uvv5AoGBALR291kjXcJpKlr6AbMn 19 | oipD9c8obMVBMNuAGh7pvjORoD7DMf+tu0XV8z8a6uHcCOmUTx/XvlP9yuDeegO/ 20 | OVYV+NdzDDi04r0PAGdKK3NAQ6Y60Fhn1J/OLFqdpHDBu/X/9eKoaKJ7KvWumVUe 21 | YuVtXTauB8c4JkujTwQ4ov/hAoGAHxvhbGhkFhSbT0K7gx3w7BJE3iM2AojTOKqC 22 | SYzwOM6tJO5wHz4PAHbq8kyxsZcLgFenGoTYhlMmcM7JwYorThKiHKmyfL7s++ap 23 | vQlp785bIPp8RcO2RyK1CFuAn79jTgujjA9vBTKXJIlqncIPFOXtgl1/FzPrqvK3 24 | NmXoyhECgYEAje9hM9RYO0jbfmTZoQh+onMRz34SM9XWLH+NQGgfvsGtjeRnrUKK 25 | GuWQz/GQGJLy/Uc1KHIdrfPDjvQhZXmPL1v7pNfCrqyj+EnKCNDPPnYq5Zq4WLsB 26 | x1hKPH0LmfEBkXOiFGrD3h3KAuBK5nb0/EFBDR4JuMaySC5CpbOds9o= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_ssl/certs/nginx-proxy.tld.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a 3 | wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa 4 | rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm 5 | snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn 6 | FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI 7 | E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO 8 | fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a 9 | dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x 10 | fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p 11 | e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn 12 | QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB 13 | uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf 14 | oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k 15 | VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf 16 | MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2 17 | pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M 18 | RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI 19 | ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og 20 | 4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD 21 | Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4 22 | pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9 23 | A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH 24 | iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr 25 | iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV 26 | THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_ssl/test_hsts.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_web1_HSTS_default(docker_compose, nginxproxy): 5 | r = nginxproxy.get("https://web1.nginx-proxy.tld/port", allow_redirects=False) 6 | assert "answer from port 81\n" in r.text 7 | assert "Strict-Transport-Security" in r.headers 8 | assert "max-age=31536000" == r.headers["Strict-Transport-Security"] 9 | 10 | # Regression test to ensure HSTS is enabled even when the upstream sends an error in response 11 | # Issue #1073 https://github.com/nginx-proxy/nginx-proxy/pull/1073 12 | def test_web1_HSTS_error(docker_compose, nginxproxy): 13 | r = nginxproxy.get("https://web1.nginx-proxy.tld/status/500", allow_redirects=False) 14 | assert "Strict-Transport-Security" in r.headers 15 | assert "max-age=31536000" == r.headers["Strict-Transport-Security"] 16 | 17 | def test_web2_HSTS_off(docker_compose, nginxproxy): 18 | r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False) 19 | assert "answer from port 81\n" in r.text 20 | assert "Strict-Transport-Security" not in r.headers 21 | 22 | def test_web3_HSTS_custom(docker_compose, nginxproxy): 23 | r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False) 24 | assert "answer from port 81\n" in r.text 25 | assert "Strict-Transport-Security" in r.headers 26 | assert "max-age=86400; includeSubDomains; preload" == r.headers["Strict-Transport-Security"] 27 | 28 | # Regression test for issue 1080 29 | # https://github.com/nginx-proxy/nginx-proxy/issues/1080 30 | def test_web4_HSTS_off_noredirect(docker_compose, nginxproxy): 31 | r = nginxproxy.get("https://web4.nginx-proxy.tld/port", allow_redirects=False) 32 | assert "answer from port 81\n" in r.text 33 | assert "Strict-Transport-Security" not in r.headers 34 | -------------------------------------------------------------------------------- /test/test_headers/certs/web.nginx-proxy.tld.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAlVbHDUilKzxlST8m4TgrYTBW5JLXY+Drraz5M5uyMfE5Ewvl 3 | Q3vFvYqFyNk92KxxuhbngZayq67GwL2+p9GWj7Kb37r5TaE7fiFKzbZF+W15UL8k 4 | j8FrwQkZW2LLlugEFCDo1BZiavI3wZbinVMFC1Id52iS24s2aM2NWwL/EvCsXQzE 5 | 4HpVoklgn/9HH1JzVU3U8tFiovRQncn28UOz3FfhMXa04KRpfvJtNK65jXQme9n2 6 | BwDvSzZhs+96oTY6ttCe+LipDUwwou3rq2vrLuILKL73BLHp4ITWXTF3fNzSH9Qd 7 | cW9vbG0bvzHiW8NS0BT8i/tF6kHsysc7ZxLE3wIDAQABAoIBAEmK7IecKMq7+V0y 8 | 3mC3GpXICmKR9cRX9XgX4LkLiZuSoXrBtuuevmhzGSMp6I0VjwQHV4a3wdFORs6Q 9 | Ip3eVvj5Ck4Jc9BJAFVC6+WWR6tnwACFwOmSZRAw/O3GH2B3bdrDwiT/yQPFuLN7 10 | LKoxQiCrFdLp6rh3PBosb9pMBXU7k/HUazIdgmSKg6/JIoo/4Gwyid04TF/4MI2l 11 | RscxtP5/ANtS8VgwBEqhgdafRJ4KnLEpgvswgIQvUKmduVhZQlzd0LMY8FbhKVqz 12 | Utg8gsXaTyH6df/nmgUIInxLMz/MKPnMkv99fS6Sp/hvYlGpLZFWBJ6unMq3lKEr 13 | LMbHfIECgYEAxB+5QWdVqG2r9loJlf8eeuNeMPml4P8Jmi5RKyJC7Cww6DMlMxOS 14 | 78ZJfl4b3ZrWuyvhjOfX/aTq7kQaF1BI9o3KJBH8k6EtO4gI8KeNmDONyQk9zsrn 15 | ru8Zwr7hVbAo8fCXxCnmPzhDLsYg6f3BVOsQWoX2SFYKZ1GvkPfIReECgYEAwu6G 16 | qtgFb57Vim10ecfWGM6vrPxvyfqP+zlH/p4nR+aQ+2sFbt27D0B1byWBRZe4KQyw 17 | Vq6XiQ09Fk6MJr8E8iAr9GXPPHcqlYI6bbNc6YOP3jVSKut0tQdTUOHll4kYIY+h 18 | RS3VA3+BA//ADpWpywu+7RZRbaIECA+U2a224r8CgYB5PCMIixgoRaNHZeEHF+1/ 19 | iY1wOOKRcxY8eOU0BLnZxHd3EiasrCzoi2pi80nGczDKAxYqRCcAZDHVl8OJJdf0 20 | kTGjmnrHx5pucmkUWn7s1vGOlGfgrQ0K1kLWX6hrj7m/1Tn7yOrLqbvd7hvqiTI5 21 | jBVP3/+eN5G2zIf61TC4AQKBgCX2Q92jojNhsF58AHHy+/vqzIWYx8CC/mVDe4TX 22 | kfjLqzJ7XhyAK/zFZdlWaX1/FYtRAEpxR+uV226rr1mgW7s3jrfS1/ADmRRyvyQ8 23 | CP0k9PCmW7EmF51lptEanRbMyRlIGnUZfuFmhF6eAO4WMXHsgKs1bHg4VCapuihG 24 | T1aLAoGACRGn1UxFuBGqtsh2zhhsBZE7GvXKJSk/eP7QJeEXUNpNjCpgm8kIZM5K 25 | GorpL7PSB8mwVlDl18TpMm3P7nz6YkJYte+HdjO7pg59H39Uvtg3tZnIrFxNxVNb 26 | YF62/yHfk2AyTgjQZQUSmDS84jq1zUK4oS90lxr+u8qwELTniMs= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_ssl/certs/web2.nginx-proxy.tld.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA2u5GLUR88eaREb801gJO/kMj+20g940bxpzNHBoHlcLtuSNz 3 | wQIrUFE/M8+OqvETWEz/f31Kh/zwD1SvjOu6tA9xXhIfZLE9g4jdnAlQLTcdAzsY 4 | MDb0gpSHfzEnKIQMmW13tbhPyoNY1dhONnMcGlztBe+VYAMoDJ/Yb5iozQi+r7GV 5 | WmCW/irQmHSbSsBIZnNnVDMRIiDqEQW6pu10EgXY3k8BRjl02DQa8izC322UV1LB 6 | 5C4bjhIOQ+dvH9pRgDXCipu2KjA5a6B3+jcRt+zeboxvk4FeLZBpG0ukgMr05VvA 7 | E0W5dnDt007dZpiZn53wHv3dBE+aVbw4rUK53QIDAQABAoIBABeTCsl7E30017Ay 8 | h6z3yKvGbQx43tDpR/FmFwwMnX555AFImQFSi3l1ljmtAu7TUML0X5rJ0gm8qdjs 9 | xI6HH66d7xYzG2BLWZVdWoef1RtZUO11IpCmikO5XLHMiCvrtDOdPwO5WhYzeJBm 10 | X12rnX4VPYyjFNGm5Vwepj62EI8rS9G3NG00pDYPmN/vUQJiV/iTRIlvXgFm4Hl2 11 | zJhVi+NhyiptFEycdg65JwVfLKtmUXRmwGFiSxQi1FX8YS5EdIV2S8yDwXlWSxmq 12 | 4R1eSID7pKxtzyRtBqZJggzfqtY8cMpoOC12FbLAvzagOavs4ntMgAVy5k2T15G2 13 | syQyLSECgYEA+1NIRF3CxNUaPvpcR8Y4PWhwDEzqn5ZscnXaFrUp1W72f3bpwSCa 14 | /t9lXe9O53R5/yt4nCbwvVM0UWYPHGZGQr+5AS7WWDVWVcwkXX1NIjALi0TXQ0Ty 15 | zeuViXDofUS31yhwFFmgGa52pd+edXaGRvxzGyPVdtwpSLZP/gBLQykCgYEA3wC9 16 | sHNPKMxi6vI2VBvmBXHoCSDAkuRLmQEGDmjjt0Ve4fmwGRbBJxniOlNtufNQRfRg 17 | 67qaeM4BTrP03cilZ71yXvLN5G2opY5c/I0dYTXRhV3IV6XwlCC+0xmn+ro7kB8x 18 | J4wAj/h/tJ8T0+0TpnbyjmPH4KTJ9GjRTKwe65UCgYBszIHlbr2JXkONbe6S98GS 19 | ++o9uPJ9Aa6S4mf2Gpkwl2fIiF7rR0Ux/t2wC5AZ7Ld/en8tAkKHg0SL1GXIQpI6 20 | BSt+0prh9r0YSVaYzkyc9zWYJcYWjfuan1jN9f3/dMctMolKlf4UAA3HAwZjDVtV 21 | 0aW24w1e9jI9EweQCuqJ+QKBgBwZec18GiNn7abxMktS4J8bBUPxLpLT1XrIGD1E 22 | lj0HrrcGwVvH9Dq7FjiHPrJJqHnIG1ZYwxIp0xxZrKctmzoBMyInsi3wa2nBEJJ6 23 | LZOMNoR5lr8El9XyclkjSHldchfs9kKnb4K0q1LVIKh5nRpCrrmmdQ8ndJMpigYB 24 | QjwpAoGAWIhFrN0Acdq7Xc9pScqnAohPMWCTBUbeKrOm0ZwN4Q9D+lLeeggbWlWy 25 | AqR4WHQMHc6B+p4Ncg3XBCFw0V62PkYhSCdaLNH3CPyFT0qoeY8VuMjmqS7yoKvp 26 | uMMHfzMmyGg0dyplVGgANafb/it6Dp8T0pnCmzxhe57jf9xsVBo= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_ssl/certs/web3.nginx-proxy.tld.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEArJuQ3CwNw670oMxAFdLEwiyMFbZwKM8yxAPOs4cwXaYSlml6 3 | /mcpHIwku2rIhhMZkQA+7wBnULDqyZPGinOC2Defjm4SE+z6CA+sc25ClmefIMUc 4 | o7FKgzYOCjGTdrG2N0/giDxG3MFTYN8orj4g2NlTor4JOPD/SpFFy8u1sz19CZhH 5 | 3A1eg3O2yfP7mvK7sGLKqq9rQuUIshSH9PzxFPjMdrPASd9mxiGgvF4Mu97pnOf7 6 | MaHPxOm7vcNaDSNSxrOEd/EMPcrDYEj5fqbcT/fSW3wCTTgJZDN+u7Flu+IrHZpJ 7 | 1DQhQnpJPhFsEGa0kdu9OsKN9OQDsbRuXJi/fQIDAQABAoIBAA14DjPAFEriyiAK 8 | EC4jxkrIox3GoLXuhS2ahnSn5fRI00Z9cKWNcz3RCcS+LmuX7fTMqhyIUYeQZqHY 9 | MDP5k4o/vOmmWS7I3THn1zMitXt7FoW+G+ACI6hdfXb6K2GluGxUhVbcLUNoqpLy 10 | lwARxQpm2wnl/l49IA63i1S9zq3vy5HSvxBv3jq8xp2PX3Sho33LhsvW+miCJe+R 11 | etKSV4mAjvR62XVgUGJ40FciVMK3pzwwIKPLI7k9sa7WHZr6fNHeDxIWA6AsVBTH 12 | O+2l8Ufd79KesOD6VqdHlxg2a79s3NoCOflQQAbOSSR9ioCE7Ykgvw0wVl57xNoB 13 | WZVAY0ECgYEA2I6+a56NMzkEMxr0w9ZRqgocXCgbqxZx1v7newDyO5J3X4jYhmMJ 14 | abNZtnVs1Pz0IAgCMCf+N4D+RAi9/ahYxq7jmhIkT/IUHseh93XLgd/x9Q9ZJOGm 15 | 9+NI3aIBgWOsy4orKxfwzRAVEakOCChYUCy/GUDDD1MPcjxC8ma5abUCgYEAzAua 16 | 15Nayr9Sg0QsHqNvgTVkVlA6u+TwN+vfI8cH5nmXIMm5ShwCc9+Pm8mpcFwUo4uE 17 | vOzQ+NwG9CBbVu6/i1/aR+ZlbctdhpsW51v18B9eFVXSZUvEv1ONGdoJZhq0tW7W 18 | V4Zjf+UwdTcrSZKVpd5woUbRkByROPdir/3Ie6kCgYBhf5LX3SBxSWBMqfw9F4bY 19 | 6YhvLVeXpZlHVKhfRsPIcl7wUio6Bui8ABWKAkAnfGNk8HYbvEXGM3tGojD3vQ2L 20 | Fj4+paBXpgPM/9A6G3yuUmcbD/fwlO+Zd2jc8A2BdaDcWq6ozjSJ/o2dz+ETZyar 21 | ohm/gtrPUXQI2HzDqeAcaQKBgEBMd+LvAHFbkPjkhrKw9fZViOTaK2gCYOB+Z7ay 22 | hX7PWhxu9QCxiuRQ0sRY7BgILEjNMmsGhWOmklpjx+TBH4MgFX0K0XOj3jkIrlMB 23 | 26JrgA5hGQfqtHlGLvSyjLusNr3ly42RP9GRu490byOkGZxHWF66Hle3aNv2uRaU 24 | dpThAoGBAMIPpf4E6xGzurhgYdXMit3jGAYD85BbNUIm9jOym2lxS63ipoF08QhH 25 | NoMVRf/AUoS4VDGXsuABjMffTZbE8L+DaL1cWSuPJAoF9AXUXtz6A8LRc9Mn2WjS 26 | L8BIs9xZCzrJ5XzY4PSnjjyAU81z6E80azWglkmh8rRDzi/o9O79 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAtidjpcbo9HqUDsyiYnZtXTNvzxn85+W7Dg7QfE9zTEgrF9FN 3 | 1Z9CCHOEVIyG0sXaWQE/QiLgNvDcq94KvSYrIhOHph8j7w6ZJ4sVShvvk8lrkd6g 4 | Agxiu8xWN+glksMf8WnYfKgz4InOFGegOXeIkeajB5eQIojQeRhj+29+7itCfiP1 5 | 59rp7mr6lmWf4SsVScjNLc6GTywqZ3m/QTAUzPYPFHSettPQO/AbuOgZKv3W/dxL 6 | TmV9m783fi01Ii50kM5BNT1BoJnblx+/Phg8SPva38ZOTrlnuBDVpRMDxLdl56rw 7 | FEvTTer+j2nPUCFjJ8+eTGcVez872ssXgGEeJQIDAQABAoIBAGQCMFW+ZfyEqHGP 8 | rMA+oUEAkqy0agSwPwky3QjDXlxNa0uCYSeebtTRB6CcHxHuCzm+04puN4gyqhW6 9 | rU64fAoTivCMPGBuNWxekmvD9r+/YM4P2u4E+th9EgFT9f0kII+dO30FpKXtQzY0 10 | xuWGWXcxl+T9M+eiEkPKPmq4BoqgTDo5ty7qDv0ZqksGotKFmdYbtSvgBAueJdwu 11 | VWJvenI9F42ExBRKOW1aldiRiaYBCLiCVPKJtOg9iuOP9RHUL1SE8xy5I5mm78g3 12 | a13ji3BNq3yS+VhGjQ7zDy1V1jGupLoJw4I7OThu8hy+B8Vt8EN/iqakufOkjlTN 13 | xTJ33CkCgYEA5Iymg0NTjWk6aEkFa9pERjfUWqdVp9sWSpFFZZgi55n7LOx6ohi3 14 | vuLim3is/gYfK2kU/kHGZZLPnT0Rdx0MbOB4XK0CAUlqtUd0IyO4jMZ06g4/kn3N 15 | e2jLdCCIBoEQuLk4ELxj2mHsLQhEvDrg7nzU2WpTHHhvJbIbDWOAxhsCgYEAzAgv 16 | rKpanF+QDf4yeKHxAj2rrwRksTw4Pe7ZK/bog/i+HIVDA70vMapqftHbual/IRrB 17 | JL7hxskoJ/h9c1w4xkWDjqkSKz8/Ihr4dyPfWyGINWbx/rarT/m5MU5SarScoK7o 18 | Xgb25x+W+61rtI+2JhVRGO86+JiAeT4LkAX88L8CgYAwHHug/jdEeXZWJakCfzwI 19 | HBCT1M3vO+uBXvtg25ndb0i0uENIhDOJ93EEkW65Osis9r34mBgPocwaqZRXosHO 20 | 2aH8wF6/rpjL+HK2QvrCh7Rs4Pr494qeA/1wQLjhxaGjgToQK9hJTHvPLwJpLWvU 21 | SGr2Ka+9Oo0LPmb7dorRKQKBgQCLsNcjOodLJMp2KiHYIdfmlt6itzlRd09yZ8Nc 22 | rHHJWVagJEUbnD1hnbHIHlp3pSqbObwfMmlWNoc9xo3tm6hrZ1CJLgx4e5b3/Ms8 23 | ltznge/F0DPDFsH3wZwfu+YFlJ7gDKCfL9l/qEsxCS0CtJobPOEHV1NivNbJK8ey 24 | 1ca19QKBgDTdMOUsobAmDEkPQIpxfK1iqYAB7hpRLi79OOhLp23NKeyRNu8FH9fo 25 | G3DZ4xUi6hP2bwiYugMXDyLKfvxbsXwQC84kGF8j+bGazKNhHqEC1OpYwmaTB3kg 26 | qL9cHbjWySeRdIsRY/eWmiKjUwmiO54eAe1HWUdcsuz8yM3xf636 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/certs/default.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEA8v15cJkM2mNcgShyMQFi6WjXy43GlfnsJjQcCMZt3q3YsMCu 3 | SANzdms/xTWGxkKRUzyqhYmEkmeS76lb8tQEczQCNdRq+sLakUqpcIclOIQdk5k8 4 | 1wNhpm0zb4NFBK9PlmIewXmHydVM6Y+F4sgbW/y4Av97bTRMXUBzRJ7FH1/gD4mI 5 | xDUrBFOMjqB8fJcWIMJPocDdv9UTLWQlA/LY1ScBcMn0NzM2fntIVOw3K4E9UDzU 6 | XwUZ4gu6dvYsOyNLgnhf6eNX/DlKXEKCcsijr7ezkeQBnCxHXv+qrWMc55wuoqxd 7 | UTCDZ274Wu0LcORo1Olep/VehzvoMa0ABPh72QIDAQABAoIBAQDqcaW5/fFoxHV8 8 | KIoEvlGw4ndS7nesPHacZaqmzM01DIcGAuIkmS/OEax1mi9vGsschGwCa6x9lXEv 9 | yzfsEqQ4gvWe+lQ9ncNEa8UPzVUcMlxXDIKm8ZxF9xapgP4Whw9DCWijQ57AHg0X 10 | TGLhbDD5j9v7CIUN2GfVkVml24pVuUoeXqv7ZLzTJKZ+Q/eqxyeIikjFheXzaQxb 11 | bUHbEHIXJtHMYULXmfc5WCxuobHqal3z0ymCijoZVXV8hp8dtDP34tRV9MID9wck 12 | lRUVqboFCIXxmLLRTZgyCbiFLkCIu2nmgNobWCNfkHN7QQhToPEecSFMZzYtmo6/ 13 | T1fHE3ABAoGBAP1J1Izfc4CF9t2iPGzXyn8oNkXHLMPKtFQ2Rb8XwBryUOOrAHqT 14 | FIZ2FsDJr0VvS1ihFs1kbO+WAY5W5GytwiiVXvztHz3/f5JnGgvMCeUcEmaj90vq 15 | sTyfHc2OKFjumIjGe87uav3bgac7nOWLO+RIJ/ua6UO7/8psqwryxY4FAoGBAPWX 16 | a502kT56VwI3Gf8hb37PZ/PD+gOzgzVcMn13yLZ4gC9xoP4TKUBHSz4wO8asjKk5 17 | 1RD/DITXYKelyRXynOtMW+2j2s5bVBpOshN/n9jRC1haoGJZYb2JVP6+8WoZKQOF 18 | NwgNlI4he32kSFw59fjkdG64iw7KY8ZYUatkrgrFAoGBAPozTjUCHfRdYOi6c/oI 19 | h81oCYSQJVYbDFsLaYZEjc2Qg/sBVm2+kE3qpLs3/10VfVZFemLVyw44Hb1fdDEu 20 | y1aPhs9N5Mi3dGtIUWBJ45RgUIT3fzeM1BtQCn6c6JpAxoiFmJNmzGWLyd1Kc8gD 21 | 69uqs2RFOBtiwGBTS/p6qk+JAoGBAM1QkpnzFYf69SSX9jbRuAl20Xv8GdbgS0/f 22 | zSIRcw4BPYDsaOAgGrtvHttVrZORi2KqQ5Ma9ldUS6y8L5kWo9MemjfYZUNhHLWF 23 | luAwMO0tDmQGF9FA0jKHTjROYzsE38Heq7wixk/wc/H81rWrixRRwXkS9MYfszwN 24 | d/FmkQ3VAoGAXHZrDEygUmf4q0LwjLVF0TPzElh530qVmyhPa0OBs/hVh9Mwv/i6 25 | fj3+k7uYWgKDzcaVXSMOFGt515F8qy0AUEY9r+IjAn01KTLKO4ZuPiSpxliqDbCs 26 | gzsX9CWVSVgTN+TY15QCoJNpzLiyrXe3uldAP5JEBQSnjt9OfSJQ5IU= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_headers/certs/web-server-tokens-off.nginx-proxy.tld.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAtPqdinQ/F+qZHEVxGJDrkjU415AhgQqRBUHPtYc0vdh7f30G 3 | M/iUZ47kB1R/t2LFdmx/fBklGSw2miZUji2XAngxxhPTrfMxYubPlq5jN929c8tO 4 | +z+bZWeX2FpdDnKxEasO1yOptyLeI3R+iHwomKluAPS+jGnqPzOLGZfaG6ZltVqS 5 | ATw6E2sAAuGYeNPa6qacM7AdnwLE8dDW3nr3QhJLMfvt6dfYFehOGJF8nb8PsBLW 6 | 4oCLeu8XcFH0PLdDy1Zhr2F6Tp1sXtgnDDvXpB0vDaCZj7VxkyG0h760HHeguc2R 7 | vZzQuYFQEmPSCqlhBZEZJ/fqnY5IZS4a5/3xtwIDAQABAoIBAAaBi/BSRYJimKZ/ 8 | iJVNgGp9J1H4iHvPGW+K8iCgf7Dje20V3Yc4xH0EkgYBb6X0Ew0y0VJwxPimsj/Q 9 | aPHDic446/Em/VEfkQLxMT1Ff6OegRUMlgZKPxfiJX9NoFLIpLzx3VK2oX9H7Zxw 10 | r6vQatUyIhY+tiruE9G51KJS5zBfN388ErfRUI8ByBaDGH0huA6kTBcNffhCfZr5 11 | 9naWSIIcuBe8v7z6nAaeYL00q1q3vuWPmuQduSgsmef7QuN71CIxuOAqXTJl8koS 12 | LYNbj8yvIy3nOF90D+uZD/Pa2Y0kB6aum09hbUP15K0QFKulbKLRQ60IuvRcw3Qv 13 | MM177OECgYEA5Rw3qUcoTDfsx+nu2BxECj62uyNVZfX/QMf7dvzCqjXuOhij+KBB 14 | U9xnNfuLc4HfCXx/rMg5dGExEBbD2iHAo0nvnCSxzLJmF6i66Uves0VWISXcv2Au 15 | L0TWMhhsbDFoqkWuxXr69oNwKyl9yFRFWEY3p3G+aBAEqWZ1lOkU8O0CgYEAyjhC 16 | bN4mJJYhvX+cXhv+89Z+JIDAvtvQ5Vy7kxvhQUTx2By6rWKKrBPdTnzsxBGKqQwv 17 | lXzfgj/MlIr6A6QDReGwU3ZXTJqSGEuT8Ra9SbjczQgaGOrPCrWhnbeZ18iM67pJ 18 | LPfLgdRdkh3XgbOOKcDhpg2KybbbyXx6Q2xb7LMCgYEAzKHKWUh0BreApgIcUSvV 19 | 3ayr+zOQ5/Oy24KC6IDTwcFPmNY/RiakkqluCfo1UKKzuj5XrtRa9MaGUs9yeJbi 20 | /zVfbQAdSi4hH4qV/x/Dtiz8w7iUlN3sAk4iXjYQSQZMbKC2fC3ej2VQP0zcypvy 21 | H+j/dnASV9HOyBr6dFlGWfUCgYB3gfYntsXd+2fnQOJdb7glzM5xrjG62dfDpSEp 22 | mGFwHFm8+YWNcF45weeZOhUG7sL+krgQZWMF68RwyQ1mV2ijxPRa7uY63GKYvxmo 23 | cmLdjcXX2gDqVuKTFrJzrgzaTKiTq10RmUQI70N5Ve+FtGLA5D+2zewGt+1+TvVG 24 | oWRWJwKBgAUpJ/NXOB82ie9RtwfAeuiD0yDPM3gNFVe0udAyG/71nXyHiW5aHn/w 25 | H+QSliw7gqir4u6bcrprFQMcwiowtCfeDkcXoQCOBx6TvL2zZTrG7J/68yDHfHGg 26 | w3eFN7ac8FsliRpT+UVKM97zJXcWFkai5Q+R7oKsWXRVXQUZZxg9 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/certs/web.nginx-proxy.tld.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAzit0E7Ia1XJcPhD3YwEi3+jZzwuKP0B1Ylh4J56vM9KhGWrh 3 | t1fb2Y8FcMI1XfFEDVFidHPld9m7xtAzekOI6eY8LdQ5nWE0WhnzwZbgvSZbaRim 4 | TIwhBNj6ViLsVQ26SU2OJ2l/gunn6cS3h3DX10tJ0cGMsFoTYtvewZQx0cl0xGMB 5 | UBBwQnNnxHYy+9K3kS/ozzqWSu6ODRN0cxvkdIPnZtaNgRlUW9hHPju1/TWi3/N9 6 | HJ5n7lDaKJwCCq11jQT3KB8EiROs7ak0Jtz3+R9yIdVy+wnZy0DADTY8wHcOmvdB 7 | 8TvdtgWrE2DF/cZf9QXEQgC6te/73GSY2U0rBwIDAQABAoIBAGVkDVPaVUP/V8nW 8 | QjNYTbRcKTGfdT+iDZht9blWWsdboIqFe7fU53PY2E4Z1HD8xADgs1Cd5o3IcIZX 9 | wdkw+VY+Of43zpXNRhfBh5T/BEtBX9cRnkcq6todcw+FYUB63dBK6cwMH/9b1Qes 10 | DK35GszwY79aNjxMMBiAFM6SeOW4EElPsV8wd9ldX/ndiZuwkZ6k9PfyWrfeeaF+ 11 | EwVf/HaT0bV7cHQ73tYqzKjMpdbzIyaMzuAMGZDwPfLK+O1rEsWvLvK0ypl2Omzw 12 | ndon8U3z0JPNmBGoq+SFS2qtCeOezNX3lPz+TWxG05R5iiFtuK83zJ5qGqCgCNZ6 13 | qzpZsOECgYEA/NvWqT5MdZS1fdL2wROzFMTH4OBdUGr1Gh/DsNZj4qFVSFl969mA 14 | 7Vntm+koNLFsJt2EB67kC3ZWjozLXomHJ55/uKNnJ5LrLxczQ9x4l52CsTzrlvFq 15 | crYjQZDmeN3B4Z+8RSi2icq6j1PeaCZRTvcz6eBjNYj/v/O0SmiXIp8CgYEA0Lsh 16 | fZWuw23a8UXS2YUrXXqfIEdisVMnLRu3Zi0Y1R4lIpuwn5+2n+TxnuWcY1q+ZTMw 17 | dcmGPi6aRj81kEN/Kw5raKoVb6YywTNB4/Dwz7PRQH386FrjfivGXGEEINgbPQ09 18 | 2u0QV2Cr9yMGZ5qNXut70RYewkxjF7+s6L8+RpkCgYB9ikBHgtC/R/fb4pP0RG2T 19 | ECgUtBBgTtomAENOVwL8kBEhfJ0SLcjfDtjzoYz+rF//49cbYW+DaVuMJscJxso9 20 | l2neJ/KdKUpu9NvVA280B1XN3WsyY+Xv0hIrCWAD/kW2WXJF+/K08twxMPipSOzx 21 | gbZalbdr6vrfOIX4s3jmDQKBgDiXA3Vw53jEh99x9sBSgndNj2bI89DvomdwZECn 22 | aVweWCMR4sjkHDctcvSJe+TT7VqyjijhAixJpjn1WShLpGaf+i7eLgGfJZOLugl6 23 | gU9OiSTbA35bZeIHLDhPdTcSYBAlTufT7eJCq1zNeicMl9dsMJ13Sc+TtinyJYbU 24 | kqXBAoGBAL9gRa1PkNkpCJ5F9aYSohCAXB7DaAgYvVyvOTQ8Bw2uACPgdnpHmxQd 25 | /sT7qJ1h8ZCtn89Ug/4yx79eUcOImugoCRIUVtq1xhyXUdVl55Tuy5bKBSSAe/Vh 26 | T7sAmryCkzn9ihRziY2j84vK0mdMkCU5AoatPg5l0g1adn5zcY6q 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/requirements/README.md: -------------------------------------------------------------------------------- 1 | This directory contains resources to build Docker images tests depend on 2 | 3 | # Build images 4 | 5 | make build-webserver 6 | 7 | 8 | # python-requirements.txt 9 | 10 | If you want to run the test suite from your computer, you need python and a few python modules. 11 | The _python-requirements.txt_ file describes the python modules required. To install them, use 12 | pip: 13 | 14 | pip install -r python-requirements.txt 15 | 16 | If you don't want to run the test from your computer, you can run the tests from a docker container, see the _pytest.sh_ script. 17 | 18 | 19 | # Images 20 | 21 | ## web 22 | 23 | This container will run one or many webservers, each of them listening on a single port. 24 | 25 | Ports are specified using the `WEB_PORTS` environment variable: 26 | 27 | docker run -d -e WEB_PORTS=80 web # will create a container running one webserver listening on port 80 28 | docker run -d -e WEB_PORTS="80 81" web # will create a container running two webservers, one listening on port 80 and a second one listening on port 81 29 | 30 | The webserver answers on two paths: 31 | 32 | - `/headers` 33 | - `/port` 34 | 35 | ``` 36 | $ docker run -d -e WEB_PORTS=80 -p 80:80 web 37 | $ curl http://127.0.0.1:80/headers 38 | Host: 127.0.0.1 39 | User-Agent: curl/7.47.0 40 | Accept: */* 41 | 42 | $ curl http://127.0.0.1:80/port 43 | answer from port 80 44 | 45 | ``` 46 | 47 | 48 | ## nginx-proxy-tester 49 | 50 | This is an optional requirement which is usefull if you cannot (or don't want to) install pytest and its requirements on your computer. In this case, you can use the `nginx-proxy-tester` docker image to run the test suite from a Docker container. 51 | 52 | To use this image, it is mandatory to run the container using the `pytest.sh` shell script. The script will build the image and run a container from it with the appropriate volumes and settings. 53 | -------------------------------------------------------------------------------- /test/test_dockergen/test_dockergen_v3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import docker 3 | import logging 4 | import pytest 5 | import re 6 | from distutils.version import LooseVersion 7 | 8 | 9 | raw_version = docker.from_env().version()["Version"] 10 | pytestmark = pytest.mark.skipif( 11 | LooseVersion(raw_version) < LooseVersion("1.13"), 12 | reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" 13 | ) 14 | 15 | 16 | @pytest.fixture(scope="module") 17 | def nginx_tmpl(): 18 | """ 19 | pytest fixture which extracts the the nginx config template from 20 | the nginxproxy/nginx-proxy:test image 21 | """ 22 | script_dir = os.path.dirname(__file__) 23 | logging.info("extracting nginx.tmpl from nginxproxy/nginx-proxy:test") 24 | docker_client = docker.from_env() 25 | print( 26 | docker_client.containers.run( 27 | image="nginxproxy/nginx-proxy:test", 28 | remove=True, 29 | volumes=["{current_dir}:{current_dir}".format(current_dir=script_dir)], 30 | entrypoint="sh", 31 | command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format( 32 | current_dir=script_dir 33 | ), 34 | stderr=True, 35 | ) 36 | ) 37 | yield 38 | logging.info("removing nginx.tmpl") 39 | os.remove(os.path.join(script_dir, "nginx.tmpl")) 40 | 41 | 42 | def test_unknown_virtual_host_is_503(nginx_tmpl, docker_compose, nginxproxy): 43 | r = nginxproxy.get("http://unknown.nginx.container.docker/") 44 | assert r.status_code == 503 45 | 46 | 47 | def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy): 48 | r = nginxproxy.get("http://whoami.nginx.container.docker/") 49 | assert r.status_code == 200 50 | whoami_container = docker_compose.containers.get("whoami") 51 | assert r.text == f"I'm {whoami_container.id[:12]}\n" 52 | 53 | 54 | if __name__ == "__main__": 55 | import doctest 56 | doctest.testmod() 57 | -------------------------------------------------------------------------------- /test/test_ssl/test_dhparam.yml: -------------------------------------------------------------------------------- 1 | web5: 2 | image: web 3 | expose: 4 | - "85" 5 | environment: 6 | WEB_PORTS: "85" 7 | VIRTUAL_HOST: "web5.nginx-proxy.tld" 8 | 9 | # Intended for testing with `dh-file` container. 10 | # VIRTUAL_HOST is paired with site-specific DH param file. 11 | # DEFAULT_HOST is required to avoid defaulting to web2, 12 | # if not specifying FQDN (`-servername`) in openssl queries. 13 | web2: 14 | image: web 15 | expose: 16 | - "85" 17 | environment: 18 | WEB_PORTS: "85" 19 | VIRTUAL_HOST: "web2.nginx-proxy.tld" 20 | 21 | 22 | # sut - System Under Test 23 | # `docker.sock` required for functionality 24 | # `certs` required to enable HTTPS via template 25 | with_default_group: 26 | container_name: dh-default 27 | image: &img-nginxproxy nginxproxy/nginx-proxy:test 28 | environment: &env-common 29 | - &default-host DEFAULT_HOST=web5.nginx-proxy.tld 30 | volumes: &vols-common 31 | - &docker-sock /var/run/docker.sock:/tmp/docker.sock:ro 32 | - &nginx-certs ./certs:/etc/nginx/certs:ro 33 | 34 | with_alternative_group: 35 | container_name: dh-env 36 | environment: 37 | - DHPARAM_BITS=3072 38 | - *default-host 39 | image: *img-nginxproxy 40 | volumes: *vols-common 41 | 42 | with_invalid_group: 43 | container_name: invalid-group-1024 44 | environment: 45 | - DHPARAM_BITS=1024 46 | - *default-host 47 | image: *img-nginxproxy 48 | volumes: *vols-common 49 | 50 | with_custom_file: 51 | container_name: dh-file 52 | image: *img-nginxproxy 53 | environment: *env-common 54 | volumes: 55 | - *docker-sock 56 | - *nginx-certs 57 | - ../../app/dhparam/ffdhe3072.pem:/etc/nginx/dhparam/dhparam.pem:ro 58 | 59 | with_skip: 60 | container_name: dh-skip 61 | environment: 62 | - DHPARAM_SKIP=true 63 | - *default-host 64 | image: *img-nginxproxy 65 | volumes: *vols-common 66 | 67 | with_skip_backward: 68 | container_name: dh-skip-backward 69 | environment: 70 | - DHPARAM_GENERATION=false 71 | - *default-host 72 | image: *img-nginxproxy 73 | volumes: *vols-common 74 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # ⚠️ PLEASE READ ⚠️ 2 | 3 | ## Questions or Features 4 | 5 | If you have a question or want to request a feature, please **DO NOT SUBMIT** a new issue. 6 | 7 | Instead please use the relevant Discussions section's category: 8 | - 🙏 [Ask a question](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/q-a) 9 | - 💡 [Request a feature](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/ideas) 10 | 11 | ## Bugs 12 | 13 | If you are logging a bug, please search the current open issues first to see if there is already a bug opened. 14 | 15 | For bugs, the easier you make it to reproduce the issue you see and the more initial information you provide, the easier and faster the bug can be identified and can get fixed. 16 | 17 | Please at least provide: 18 | - the exact nginx-proxy version you're using (if using `latest` please make sure it is up to date and provide the version number printed at container startup). 19 | - complete configuration (compose file, command line, etc) of both your nginx-proxy container(s) and proxied containers. You should redact sensitive info if needed but please provide **full** configurations. 20 | - generated nginx configuration obtained with `docker exec nameofyournginxproxycontainer nginx -T` 21 | 22 | If you can provide a script or docker-compose file that reproduces the problems, that is very helpful. 23 | 24 | ## General advice about `latest` 25 | 26 | Do not use the `latest` tag for production setups. 27 | 28 | `latest` is nothing more than a convenient default used by Docker if no specific tag is provided, there isn't any strict convention on what goes into this tag over different projects, and it does not carry any promise of stability. 29 | 30 | Using `latest` will most certainly put you at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes) and makes it harder for maintainers to track which exact version of the container you are experiencing an issue with. 31 | 32 | This recommendation stands for pretty much every Docker image in existence, not just nginx-proxy's ones. 33 | 34 | Thanks, 35 | Nicolas 36 | -------------------------------------------------------------------------------- /test/test_virtual-path/test_virtual_paths.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | import pytest 4 | from docker.errors import NotFound 5 | 6 | @pytest.mark.parametrize("stub,expected_port", [ 7 | ("nginx-proxy.test/web1", 81), 8 | ("nginx-proxy.test/web2", 82), 9 | ("nginx-proxy.test", 83), 10 | ("foo.nginx-proxy.test", 42), 11 | ]) 12 | def test_valid_path(docker_compose, nginxproxy, stub, expected_port): 13 | r = nginxproxy.get(f"http://{stub}/port") 14 | assert r.status_code == 200 15 | assert r.text == f"answer from port {expected_port}\n" 16 | 17 | @pytest.mark.parametrize("stub", [ 18 | "nginx-proxy.test/foo", 19 | "bar.nginx-proxy.test", 20 | ]) 21 | def test_invalid_path(docker_compose, nginxproxy, stub): 22 | r = nginxproxy.get(f"http://{stub}/port") 23 | assert r.status_code in [404, 503] 24 | 25 | @pytest.fixture() 26 | def web4(docker_compose): 27 | """ 28 | pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy.test`, `VIRTUAL_PATH=/web4/` and `VIRTUAL_DEST=/` listening on port 84. 29 | """ 30 | container = docker_compose.containers.run( 31 | name="web4", 32 | image="web", 33 | detach=True, 34 | environment={ 35 | "WEB_PORTS": "84", 36 | "VIRTUAL_HOST": "nginx-proxy.test", 37 | "VIRTUAL_PATH": "/web4/", 38 | "VIRTUAL_DEST": "/", 39 | }, 40 | ports={"84/tcp": None} 41 | ) 42 | sleep(2) # give it some time to initialize and for docker-gen to detect it 43 | yield container 44 | try: 45 | docker_compose.containers.get("web4").remove(force=True) 46 | except NotFound: 47 | pass 48 | 49 | """ 50 | Test if we can add and remove a single virtual_path from multiple ones on the same subdomain. 51 | """ 52 | def test_container_hotplug(web4, nginxproxy): 53 | r = nginxproxy.get(f"http://nginx-proxy.test/web4/port") 54 | assert r.status_code == 200 55 | assert r.text == f"answer from port 84\n" 56 | web4.remove(force=True) 57 | sleep(2) 58 | r = nginxproxy.get(f"http://nginx-proxy.test/web4/port") 59 | assert r.status_code == 404 60 | -------------------------------------------------------------------------------- /Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | # setup build arguments for version of dependencies to use 2 | ARG DOCKER_GEN_VERSION=0.9.0 3 | ARG FOREGO_VERSION=v0.17.0 4 | 5 | # Use a specific version of golang to build both binaries 6 | FROM golang:1.18.1-alpine as gobuilder 7 | RUN apk add --no-cache git musl-dev 8 | 9 | # Build docker-gen from scratch 10 | FROM gobuilder as dockergen 11 | 12 | ARG DOCKER_GEN_VERSION 13 | 14 | RUN git clone https://github.com/nginx-proxy/docker-gen \ 15 | && cd /go/docker-gen \ 16 | && git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \ 17 | && go mod download \ 18 | && CGO_ENABLED=0 go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" ./cmd/docker-gen \ 19 | && go clean -cache \ 20 | && mv docker-gen /usr/local/bin/ \ 21 | && cd - \ 22 | && rm -rf /go/docker-gen 23 | 24 | # Build forego from scratch 25 | FROM gobuilder as forego 26 | 27 | ARG FOREGO_VERSION 28 | 29 | RUN git clone https://github.com/nginx-proxy/forego/ \ 30 | && cd /go/forego \ 31 | && git -c advice.detachedHead=false checkout $FOREGO_VERSION \ 32 | && go mod download \ 33 | && CGO_ENABLED=0 go build -o forego . \ 34 | && go clean -cache \ 35 | && mv forego /usr/local/bin/ \ 36 | && cd - \ 37 | && rm -rf /go/forego 38 | 39 | # Build the final image 40 | FROM nginx:1.21.6-alpine 41 | 42 | ARG NGINX_PROXY_VERSION 43 | # Add DOCKER_GEN_VERSION environment variable 44 | # Because some external projects rely on it 45 | ARG DOCKER_GEN_VERSION 46 | ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \ 47 | DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \ 48 | DOCKER_HOST=unix:///tmp/docker.sock 49 | 50 | # Install wget and install/updates certificates 51 | RUN apk add --no-cache --virtual .run-deps \ 52 | ca-certificates bash wget openssl \ 53 | && update-ca-certificates 54 | 55 | # Configure Nginx 56 | RUN echo "daemon off;" >> /etc/nginx/nginx.conf \ 57 | && sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \ 58 | && sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \ 59 | && mkdir -p '/etc/nginx/dhparam' 60 | 61 | # Install Forego + docker-gen 62 | COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego 63 | COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen 64 | 65 | COPY network_internal.conf /etc/nginx/ 66 | 67 | COPY app nginx.tmpl LICENSE /app/ 68 | WORKDIR /app/ 69 | 70 | ENTRYPOINT ["/app/docker-entrypoint.sh"] 71 | CMD ["forego", "start", "-r"] 72 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # setup build arguments for version of dependencies to use 2 | ARG DOCKER_GEN_VERSION=0.9.0 3 | ARG FOREGO_VERSION=v0.17.0 4 | 5 | # Use a specific version of golang to build both binaries 6 | FROM golang:1.18.1 as gobuilder 7 | 8 | # Build docker-gen from scratch 9 | FROM gobuilder as dockergen 10 | 11 | ARG DOCKER_GEN_VERSION 12 | 13 | RUN git clone https://github.com/nginx-proxy/docker-gen \ 14 | && cd /go/docker-gen \ 15 | && git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \ 16 | && go mod download \ 17 | && CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" ./cmd/docker-gen \ 18 | && go clean -cache \ 19 | && mv docker-gen /usr/local/bin/ \ 20 | && cd - \ 21 | && rm -rf /go/docker-gen 22 | 23 | # Build forego from scratch 24 | FROM gobuilder as forego 25 | 26 | ARG FOREGO_VERSION 27 | 28 | RUN git clone https://github.com/nginx-proxy/forego/ \ 29 | && cd /go/forego \ 30 | && git -c advice.detachedHead=false checkout $FOREGO_VERSION \ 31 | && go mod download \ 32 | && CGO_ENABLED=0 GOOS=linux go build -o forego . \ 33 | && go clean -cache \ 34 | && mv forego /usr/local/bin/ \ 35 | && cd - \ 36 | && rm -rf /go/forego 37 | 38 | # Build the final image 39 | FROM nginx:1.21.6 40 | 41 | ARG NGINX_PROXY_VERSION 42 | # Add DOCKER_GEN_VERSION environment variable 43 | # Because some external projects rely on it 44 | ARG DOCKER_GEN_VERSION 45 | ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \ 46 | DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \ 47 | DOCKER_HOST=unix:///tmp/docker.sock 48 | 49 | # Install wget and install/updates certificates 50 | RUN apt-get update \ 51 | && apt-get install -y -q --no-install-recommends \ 52 | ca-certificates \ 53 | wget \ 54 | && apt-get clean \ 55 | && rm -r /var/lib/apt/lists/* 56 | 57 | 58 | # Configure Nginx 59 | RUN echo "daemon off;" >> /etc/nginx/nginx.conf \ 60 | && sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \ 61 | && sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \ 62 | && mkdir -p '/etc/nginx/dhparam' 63 | 64 | # Install Forego + docker-gen 65 | COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego 66 | COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen 67 | 68 | COPY network_internal.conf /etc/nginx/ 69 | 70 | COPY app nginx.tmpl LICENSE /app/ 71 | WORKDIR /app/ 72 | 73 | ENTRYPOINT ["/app/docker-entrypoint.sh"] 74 | CMD ["forego", "start", "-r"] 75 | -------------------------------------------------------------------------------- /test/test_events.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test that nginx-proxy detects new containers 3 | """ 4 | from time import sleep 5 | 6 | import pytest 7 | from docker.errors import NotFound 8 | 9 | 10 | @pytest.fixture() 11 | def web1(docker_compose): 12 | """ 13 | pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81. 14 | """ 15 | container = docker_compose.containers.run( 16 | name="web1", 17 | image="web", 18 | detach=True, 19 | environment={ 20 | "WEB_PORTS": "81", 21 | "VIRTUAL_HOST": "web1.nginx-proxy" 22 | }, 23 | ports={"81/tcp": None} 24 | ) 25 | sleep(2) # give it some time to initialize and for docker-gen to detect it 26 | yield container 27 | try: 28 | docker_compose.containers.get("web1").remove(force=True) 29 | except NotFound: 30 | pass 31 | 32 | @pytest.fixture() 33 | def web2(docker_compose): 34 | """ 35 | pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82. 36 | """ 37 | container = docker_compose.containers.run( 38 | name="web2", 39 | image="web", 40 | detach=True, 41 | environment={ 42 | "WEB_PORTS": "82", 43 | "VIRTUAL_HOST": "nginx-proxy", 44 | "VIRTUAL_PATH": "/web2/", 45 | "VIRTUAL_DEST": "/", 46 | }, 47 | ports={"82/tcp": None} 48 | ) 49 | sleep(2) # give it some time to initialize and for docker-gen to detect it 50 | yield container 51 | try: 52 | docker_compose.containers.get("web2").remove(force=True) 53 | except NotFound: 54 | pass 55 | 56 | def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy): 57 | r = nginxproxy.get("http://nginx-proxy/") 58 | assert r.status_code == 503 59 | 60 | 61 | def test_new_container_is_detected_vhost(web1, nginxproxy): 62 | r = nginxproxy.get("http://web1.nginx-proxy/port") 63 | assert r.status_code == 200 64 | assert "answer from port 81\n" == r.text 65 | 66 | web1.remove(force=True) 67 | sleep(2) 68 | r = nginxproxy.get("http://web1.nginx-proxy/port") 69 | assert r.status_code == 503 70 | 71 | def test_new_container_is_detected_vpath(web2, nginxproxy): 72 | r = nginxproxy.get("http://nginx-proxy/web2/port") 73 | assert r.status_code == 200 74 | assert "answer from port 82\n" == r.text 75 | r = nginxproxy.get("http://nginx-proxy/port") 76 | assert r.status_code in [404, 503] 77 | 78 | web2.remove(force=True) 79 | sleep(2) 80 | r = nginxproxy.get("http://nginx-proxy/web2/port") 81 | assert r.status_code == 503 82 | 83 | -------------------------------------------------------------------------------- /test/certs/README.md: -------------------------------------------------------------------------------- 1 | create_server_certificate.sh 2 | ============================ 3 | 4 | `create_server_certificate.sh` is a script helping with issuing server certificates that can be used to provide TLS on web servers. 5 | 6 | It also creates a Certificate Authority (CA) root key and certificate. This CA root certificate can be used to validate the server certificates it generates. 7 | 8 | For instance, with _curl_: 9 | 10 | curl --cacert /somewhere/ca-root.crt https://www.example.com/ 11 | 12 | or with _wget_: 13 | 14 | wget --certificate=/somewhere/ca-root.crt https://www.example.com/ 15 | 16 | or with the python _requests_ module: 17 | 18 | import requests 19 | r = requests.get("https://www.example.com", verify="/somewhere/ca-root.crt") 20 | 21 | Usage 22 | ----- 23 | 24 | ### Simple domain 25 | 26 | Create a server certificate for domain `www.example.com`: 27 | 28 | ./create_server_certificate.sh www.example.com 29 | 30 | Will produce: 31 | - `www.example.com.key` 32 | - `www.example.com.crt` 33 | 34 | 35 | ### Multiple domains 36 | 37 | Create a server certificate for main domain `www.example.com` and alternative domains `example.com`, `foo.com` and `bar.com`: 38 | 39 | ./create_server_certificate.sh www.example.com foo.com bar.com 40 | 41 | Will produce: 42 | - `www.example.com.key` 43 | - `www.example.com.crt` 44 | 45 | ### Wildcard domain 46 | 47 | Create a server certificate for wildcard domain `*.example.com`: 48 | 49 | ./create_server_certificate.sh "*.example.com" 50 | 51 | Note that you need to use quotes around the domain string or the shell would expand `*`. 52 | 53 | Will produce: 54 | - `*.example.com.key` 55 | - `*.example.com.crt` 56 | 57 | Again, to prevent your shell from expanding `*`, use quotes. i.e.: `cat "*.example.com.crt"`. 58 | 59 | Such a server certificate would be valid for domains: 60 | - `foo.example.com` 61 | - `bar.example.com` 62 | 63 | but not for domains: 64 | - `example.com` 65 | - `foo.bar.example.com` 66 | 67 | 68 | ### Wildcard domain on multiple levels 69 | 70 | While you can technically create a server certificate for wildcard domain `*.example.com` and alternative name `*.*.example.com`, client implementations generally do not support multiple wildcards in a domain name. 71 | 72 | For instance, a python script using urllib3 would fail to validate domain `foo.bar.example.com` presenting a certificate with name `*.*.example.com`. It is advised to stay away from producing such certificates. 73 | 74 | If you want to give it a try: 75 | 76 | ./create_server_certificate.sh "*.example.com" "*.*.example.com" 77 | 78 | Such a server certificate would be valid for domains: 79 | - `foo.example.com` 80 | - `bar.example.com` 81 | - `foo.bar.example.com` 82 | -------------------------------------------------------------------------------- /test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from os.path import join, isfile 4 | from shutil import copy 5 | from time import sleep 6 | 7 | import pytest 8 | from requests import ConnectionError 9 | 10 | script_dir = os.path.dirname(__file__) 11 | 12 | pytestmark = pytest.mark.xfail() # TODO delete this marker once those issues are fixed 13 | 14 | @pytest.fixture(scope="module", autouse=True) 15 | def certs(): 16 | """ 17 | pytest fixture that provides cert and key files into the tmp_certs directory 18 | """ 19 | file_names = ("web.nginx-proxy.crt", "web.nginx-proxy.key") 20 | logging.info("copying server cert and key files into tmp_certs") 21 | for f_name in file_names: 22 | copy(join(script_dir, "certs", f_name), join(script_dir, "tmp_certs")) 23 | yield 24 | logging.info("cleaning up the tmp_cert directory") 25 | for f_name in file_names: 26 | if isfile(join(script_dir, "tmp_certs", f_name)): 27 | os.remove(join(script_dir, "tmp_certs", f_name)) 28 | 29 | ############################################################################### 30 | 31 | 32 | def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): 33 | r = nginxproxy.get("http://foo.nginx-proxy/") 34 | assert r.status_code == 503 35 | 36 | 37 | def test_http_web_is_301(docker_compose, nginxproxy): 38 | r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False) 39 | assert r.status_code == 301 40 | 41 | 42 | def test_https_web_is_200(docker_compose, nginxproxy): 43 | r = nginxproxy.get("https://web.nginx-proxy/port") 44 | assert r.status_code == 200 45 | assert "answer from port 81\n" in r.text 46 | 47 | 48 | @pytest.mark.incremental 49 | def test_delete_cert_and_restart_reverseproxy(docker_compose): 50 | os.remove(join(script_dir, "tmp_certs", "web.nginx-proxy.crt")) 51 | docker_compose.containers.get("reverseproxy").restart() 52 | sleep(3) # give time for the container to initialize 53 | assert "running" == docker_compose.containers.get("reverseproxy").status 54 | 55 | 56 | @pytest.mark.incremental 57 | def test_unknown_virtual_host_is_still_503(nginxproxy): 58 | r = nginxproxy.get("http://foo.nginx-proxy/") 59 | assert r.status_code == 503 60 | 61 | 62 | @pytest.mark.incremental 63 | def test_http_web_is_now_200(nginxproxy): 64 | r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False) 65 | assert r.status_code == 200 66 | assert "answer from port 81\n" == r.text 67 | 68 | 69 | @pytest.mark.incremental 70 | def test_https_web_is_now_broken_since_there_is_no_cert(nginxproxy): 71 | with pytest.raises(ConnectionError): 72 | nginxproxy.get("https://web.nginx-proxy/port") 73 | -------------------------------------------------------------------------------- /test/stress_tests/test_unreachable_network/README.md: -------------------------------------------------------------------------------- 1 | # nginx-proxy template is not considered when a container is not reachable 2 | 3 | Having a container with the `VIRTUAL_HOST` environment variable set but on a network not reachable from the nginx-proxy container will result in nginx-proxy serving the default nginx welcome page for all requests. 4 | 5 | Furthermore, if the nginx-proxy in such state is restarted, the nginx process will crash and the container stops. 6 | 7 | In the generated nginx config file, we can notice the presence of an empty `upstream {}` block. 8 | 9 | This can be fixed by merging [PR-585](https://github.com/nginx-proxy/nginx-proxy/pull/585). 10 | 11 | ## How to reproduce 12 | 13 | 1. a first web container is created on network `netA` 14 | 1. a second web container is created on network `netB` 15 | 1. nginx-proxy is created with access to `netA` only 16 | 17 | 18 | ## Erratic behavior 19 | 20 | - nginx serves the default welcome page for all requests to `/` and error 404 for any other path 21 | - nginx-container crash on restart 22 | 23 | Log shows: 24 | 25 | ``` 26 | webB_1 | starting a web server listening on port 82 27 | webA_1 | starting a web server listening on port 81 28 | reverseproxy | forego | starting dockergen.1 on port 5000 29 | reverseproxy | forego | starting nginx.1 on port 5100 30 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Generated '/etc/nginx/conf.d/default.conf' from 3 containers 31 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Running 'nginx -s reload' 32 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Error running notify command: nginx -s reload, exit status 1 33 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Watching docker events 34 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload' 35 | reverseproxy | reverseproxy | forego | starting dockergen.1 on port 5000 <---- nginx-proxy container restarted 36 | reverseproxy | forego | starting nginx.1 on port 5100 37 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Generated '/etc/nginx/conf.d/default.conf' from 3 containers 38 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Running 'nginx -s reload' 39 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Error running notify command: nginx -s reload, exit status 1 40 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Watching docker events 41 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload' 42 | reverseproxy | forego | starting dockergen.1 on port 5000 43 | reverseproxy | forego | starting nginx.1 on port 5100 44 | reverseproxy | nginx.1 | 2017/02/20 01:11:02 [emerg] 17#17: no servers are inside upstream in /etc/nginx/conf.d/default.conf:64 45 | reverseproxy | forego | starting nginx.1 on port 5200 46 | reverseproxy | forego | sending SIGTERM to nginx.1 47 | reverseproxy | forego | sending SIGTERM to dockergen.1 48 | reverseproxy exited with code 0 49 | reverseproxy exited with code 0 50 | 51 | ``` 52 | 53 | ## Expected behavior 54 | 55 | - no default nginx welcome page should be served 56 | - nginx is able to forward requests to containers of `netA` 57 | - nginx respond with error 503 for unknown virtual hosts 58 | - nginx is not able to forward requests to containers of `netB` and responds with an error 59 | - nginx should survive restarts 60 | -------------------------------------------------------------------------------- /app/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | function _parse_true() { 5 | case "$1" in 6 | 7 | true | True | TRUE | 1) 8 | return 0 9 | ;; 10 | 11 | *) 12 | return 1 13 | ;; 14 | 15 | esac 16 | } 17 | 18 | function _parse_false() { 19 | case "$1" in 20 | 21 | false | False | FALSE | 0) 22 | return 0 23 | ;; 24 | 25 | *) 26 | return 1 27 | ;; 28 | 29 | esac 30 | } 31 | 32 | function _print_version { 33 | if [[ -n "${NGINX_PROXY_VERSION:-}" ]]; then 34 | echo "Info: running nginx-proxy version ${NGINX_PROXY_VERSION}" 35 | fi 36 | } 37 | 38 | function _check_unix_socket() { 39 | # Warn if the DOCKER_HOST socket does not exist 40 | if [[ ${DOCKER_HOST} == unix://* ]]; then 41 | local SOCKET_FILE="${DOCKER_HOST#unix://}" 42 | 43 | if [[ ! -S ${SOCKET_FILE} ]]; then 44 | cat >&2 <<-EOT 45 | ERROR: you need to share your Docker host socket with a volume at ${SOCKET_FILE} 46 | Typically you should run your nginxproxy/nginx-proxy with: \`-v /var/run/docker.sock:${SOCKET_FILE}:ro\` 47 | See the documentation at: https://github.com/nginx-proxy/nginx-proxy/#usage 48 | EOT 49 | 50 | exit 1 51 | fi 52 | fi 53 | } 54 | 55 | function _resolvers() { 56 | # Compute the DNS resolvers for use in the templates - if the IP contains ":", it's IPv6 and must be enclosed in [] 57 | RESOLVERS=$(awk '$1 == "nameserver" {print ($2 ~ ":")? "["$2"]": $2}' ORS=' ' /etc/resolv.conf | sed 's/ *$//g'); export RESOLVERS 58 | 59 | SCOPED_IPV6_REGEX='\[fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}\]' 60 | 61 | if [[ -z ${RESOLVERS} ]]; then 62 | echo 'Warning: unable to determine DNS resolvers for nginx' >&2 63 | unset RESOLVERS 64 | elif [[ ${RESOLVERS} =~ ${SCOPED_IPV6_REGEX} ]]; then 65 | echo -n 'Warning: Scoped IPv6 addresses removed from resolvers: ' >&2 66 | echo "${RESOLVERS}" | grep -Eo "$SCOPED_IPV6_REGEX" | paste -s -d ' ' >&2 67 | RESOLVERS=$(echo "${RESOLVERS}" | sed -r "s/${SCOPED_IPV6_REGEX}//g" | xargs echo -n); export RESOLVERS 68 | fi 69 | } 70 | 71 | function _setup_dhparam() { 72 | # DH params will be supplied for nginx here: 73 | local DHPARAM_FILE='/etc/nginx/dhparam/dhparam.pem' 74 | 75 | # Should be 2048, 3072, or 4096 (default): 76 | local FFDHE_GROUP="${DHPARAM_BITS:=4096}" 77 | 78 | # DH params may be provided by the user (rarely necessary) 79 | if [[ -f ${DHPARAM_FILE} ]]; then 80 | echo 'Warning: A custom dhparam.pem file was provided. Best practice is to use standardized RFC7919 DHE groups instead.' >&2 81 | return 0 82 | elif _parse_true "${DHPARAM_SKIP:=false}"; then 83 | echo 'Skipping Diffie-Hellman parameters setup.' 84 | return 0 85 | elif _parse_false "${DHPARAM_GENERATION:=true}"; then 86 | echo 'Warning: The DHPARAM_GENERATION environment variable is deprecated, please consider using DHPARAM_SKIP set to true instead.' >&2 87 | echo 'Skipping Diffie-Hellman parameters setup.' 88 | return 0 89 | elif [[ ! ${DHPARAM_BITS} =~ ^(2048|3072|4096)$ ]]; then 90 | echo "ERROR: Unsupported DHPARAM_BITS size: ${DHPARAM_BITS}. Use: 2048, 3072, or 4096 (default)." >&2 91 | exit 1 92 | fi 93 | 94 | echo 'Setting up DH Parameters..' 95 | 96 | # Use an existing pre-generated DH group from RFC7919 (https://datatracker.ietf.org/doc/html/rfc7919#appendix-A): 97 | local RFC7919_DHPARAM_FILE="/app/dhparam/ffdhe${FFDHE_GROUP}.pem" 98 | 99 | # Provide the DH params file to nginx: 100 | cp "${RFC7919_DHPARAM_FILE}" "${DHPARAM_FILE}" 101 | } 102 | 103 | # Run the init logic if the default CMD was provided 104 | if [[ $* == 'forego start -r' ]]; then 105 | _print_version 106 | 107 | _check_unix_socket 108 | 109 | _resolvers 110 | 111 | _setup_dhparam 112 | fi 113 | 114 | exec "$@" 115 | -------------------------------------------------------------------------------- /test/test_ssl/certs/nginx-proxy.tld.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Jan 10 00:08:52 2017 GMT 9 | Not After : May 28 00:08:52 2044 GMT 10 | Subject: CN=*.nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1: 16 | 27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c: 17 | 17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba: 18 | d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27: 19 | 2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f: 20 | de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1: 21 | 71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81: 22 | 0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e: 23 | 1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74: 24 | 61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37: 25 | 78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5: 26 | 14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21: 27 | 67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24: 28 | 7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d: 29 | 6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91: 30 | f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f: 31 | ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96: 32 | 45:85 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:*.nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06: 39 | a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a: 40 | 22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50: 41 | 9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc: 42 | 03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73: 43 | af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9: 44 | 40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7: 45 | 6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11: 46 | 3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7: 47 | f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86: 48 | 3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7: 49 | 26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba: 50 | 63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50: 51 | e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28: 52 | c7:52:de:f9 53 | -----BEGIN CERTIFICATE----- 54 | MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou 57 | bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 58 | y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7 59 | oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti 60 | jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4 61 | /bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9 62 | yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K 63 | 90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy 64 | b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs 65 | E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl 66 | PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC 67 | ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31 68 | iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL 69 | vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q== 70 | -----END CERTIFICATE----- 71 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/certs/default.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Mar 15 00:17:52 2017 GMT 9 | Not After : Jul 31 00:17:52 2044 GMT 10 | Subject: CN=nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:f2:fd:79:70:99:0c:da:63:5c:81:28:72:31:01: 16 | 62:e9:68:d7:cb:8d:c6:95:f9:ec:26:34:1c:08:c6: 17 | 6d:de:ad:d8:b0:c0:ae:48:03:73:76:6b:3f:c5:35: 18 | 86:c6:42:91:53:3c:aa:85:89:84:92:67:92:ef:a9: 19 | 5b:f2:d4:04:73:34:02:35:d4:6a:fa:c2:da:91:4a: 20 | a9:70:87:25:38:84:1d:93:99:3c:d7:03:61:a6:6d: 21 | 33:6f:83:45:04:af:4f:96:62:1e:c1:79:87:c9:d5: 22 | 4c:e9:8f:85:e2:c8:1b:5b:fc:b8:02:ff:7b:6d:34: 23 | 4c:5d:40:73:44:9e:c5:1f:5f:e0:0f:89:88:c4:35: 24 | 2b:04:53:8c:8e:a0:7c:7c:97:16:20:c2:4f:a1:c0: 25 | dd:bf:d5:13:2d:64:25:03:f2:d8:d5:27:01:70:c9: 26 | f4:37:33:36:7e:7b:48:54:ec:37:2b:81:3d:50:3c: 27 | d4:5f:05:19:e2:0b:ba:76:f6:2c:3b:23:4b:82:78: 28 | 5f:e9:e3:57:fc:39:4a:5c:42:82:72:c8:a3:af:b7: 29 | b3:91:e4:01:9c:2c:47:5e:ff:aa:ad:63:1c:e7:9c: 30 | 2e:a2:ac:5d:51:30:83:67:6e:f8:5a:ed:0b:70:e4: 31 | 68:d4:e9:5e:a7:f5:5e:87:3b:e8:31:ad:00:04:f8: 32 | 7b:d9 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 39:d4:cc:78:a3:5e:64:e9:ab:9d:a9:89:3b:9e:18:01:98:cb: 39 | e2:0c:ef:e9:2b:50:34:ed:63:ed:e6:0e:53:59:30:80:e0:3b: 40 | 5e:08:ca:09:55:da:e3:3e:c2:01:d8:d6:ca:92:2a:0b:ee:2c: 41 | a1:93:18:7b:15:28:8d:2a:17:25:76:eb:ef:70:e0:d7:02:d3: 42 | ad:81:33:47:9b:fb:d8:52:87:69:a4:3a:20:a4:9a:2d:3f:40: 43 | 5f:52:bf:0b:96:e3:52:c3:59:55:dc:5a:37:f3:e6:d6:16:46: 44 | 64:e4:20:32:5d:cd:4b:da:2b:ef:e9:85:af:00:a1:ca:a1:08: 45 | ed:0f:f4:65:dc:2a:c9:b3:4e:cc:f3:82:d7:69:3a:4d:fc:8e: 46 | db:10:95:28:20:07:55:f0:d1:11:1f:c5:00:74:88:c6:c9:94: 47 | 15:90:93:3a:de:90:85:fb:72:9c:d8:57:58:05:7d:bb:6a:36: 48 | eb:d8:12:22:41:0e:fc:c9:24:79:c0:28:4f:4f:1b:4b:59:f9: 49 | e4:c6:97:be:b1:94:74:de:a7:65:d3:cb:0a:56:3b:d3:63:fc: 50 | b2:05:fc:e7:ec:bb:45:04:91:9f:21:f9:05:3b:5d:4c:af:8e: 51 | 84:04:f5:25:fb:4d:ab:db:23:56:74:7e:4f:b3:da:bb:27:e7: 52 | ea:fb:bd:00 53 | -----BEGIN CERTIFICATE----- 54 | MIIC8zCCAdugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAzMTUwMDE3NTJaFw00NDA3MzEwMDE3NTJaMBoxGDAWBgNVBAMMD25n 57 | aW54LXByb3h5LnRsZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPL9 58 | eXCZDNpjXIEocjEBYulo18uNxpX57CY0HAjGbd6t2LDArkgDc3ZrP8U1hsZCkVM8 59 | qoWJhJJnku+pW/LUBHM0AjXUavrC2pFKqXCHJTiEHZOZPNcDYaZtM2+DRQSvT5Zi 60 | HsF5h8nVTOmPheLIG1v8uAL/e200TF1Ac0SexR9f4A+JiMQ1KwRTjI6gfHyXFiDC 61 | T6HA3b/VEy1kJQPy2NUnAXDJ9DczNn57SFTsNyuBPVA81F8FGeILunb2LDsjS4J4 62 | X+njV/w5SlxCgnLIo6+3s5HkAZwsR17/qq1jHOecLqKsXVEwg2du+FrtC3DkaNTp 63 | Xqf1Xoc76DGtAAT4e9kCAwEAAaMeMBwwGgYDVR0RBBMwEYIPbmdpbngtcHJveHku 64 | dGxkMA0GCSqGSIb3DQEBCwUAA4IBAQA51Mx4o15k6audqYk7nhgBmMviDO/pK1A0 65 | 7WPt5g5TWTCA4DteCMoJVdrjPsIB2NbKkioL7iyhkxh7FSiNKhclduvvcODXAtOt 66 | gTNHm/vYUodppDogpJotP0BfUr8LluNSw1lV3Fo38+bWFkZk5CAyXc1L2ivv6YWv 67 | AKHKoQjtD/Rl3CrJs07M84LXaTpN/I7bEJUoIAdV8NERH8UAdIjGyZQVkJM63pCF 68 | +3Kc2FdYBX27ajbr2BIiQQ78ySR5wChPTxtLWfnkxpe+sZR03qdl08sKVjvTY/yy 69 | Bfzn7LtFBJGfIfkFO11Mr46EBPUl+02r2yNWdH5Ps9q7J+fq+70A 70 | -----END CERTIFICATE----- 71 | -------------------------------------------------------------------------------- /test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Feb 17 23:20:54 2017 GMT 9 | Not After : Jul 5 23:20:54 2044 GMT 10 | Subject: CN=web.nginx-proxy 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:b6:27:63:a5:c6:e8:f4:7a:94:0e:cc:a2:62:76: 16 | 6d:5d:33:6f:cf:19:fc:e7:e5:bb:0e:0e:d0:7c:4f: 17 | 73:4c:48:2b:17:d1:4d:d5:9f:42:08:73:84:54:8c: 18 | 86:d2:c5:da:59:01:3f:42:22:e0:36:f0:dc:ab:de: 19 | 0a:bd:26:2b:22:13:87:a6:1f:23:ef:0e:99:27:8b: 20 | 15:4a:1b:ef:93:c9:6b:91:de:a0:02:0c:62:bb:cc: 21 | 56:37:e8:25:92:c3:1f:f1:69:d8:7c:a8:33:e0:89: 22 | ce:14:67:a0:39:77:88:91:e6:a3:07:97:90:22:88: 23 | d0:79:18:63:fb:6f:7e:ee:2b:42:7e:23:f5:e7:da: 24 | e9:ee:6a:fa:96:65:9f:e1:2b:15:49:c8:cd:2d:ce: 25 | 86:4f:2c:2a:67:79:bf:41:30:14:cc:f6:0f:14:74: 26 | 9e:b6:d3:d0:3b:f0:1b:b8:e8:19:2a:fd:d6:fd:dc: 27 | 4b:4e:65:7d:9b:bf:37:7e:2d:35:22:2e:74:90:ce: 28 | 41:35:3d:41:a0:99:db:97:1f:bf:3e:18:3c:48:fb: 29 | da:df:c6:4e:4e:b9:67:b8:10:d5:a5:13:03:c4:b7: 30 | 65:e7:aa:f0:14:4b:d3:4d:ea:fe:8f:69:cf:50:21: 31 | 63:27:cf:9e:4c:67:15:7b:3f:3b:da:cb:17:80:61: 32 | 1e:25 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:web.nginx-proxy 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 09:31:be:db:4e:b0:b6:68:da:ae:5b:16:51:29:fc:9f:61:b6: 39 | 5a:2f:3c:35:ef:67:76:97:b0:34:4e:3b:b4:d6:88:19:4f:84: 40 | 2e:73:d3:c0:3a:4c:41:54:6c:bb:67:89:67:ad:25:55:d7:d4: 41 | 80:fe:a7:3f:3d:9e:f1:34:96:d8:da:5a:78:51:c0:63:f1:52: 42 | 29:35:55:f4:7d:70:1c:d3:96:62:7f:64:86:81:52:27:c4:c6: 43 | 10:13:c6:73:56:4d:32:d0:b3:c3:c8:2c:25:83:e4:2b:1d:d4: 44 | 74:30:e5:85:af:2d:b6:a5:6b:fe:5d:d3:3c:00:58:94:f4:6a: 45 | f5:a6:1d:cf:f9:ed:d5:27:ed:13:24:b2:4f:2b:f3:b8:e4:af: 46 | 0c:1d:fe:e0:6a:01:5e:a2:44:ff:3e:96:fa:6c:39:a3:51:37: 47 | f3:72:55:d8:2d:29:6e:de:95:b9:d8:e3:1e:65:a5:9c:0d:79: 48 | 2d:39:ab:c7:ac:16:b6:a5:71:4b:35:a4:6c:72:47:1b:72:9c: 49 | 67:58:c1:fc:f6:7f:a7:73:50:7b:d6:27:57:74:a1:31:38:a7: 50 | 31:e3:b9:d4:c9:45:33:ec:ed:16:cf:c5:bd:d0:03:b1:45:3f: 51 | 68:0d:91:5c:26:4e:37:05:74:ed:3e:75:5e:ca:5e:ee:e2:51: 52 | 4b:da:08:99 53 | -----BEGIN CERTIFICATE----- 54 | MIIC8zCCAdugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAyMTcyMzIwNTRaFw00NDA3MDUyMzIwNTRaMBoxGDAWBgNVBAMMD3dl 57 | Yi5uZ2lueC1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYn 58 | Y6XG6PR6lA7MomJ2bV0zb88Z/Ofluw4O0HxPc0xIKxfRTdWfQghzhFSMhtLF2lkB 59 | P0Ii4Dbw3KveCr0mKyITh6YfI+8OmSeLFUob75PJa5HeoAIMYrvMVjfoJZLDH/Fp 60 | 2HyoM+CJzhRnoDl3iJHmoweXkCKI0HkYY/tvfu4rQn4j9efa6e5q+pZln+ErFUnI 61 | zS3Ohk8sKmd5v0EwFMz2DxR0nrbT0DvwG7joGSr91v3cS05lfZu/N34tNSIudJDO 62 | QTU9QaCZ25cfvz4YPEj72t/GTk65Z7gQ1aUTA8S3Zeeq8BRL003q/o9pz1AhYyfP 63 | nkxnFXs/O9rLF4BhHiUCAwEAAaMeMBwwGgYDVR0RBBMwEYIPd2ViLm5naW54LXBy 64 | b3h5MA0GCSqGSIb3DQEBCwUAA4IBAQAJMb7bTrC2aNquWxZRKfyfYbZaLzw172d2 65 | l7A0Tju01ogZT4Quc9PAOkxBVGy7Z4lnrSVV19SA/qc/PZ7xNJbY2lp4UcBj8VIp 66 | NVX0fXAc05Zif2SGgVInxMYQE8ZzVk0y0LPDyCwlg+QrHdR0MOWFry22pWv+XdM8 67 | AFiU9Gr1ph3P+e3VJ+0TJLJPK/O45K8MHf7gagFeokT/Ppb6bDmjUTfzclXYLSlu 68 | 3pW52OMeZaWcDXktOavHrBa2pXFLNaRsckcbcpxnWMH89n+nc1B71idXdKExOKcx 69 | 47nUyUUz7O0Wz8W90AOxRT9oDZFcJk43BXTtPnVeyl7u4lFL2giZ 70 | -----END CERTIFICATE----- 71 | -------------------------------------------------------------------------------- /test/test_headers/certs/web.nginx-proxy.tld.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Jan 13 03:06:39 2017 GMT 9 | Not After : May 31 03:06:39 2044 GMT 10 | Subject: CN=web.nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:95:56:c7:0d:48:a5:2b:3c:65:49:3f:26:e1:38: 16 | 2b:61:30:56:e4:92:d7:63:e0:eb:ad:ac:f9:33:9b: 17 | b2:31:f1:39:13:0b:e5:43:7b:c5:bd:8a:85:c8:d9: 18 | 3d:d8:ac:71:ba:16:e7:81:96:b2:ab:ae:c6:c0:bd: 19 | be:a7:d1:96:8f:b2:9b:df:ba:f9:4d:a1:3b:7e:21: 20 | 4a:cd:b6:45:f9:6d:79:50:bf:24:8f:c1:6b:c1:09: 21 | 19:5b:62:cb:96:e8:04:14:20:e8:d4:16:62:6a:f2: 22 | 37:c1:96:e2:9d:53:05:0b:52:1d:e7:68:92:db:8b: 23 | 36:68:cd:8d:5b:02:ff:12:f0:ac:5d:0c:c4:e0:7a: 24 | 55:a2:49:60:9f:ff:47:1f:52:73:55:4d:d4:f2:d1: 25 | 62:a2:f4:50:9d:c9:f6:f1:43:b3:dc:57:e1:31:76: 26 | b4:e0:a4:69:7e:f2:6d:34:ae:b9:8d:74:26:7b:d9: 27 | f6:07:00:ef:4b:36:61:b3:ef:7a:a1:36:3a:b6:d0: 28 | 9e:f8:b8:a9:0d:4c:30:a2:ed:eb:ab:6b:eb:2e:e2: 29 | 0b:28:be:f7:04:b1:e9:e0:84:d6:5d:31:77:7c:dc: 30 | d2:1f:d4:1d:71:6f:6f:6c:6d:1b:bf:31:e2:5b:c3: 31 | 52:d0:14:fc:8b:fb:45:ea:41:ec:ca:c7:3b:67:12: 32 | c4:df 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:web.nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 4e:48:7d:81:66:ba:2f:50:3d:24:42:61:3f:1f:de:cf:ec:1b: 39 | 1b:bd:0a:67:b6:62:c8:79:9d:31:a0:fd:a9:61:ce:ff:69:bf: 40 | 0e:f4:f7:e6:15:2b:b0:f0:e4:f2:f4:d2:8f:74:02:b1:1e:4a: 41 | a8:6f:26:0a:77:32:29:cf:dc:b5:61:82:3e:58:47:61:92:f0: 42 | 0c:20:25:f8:41:4d:34:09:44:bc:39:9e:aa:82:06:83:13:8b: 43 | 1e:2c:3d:cf:cd:1a:f7:77:39:38:e0:a3:a7:f3:09:da:02:8d: 44 | 73:75:38:b4:dd:24:a7:f9:03:db:98:c6:88:54:87:dc:e0:65: 45 | 4c:95:c5:39:9c:00:30:dc:f0:d3:2c:19:ca:f1:f4:6c:c6:d9: 46 | b5:c4:4a:c7:bc:a1:2e:88:7b:b5:33:d0:ff:fb:48:5e:3e:29: 47 | fa:58:e5:03:de:d8:17:de:ed:96:fc:7e:1f:fe:98:f6:be:99: 48 | 38:87:51:c0:d3:b7:9a:0f:26:92:e5:53:1b:d6:25:4c:ac:48: 49 | f3:29:fc:74:64:9d:07:6a:25:57:24:aa:a7:70:fa:8f:6c:a7: 50 | 2b:b7:9d:81:46:10:32:93:b9:45:6d:0f:16:18:b2:21:1f:f3: 51 | 30:24:62:3f:e1:6c:07:1d:71:28:cb:4c:bb:f5:39:05:f9:b2: 52 | 5b:a0:05:1b 53 | -----BEGIN CERTIFICATE----- 54 | MIIC+zCCAeOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAxMTMwMzA2MzlaFw00NDA1MzEwMzA2MzlaMB4xHDAaBgNVBAMME3dl 57 | Yi5uZ2lueC1wcm94eS50bGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 58 | AQCVVscNSKUrPGVJPybhOCthMFbkktdj4OutrPkzm7Ix8TkTC+VDe8W9ioXI2T3Y 59 | rHG6FueBlrKrrsbAvb6n0ZaPspvfuvlNoTt+IUrNtkX5bXlQvySPwWvBCRlbYsuW 60 | 6AQUIOjUFmJq8jfBluKdUwULUh3naJLbizZozY1bAv8S8KxdDMTgelWiSWCf/0cf 61 | UnNVTdTy0WKi9FCdyfbxQ7PcV+ExdrTgpGl+8m00rrmNdCZ72fYHAO9LNmGz73qh 62 | Njq20J74uKkNTDCi7eura+su4gsovvcEsenghNZdMXd83NIf1B1xb29sbRu/MeJb 63 | w1LQFPyL+0XqQezKxztnEsTfAgMBAAGjIjAgMB4GA1UdEQQXMBWCE3dlYi5uZ2lu 64 | eC1wcm94eS50bGQwDQYJKoZIhvcNAQELBQADggEBAE5IfYFmui9QPSRCYT8f3s/s 65 | Gxu9Cme2Ysh5nTGg/alhzv9pvw709+YVK7Dw5PL00o90ArEeSqhvJgp3MinP3LVh 66 | gj5YR2GS8AwgJfhBTTQJRLw5nqqCBoMTix4sPc/NGvd3OTjgo6fzCdoCjXN1OLTd 67 | JKf5A9uYxohUh9zgZUyVxTmcADDc8NMsGcrx9GzG2bXESse8oS6Ie7Uz0P/7SF4+ 68 | KfpY5QPe2Bfe7Zb8fh/+mPa+mTiHUcDTt5oPJpLlUxvWJUysSPMp/HRknQdqJVck 69 | qqdw+o9spyu3nYFGEDKTuUVtDxYYsiEf8zAkYj/hbAcdcSjLTLv1OQX5slugBRs= 70 | -----END CERTIFICATE----- 71 | -------------------------------------------------------------------------------- /test/test_ssl/certs/web2.nginx-proxy.tld.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Jan 10 00:37:02 2017 GMT 9 | Not After : May 28 00:37:02 2044 GMT 10 | Subject: CN=web2.nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:da:ee:46:2d:44:7c:f1:e6:91:11:bf:34:d6:02: 16 | 4e:fe:43:23:fb:6d:20:f7:8d:1b:c6:9c:cd:1c:1a: 17 | 07:95:c2:ed:b9:23:73:c1:02:2b:50:51:3f:33:cf: 18 | 8e:aa:f1:13:58:4c:ff:7f:7d:4a:87:fc:f0:0f:54: 19 | af:8c:eb:ba:b4:0f:71:5e:12:1f:64:b1:3d:83:88: 20 | dd:9c:09:50:2d:37:1d:03:3b:18:30:36:f4:82:94: 21 | 87:7f:31:27:28:84:0c:99:6d:77:b5:b8:4f:ca:83: 22 | 58:d5:d8:4e:36:73:1c:1a:5c:ed:05:ef:95:60:03: 23 | 28:0c:9f:d8:6f:98:a8:cd:08:be:af:b1:95:5a:60: 24 | 96:fe:2a:d0:98:74:9b:4a:c0:48:66:73:67:54:33: 25 | 11:22:20:ea:11:05:ba:a6:ed:74:12:05:d8:de:4f: 26 | 01:46:39:74:d8:34:1a:f2:2c:c2:df:6d:94:57:52: 27 | c1:e4:2e:1b:8e:12:0e:43:e7:6f:1f:da:51:80:35: 28 | c2:8a:9b:b6:2a:30:39:6b:a0:77:fa:37:11:b7:ec: 29 | de:6e:8c:6f:93:81:5e:2d:90:69:1b:4b:a4:80:ca: 30 | f4:e5:5b:c0:13:45:b9:76:70:ed:d3:4e:dd:66:98: 31 | 99:9f:9d:f0:1e:fd:dd:04:4f:9a:55:bc:38:ad:42: 32 | b9:dd 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:web2.nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 38:d6:8c:be:3c:5e:5d:61:02:77:eb:5b:6e:a7:1d:4f:69:0d: 39 | 54:bd:dd:3a:1a:8e:9d:a0:c2:f3:a5:31:91:3e:ec:7a:69:48: 40 | 40:27:45:a5:c6:b9:af:6d:d9:76:24:97:ec:c5:cf:4d:cc:49: 41 | 93:ab:bc:4f:01:7e:7a:57:73:4d:27:62:a6:68:bf:4c:00:2c: 42 | c0:f3:7b:b3:32:81:6b:96:20:0a:73:a0:85:b5:f8:07:10:88: 43 | e8:62:85:41:63:df:43:c5:f9:4b:90:89:6a:16:d9:a6:85:4a: 44 | 04:1b:5e:75:d8:84:ae:69:c7:62:8f:f1:53:c8:c6:31:71:6d: 45 | 0c:05:2d:bf:6e:b8:b8:7a:8c:73:6f:17:bb:5c:5a:67:51:12: 46 | af:e2:17:9c:40:0e:35:f6:59:93:69:45:14:74:33:c6:aa:04: 47 | 76:8e:3c:a6:ac:f4:60:cb:53:eb:d6:63:1a:47:7b:be:11:8d: 48 | 74:de:e8:e5:bc:de:1b:09:db:1b:87:89:b7:6a:20:0a:5e:fb: 49 | 35:04:ab:b2:f7:4d:a1:86:65:1d:c7:c3:aa:30:15:3a:6e:66: 50 | f8:43:33:63:ac:fc:c1:58:48:5b:ec:a0:00:bf:d4:f1:06:03: 51 | 79:ca:d5:b6:f2:39:0b:62:b4:fd:27:27:e9:37:52:21:ce:a4: 52 | 32:8a:bb:c7 53 | -----BEGIN CERTIFICATE----- 54 | MIIC/TCCAeWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAxMTAwMDM3MDJaFw00NDA1MjgwMDM3MDJaMB8xHTAbBgNVBAMMFHdl 57 | YjIubmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 58 | AQEA2u5GLUR88eaREb801gJO/kMj+20g940bxpzNHBoHlcLtuSNzwQIrUFE/M8+O 59 | qvETWEz/f31Kh/zwD1SvjOu6tA9xXhIfZLE9g4jdnAlQLTcdAzsYMDb0gpSHfzEn 60 | KIQMmW13tbhPyoNY1dhONnMcGlztBe+VYAMoDJ/Yb5iozQi+r7GVWmCW/irQmHSb 61 | SsBIZnNnVDMRIiDqEQW6pu10EgXY3k8BRjl02DQa8izC322UV1LB5C4bjhIOQ+dv 62 | H9pRgDXCipu2KjA5a6B3+jcRt+zeboxvk4FeLZBpG0ukgMr05VvAE0W5dnDt007d 63 | ZpiZn53wHv3dBE+aVbw4rUK53QIDAQABoyMwITAfBgNVHREEGDAWghR3ZWIyLm5n 64 | aW54LXByb3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAONaMvjxeXWECd+tbbqcd 65 | T2kNVL3dOhqOnaDC86UxkT7semlIQCdFpca5r23ZdiSX7MXPTcxJk6u8TwF+eldz 66 | TSdipmi/TAAswPN7szKBa5YgCnOghbX4BxCI6GKFQWPfQ8X5S5CJahbZpoVKBBte 67 | ddiErmnHYo/xU8jGMXFtDAUtv264uHqMc28Xu1xaZ1ESr+IXnEAONfZZk2lFFHQz 68 | xqoEdo48pqz0YMtT69ZjGkd7vhGNdN7o5bzeGwnbG4eJt2ogCl77NQSrsvdNoYZl 69 | HcfDqjAVOm5m+EMzY6z8wVhIW+ygAL/U8QYDecrVtvI5C2K0/Scn6TdSIc6kMoq7 70 | xw== 71 | -----END CERTIFICATE----- 72 | -------------------------------------------------------------------------------- /test/test_ssl/certs/web3.nginx-proxy.tld.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Jan 10 00:58:11 2017 GMT 9 | Not After : May 28 00:58:11 2044 GMT 10 | Subject: CN=web3.nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:ac:9b:90:dc:2c:0d:c3:ae:f4:a0:cc:40:15:d2: 16 | c4:c2:2c:8c:15:b6:70:28:cf:32:c4:03:ce:b3:87: 17 | 30:5d:a6:12:96:69:7a:fe:67:29:1c:8c:24:bb:6a: 18 | c8:86:13:19:91:00:3e:ef:00:67:50:b0:ea:c9:93: 19 | c6:8a:73:82:d8:37:9f:8e:6e:12:13:ec:fa:08:0f: 20 | ac:73:6e:42:96:67:9f:20:c5:1c:a3:b1:4a:83:36: 21 | 0e:0a:31:93:76:b1:b6:37:4f:e0:88:3c:46:dc:c1: 22 | 53:60:df:28:ae:3e:20:d8:d9:53:a2:be:09:38:f0: 23 | ff:4a:91:45:cb:cb:b5:b3:3d:7d:09:98:47:dc:0d: 24 | 5e:83:73:b6:c9:f3:fb:9a:f2:bb:b0:62:ca:aa:af: 25 | 6b:42:e5:08:b2:14:87:f4:fc:f1:14:f8:cc:76:b3: 26 | c0:49:df:66:c6:21:a0:bc:5e:0c:bb:de:e9:9c:e7: 27 | fb:31:a1:cf:c4:e9:bb:bd:c3:5a:0d:23:52:c6:b3: 28 | 84:77:f1:0c:3d:ca:c3:60:48:f9:7e:a6:dc:4f:f7: 29 | d2:5b:7c:02:4d:38:09:64:33:7e:bb:b1:65:bb:e2: 30 | 2b:1d:9a:49:d4:34:21:42:7a:49:3e:11:6c:10:66: 31 | b4:91:db:bd:3a:c2:8d:f4:e4:03:b1:b4:6e:5c:98: 32 | bf:7d 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:web3.nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 9a:f6:b9:c2:08:a4:b4:d7:e4:b2:d3:22:e9:fe:69:4a:e8:76: 39 | 18:60:11:1b:3b:7c:4b:c3:72:66:95:b7:7c:de:c7:34:32:58: 40 | aa:5d:e0:12:f0:df:27:b6:3f:dd:f1:8c:ed:ce:bd:73:50:fc: 41 | 9b:e1:8c:c2:7f:ac:6b:54:9d:23:0a:d9:a6:25:cc:99:94:73: 42 | 2b:69:e8:f7:07:40:37:d3:d4:0b:14:86:6a:a1:01:53:4b:ae: 43 | 85:2d:12:13:bd:23:1e:09:cf:20:50:24:76:a6:5f:b4:d6:d6: 44 | e1:34:40:93:4d:42:f7:d0:95:98:77:53:16:e7:ce:cc:4c:35: 45 | b8:30:3b:62:95:e2:40:0c:a1:73:84:92:93:63:df:c6:21:d5: 46 | eb:1d:a1:d0:f2:ec:a5:cf:d6:10:c9:8f:ac:11:16:39:cd:b0: 47 | 38:04:29:cf:e1:1c:dd:21:df:1f:95:35:a5:a4:61:2b:3d:8b: 48 | 8a:76:02:6d:31:a1:e8:6b:c5:3b:eb:90:40:34:16:5a:07:93: 49 | 96:56:cd:8b:52:ca:65:51:78:d3:f2:af:40:44:43:ec:fe:a2: 50 | c8:d4:6d:21:c7:1f:d2:45:28:0d:d2:51:0f:d1:a5:db:00:2a: 51 | 3a:10:88:9e:5a:76:a2:f7:e2:f0:fe:14:a3:e8:ec:ef:00:f0: 52 | 35:87:eb:7c 53 | -----BEGIN CERTIFICATE----- 54 | MIIC/TCCAeWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAxMTAwMDU4MTFaFw00NDA1MjgwMDU4MTFaMB8xHTAbBgNVBAMMFHdl 57 | YjMubmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC 58 | AQEArJuQ3CwNw670oMxAFdLEwiyMFbZwKM8yxAPOs4cwXaYSlml6/mcpHIwku2rI 59 | hhMZkQA+7wBnULDqyZPGinOC2Defjm4SE+z6CA+sc25ClmefIMUco7FKgzYOCjGT 60 | drG2N0/giDxG3MFTYN8orj4g2NlTor4JOPD/SpFFy8u1sz19CZhH3A1eg3O2yfP7 61 | mvK7sGLKqq9rQuUIshSH9PzxFPjMdrPASd9mxiGgvF4Mu97pnOf7MaHPxOm7vcNa 62 | DSNSxrOEd/EMPcrDYEj5fqbcT/fSW3wCTTgJZDN+u7Flu+IrHZpJ1DQhQnpJPhFs 63 | EGa0kdu9OsKN9OQDsbRuXJi/fQIDAQABoyMwITAfBgNVHREEGDAWghR3ZWIzLm5n 64 | aW54LXByb3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAmva5wgiktNfkstMi6f5p 65 | Suh2GGARGzt8S8NyZpW3fN7HNDJYql3gEvDfJ7Y/3fGM7c69c1D8m+GMwn+sa1Sd 66 | IwrZpiXMmZRzK2no9wdAN9PUCxSGaqEBU0uuhS0SE70jHgnPIFAkdqZftNbW4TRA 67 | k01C99CVmHdTFufOzEw1uDA7YpXiQAyhc4SSk2PfxiHV6x2h0PLspc/WEMmPrBEW 68 | Oc2wOAQpz+Ec3SHfH5U1paRhKz2LinYCbTGh6GvFO+uQQDQWWgeTllbNi1LKZVF4 69 | 0/KvQERD7P6iyNRtIccf0kUoDdJRD9Gl2wAqOhCInlp2ovfi8P4Uo+js7wDwNYfr 70 | fA== 71 | -----END CERTIFICATE----- 72 | -------------------------------------------------------------------------------- /test/test_ssl/wildcard_cert_and_nohttps/certs/web.nginx-proxy.tld.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: Mar 14 23:19:36 2017 GMT 9 | Not After : Jul 30 23:19:36 2044 GMT 10 | Subject: CN=*.web.nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:ce:2b:74:13:b2:1a:d5:72:5c:3e:10:f7:63:01: 16 | 22:df:e8:d9:cf:0b:8a:3f:40:75:62:58:78:27:9e: 17 | af:33:d2:a1:19:6a:e1:b7:57:db:d9:8f:05:70:c2: 18 | 35:5d:f1:44:0d:51:62:74:73:e5:77:d9:bb:c6:d0: 19 | 33:7a:43:88:e9:e6:3c:2d:d4:39:9d:61:34:5a:19: 20 | f3:c1:96:e0:bd:26:5b:69:18:a6:4c:8c:21:04:d8: 21 | fa:56:22:ec:55:0d:ba:49:4d:8e:27:69:7f:82:e9: 22 | e7:e9:c4:b7:87:70:d7:d7:4b:49:d1:c1:8c:b0:5a: 23 | 13:62:db:de:c1:94:31:d1:c9:74:c4:63:01:50:10: 24 | 70:42:73:67:c4:76:32:fb:d2:b7:91:2f:e8:cf:3a: 25 | 96:4a:ee:8e:0d:13:74:73:1b:e4:74:83:e7:66:d6: 26 | 8d:81:19:54:5b:d8:47:3e:3b:b5:fd:35:a2:df:f3: 27 | 7d:1c:9e:67:ee:50:da:28:9c:02:0a:ad:75:8d:04: 28 | f7:28:1f:04:89:13:ac:ed:a9:34:26:dc:f7:f9:1f: 29 | 72:21:d5:72:fb:09:d9:cb:40:c0:0d:36:3c:c0:77: 30 | 0e:9a:f7:41:f1:3b:dd:b6:05:ab:13:60:c5:fd:c6: 31 | 5f:f5:05:c4:42:00:ba:b5:ef:fb:dc:64:98:d9:4d: 32 | 2b:07 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:*.web.nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 9b:78:39:b3:90:8f:31:8c:7d:02:aa:6f:46:3d:8c:f5:93:86: 39 | 03:e2:d8:9b:73:d1:e7:70:f1:d6:e6:3c:41:41:8c:76:c9:29: 40 | a4:83:47:c7:10:fd:d0:8b:fa:60:26:a8:36:41:a4:69:89:81: 41 | ec:bf:fd:33:72:bb:83:ea:42:e4:59:3f:10:df:d1:de:e2:bb: 42 | eb:fa:97:44:fe:f4:55:29:69:ca:a5:88:b2:94:60:58:5a:1a: 43 | 19:16:fb:9f:42:4c:7c:d3:6b:21:45:22:56:5c:76:07:97:35: 44 | 27:8f:46:d2:77:5b:65:1b:94:99:cb:73:37:ae:cf:61:6c:7a: 45 | 5c:b3:3b:19:f2:9f:99:8f:89:eb:98:0b:74:0d:30:f5:49:19: 46 | d6:41:32:4e:c9:fc:59:2a:4a:53:2c:83:89:3d:e8:89:ed:37: 47 | d0:b4:f1:09:49:b5:0b:76:fd:a5:75:23:fb:01:c8:bb:59:02: 48 | 5c:e4:8e:9c:f9:5b:85:5f:67:fb:04:40:de:bc:e8:c3:15:2f: 49 | ba:00:5c:36:57:47:e3:1a:95:44:5f:f4:10:55:b0:c4:af:12: 50 | dc:0e:6c:18:4a:70:9e:73:90:8d:55:37:73:a5:1a:41:7f:00: 51 | 79:96:34:01:6b:10:2d:e9:61:3d:8f:8a:9a:c8:b6:bc:0f:57: 52 | 91:84:7c:26 53 | -----BEGIN CERTIFICATE----- 54 | MIIC/zCCAeegAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0xNzAzMTQyMzE5MzZaFw00NDA3MzAyMzE5MzZaMCAxHjAcBgNVBAMMFSou 57 | d2ViLm5naW54LXByb3h5LnRsZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 58 | ggEBAM4rdBOyGtVyXD4Q92MBIt/o2c8Lij9AdWJYeCeerzPSoRlq4bdX29mPBXDC 59 | NV3xRA1RYnRz5XfZu8bQM3pDiOnmPC3UOZ1hNFoZ88GW4L0mW2kYpkyMIQTY+lYi 60 | 7FUNuklNjidpf4Lp5+nEt4dw19dLSdHBjLBaE2Lb3sGUMdHJdMRjAVAQcEJzZ8R2 61 | MvvSt5Ev6M86lkrujg0TdHMb5HSD52bWjYEZVFvYRz47tf01ot/zfRyeZ+5Q2iic 62 | AgqtdY0E9ygfBIkTrO2pNCbc9/kfciHVcvsJ2ctAwA02PMB3Dpr3QfE73bYFqxNg 63 | xf3GX/UFxEIAurXv+9xkmNlNKwcCAwEAAaMkMCIwIAYDVR0RBBkwF4IVKi53ZWIu 64 | bmdpbngtcHJveHkudGxkMA0GCSqGSIb3DQEBCwUAA4IBAQCbeDmzkI8xjH0Cqm9G 65 | PYz1k4YD4tibc9HncPHW5jxBQYx2ySmkg0fHEP3Qi/pgJqg2QaRpiYHsv/0zcruD 66 | 6kLkWT8Q39He4rvr+pdE/vRVKWnKpYiylGBYWhoZFvufQkx802shRSJWXHYHlzUn 67 | j0bSd1tlG5SZy3M3rs9hbHpcszsZ8p+Zj4nrmAt0DTD1SRnWQTJOyfxZKkpTLIOJ 68 | PeiJ7TfQtPEJSbULdv2ldSP7Aci7WQJc5I6c+VuFX2f7BEDevOjDFS+6AFw2V0fj 69 | GpVEX/QQVbDErxLcDmwYSnCec5CNVTdzpRpBfwB5ljQBaxAt6WE9j4qayLa8D1eR 70 | hHwm 71 | -----END CERTIFICATE----- 72 | -------------------------------------------------------------------------------- /test/test_headers/test_http.py: -------------------------------------------------------------------------------- 1 | def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy): 2 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'}) 3 | assert r.status_code == 200 4 | assert "Foo: Bar\n" in r.text 5 | 6 | 7 | ##### Testing the handling of X-Forwarded-For ##### 8 | 9 | def test_X_Forwarded_For_is_generated(docker_compose, nginxproxy): 10 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 11 | assert r.status_code == 200 12 | assert "X-Forwarded-For:" in r.text 13 | 14 | def test_X_Forwarded_For_is_passed_on(docker_compose, nginxproxy): 15 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-For': '1.2.3.4'}) 16 | assert r.status_code == 200 17 | assert "X-Forwarded-For: 1.2.3.4, " in r.text 18 | 19 | 20 | ##### Testing the handling of X-Forwarded-Proto ##### 21 | 22 | def test_X_Forwarded_Proto_is_generated(docker_compose, nginxproxy): 23 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 24 | assert r.status_code == 200 25 | assert "X-Forwarded-Proto: http" in r.text 26 | 27 | def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy): 28 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Proto': 'f00'}) 29 | assert r.status_code == 200 30 | assert "X-Forwarded-Proto: f00\n" in r.text 31 | 32 | 33 | ##### Testing the handling of X-Forwarded-Port ##### 34 | 35 | def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy): 36 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 37 | assert r.status_code == 200 38 | assert "X-Forwarded-Port: 80\n" in r.text 39 | 40 | def test_X_Forwarded_Port_is_passed_on(docker_compose, nginxproxy): 41 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Port': '1234'}) 42 | assert r.status_code == 200 43 | assert "X-Forwarded-Port: 1234\n" in r.text 44 | 45 | 46 | ##### Testing the handling of X-Forwarded-Ssl ##### 47 | 48 | def test_X_Forwarded_Ssl_is_generated(docker_compose, nginxproxy): 49 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 50 | assert r.status_code == 200 51 | assert "X-Forwarded-Ssl: off\n" in r.text 52 | 53 | def test_X_Forwarded_Ssl_is_overwritten(docker_compose, nginxproxy): 54 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Ssl': 'f00'}) 55 | assert r.status_code == 200 56 | assert "X-Forwarded-Ssl: off\n" in r.text 57 | 58 | 59 | ##### Other headers 60 | 61 | def test_X_Real_IP_is_generated(docker_compose, nginxproxy): 62 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 63 | assert r.status_code == 200 64 | assert "X-Real-IP: " in r.text 65 | 66 | def test_Host_is_passed_on(docker_compose, nginxproxy): 67 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 68 | assert r.status_code == 200 69 | assert "Host: web.nginx-proxy.tld" in r.text 70 | 71 | def test_httpoxy_safe(docker_compose, nginxproxy): 72 | """ 73 | See https://httpoxy.org/ 74 | nginx-proxy should suppress the `Proxy` header 75 | """ 76 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Proxy': 'tcp://some.hacker.com'}) 77 | assert r.status_code == 200 78 | assert "Proxy:" not in r.text 79 | 80 | 81 | def test_no_host_server_tokens_off(docker_compose, nginxproxy): 82 | ip = nginxproxy.get_ip() 83 | r = nginxproxy.get(f"http://{ip}/headers") 84 | assert r.status_code == 503 85 | assert r.headers["Server"] == "nginx" 86 | 87 | 88 | def test_server_tokens_on(docker_compose, nginxproxy): 89 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers") 90 | assert r.status_code == 200 91 | assert "Host: web.nginx-proxy.tld" in r.text 92 | assert r.headers["Server"].startswith("nginx/") 93 | 94 | 95 | def test_server_tokens_off(docker_compose, nginxproxy): 96 | r = nginxproxy.get("http://web-server-tokens-off.nginx-proxy.tld/headers") 97 | assert r.status_code == 200 98 | assert "Host: web-server-tokens-off.nginx-proxy.tld" in r.text 99 | assert r.headers["Server"] == "nginx" 100 | -------------------------------------------------------------------------------- /test/test_headers/certs/web-server-tokens-off.nginx-proxy.tld.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4096 (0x1000) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld 7 | Validity 8 | Not Before: May 11 18:25:49 2021 GMT 9 | Not After : Sep 26 18:25:49 2048 GMT 10 | Subject: CN=web-server-tokens-off.nginx-proxy.tld 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | RSA Public-Key: (2048 bit) 14 | Modulus: 15 | 00:b4:fa:9d:8a:74:3f:17:ea:99:1c:45:71:18:90: 16 | eb:92:35:38:d7:90:21:81:0a:91:05:41:cf:b5:87: 17 | 34:bd:d8:7b:7f:7d:06:33:f8:94:67:8e:e4:07:54: 18 | 7f:b7:62:c5:76:6c:7f:7c:19:25:19:2c:36:9a:26: 19 | 54:8e:2d:97:02:78:31:c6:13:d3:ad:f3:31:62:e6: 20 | cf:96:ae:63:37:dd:bd:73:cb:4e:fb:3f:9b:65:67: 21 | 97:d8:5a:5d:0e:72:b1:11:ab:0e:d7:23:a9:b7:22: 22 | de:23:74:7e:88:7c:28:98:a9:6e:00:f4:be:8c:69: 23 | ea:3f:33:8b:19:97:da:1b:a6:65:b5:5a:92:01:3c: 24 | 3a:13:6b:00:02:e1:98:78:d3:da:ea:a6:9c:33:b0: 25 | 1d:9f:02:c4:f1:d0:d6:de:7a:f7:42:12:4b:31:fb: 26 | ed:e9:d7:d8:15:e8:4e:18:91:7c:9d:bf:0f:b0:12: 27 | d6:e2:80:8b:7a:ef:17:70:51:f4:3c:b7:43:cb:56: 28 | 61:af:61:7a:4e:9d:6c:5e:d8:27:0c:3b:d7:a4:1d: 29 | 2f:0d:a0:99:8f:b5:71:93:21:b4:87:be:b4:1c:77: 30 | a0:b9:cd:91:bd:9c:d0:b9:81:50:12:63:d2:0a:a9: 31 | 61:05:91:19:27:f7:ea:9d:8e:48:65:2e:1a:e7:fd: 32 | f1:b7 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | DNS:web-server-tokens-off.nginx-proxy.tld 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 5b:b7:74:ad:07:08:65:3c:8e:02:50:a9:b6:f4:8d:47:95:6f: 39 | e0:ba:5a:8c:ae:5c:32:88:8b:45:04:48:ce:3d:72:45:d7:7e: 40 | 1e:d7:75:17:30:98:90:21:4c:67:e2:57:1d:c9:fa:03:f4:81: 41 | 64:cf:d2:b3:85:71:be:53:b9:2a:fd:89:04:a6:b1:88:0a:0a: 42 | f1:5c:93:9b:fb:4f:86:0e:c5:4d:6a:ff:54:7b:07:f1:7e:d1: 43 | 8a:6b:fa:3b:f3:5c:d2:1b:2c:86:05:4c:e0:b4:04:0d:c7:db: 44 | 0b:89:b4:33:09:b6:1a:f0:cb:d4:ae:2c:05:63:a4:18:19:52: 45 | c7:15:21:ac:ae:9e:15:b9:b0:58:0c:96:df:7b:77:46:ef:59: 46 | a7:96:56:da:f6:f6:81:9f:10:7d:5a:48:68:0c:28:02:5d:7b: 47 | 69:4d:89:41:e2:88:6d:c6:22:45:6a:34:1b:ba:9b:6f:d6:2d: 48 | c2:55:b1:73:b4:bb:f5:06:d6:5f:ed:01:d1:3c:51:8b:e2:6c: 49 | 31:d7:6b:a5:bd:05:e3:9a:97:15:40:bf:bb:8f:81:e5:bf:bc: 50 | 06:66:47:84:fe:f7:06:fb:5d:35:9e:04:26:0d:aa:3d:b5:92: 51 | 6b:90:c2:1c:17:ac:c1:95:d9:6b:f1:5d:0a:09:9f:a7:a6:ca: 52 | 3b:45:a4:59 53 | -----BEGIN CERTIFICATE----- 54 | MIIDHzCCAgegAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp 55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs 56 | ZDAeFw0yMTA1MTExODI1NDlaFw00ODA5MjYxODI1NDlaMDAxLjAsBgNVBAMMJXdl 57 | Yi1zZXJ2ZXItdG9rZW5zLW9mZi5uZ2lueC1wcm94eS50bGQwggEiMA0GCSqGSIb3 58 | DQEBAQUAA4IBDwAwggEKAoIBAQC0+p2KdD8X6pkcRXEYkOuSNTjXkCGBCpEFQc+1 59 | hzS92Ht/fQYz+JRnjuQHVH+3YsV2bH98GSUZLDaaJlSOLZcCeDHGE9Ot8zFi5s+W 60 | rmM33b1zy077P5tlZ5fYWl0OcrERqw7XI6m3It4jdH6IfCiYqW4A9L6Maeo/M4sZ 61 | l9obpmW1WpIBPDoTawAC4Zh409rqppwzsB2fAsTx0NbeevdCEksx++3p19gV6E4Y 62 | kXydvw+wEtbigIt67xdwUfQ8t0PLVmGvYXpOnWxe2CcMO9ekHS8NoJmPtXGTIbSH 63 | vrQcd6C5zZG9nNC5gVASY9IKqWEFkRkn9+qdjkhlLhrn/fG3AgMBAAGjNDAyMDAG 64 | A1UdEQQpMCeCJXdlYi1zZXJ2ZXItdG9rZW5zLW9mZi5uZ2lueC1wcm94eS50bGQw 65 | DQYJKoZIhvcNAQELBQADggEBAFu3dK0HCGU8jgJQqbb0jUeVb+C6WoyuXDKIi0UE 66 | SM49ckXXfh7XdRcwmJAhTGfiVx3J+gP0gWTP0rOFcb5TuSr9iQSmsYgKCvFck5v7 67 | T4YOxU1q/1R7B/F+0Ypr+jvzXNIbLIYFTOC0BA3H2wuJtDMJthrwy9SuLAVjpBgZ 68 | UscVIayunhW5sFgMlt97d0bvWaeWVtr29oGfEH1aSGgMKAJde2lNiUHiiG3GIkVq 69 | NBu6m2/WLcJVsXO0u/UG1l/tAdE8UYvibDHXa6W9BeOalxVAv7uPgeW/vAZmR4T+ 70 | 9wb7XTWeBCYNqj21kmuQwhwXrMGV2WvxXQoJn6emyjtFpFk= 71 | -----END CERTIFICATE----- 72 | -------------------------------------------------------------------------------- /test/test_headers/test_https.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy): 5 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'}) 6 | assert r.status_code == 200 7 | assert "Foo: Bar\n" in r.text 8 | 9 | 10 | ##### Testing the handling of X-Forwarded-For ##### 11 | 12 | def test_X_Forwarded_For_is_generated(docker_compose, nginxproxy): 13 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 14 | assert r.status_code == 200 15 | assert "X-Forwarded-For:" in r.text 16 | 17 | def test_X_Forwarded_For_is_passed_on(docker_compose, nginxproxy): 18 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-For': '1.2.3.4'}) 19 | assert r.status_code == 200 20 | assert "X-Forwarded-For: 1.2.3.4, " in r.text 21 | 22 | 23 | ##### Testing the handling of X-Forwarded-Proto ##### 24 | 25 | def test_X_Forwarded_Proto_is_generated(docker_compose, nginxproxy): 26 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 27 | assert r.status_code == 200 28 | assert "X-Forwarded-Proto: https" in r.text 29 | 30 | def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy): 31 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Proto': 'f00'}) 32 | assert r.status_code == 200 33 | assert "X-Forwarded-Proto: f00\n" in r.text 34 | 35 | 36 | ##### Testing the handling of X-Forwarded-Port ##### 37 | 38 | def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy): 39 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 40 | assert r.status_code == 200 41 | assert "X-Forwarded-Port: 443\n" in r.text 42 | 43 | def test_X_Forwarded_Port_is_passed_on(docker_compose, nginxproxy): 44 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Port': '1234'}) 45 | assert r.status_code == 200 46 | assert "X-Forwarded-Port: 1234\n" in r.text 47 | 48 | 49 | ##### Testing the handling of X-Forwarded-Ssl ##### 50 | 51 | def test_X_Forwarded_Ssl_is_generated(docker_compose, nginxproxy): 52 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 53 | assert r.status_code == 200 54 | assert "X-Forwarded-Ssl: on\n" in r.text 55 | 56 | def test_X_Forwarded_Ssl_is_overwritten(docker_compose, nginxproxy): 57 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Ssl': 'f00'}) 58 | assert r.status_code == 200 59 | assert "X-Forwarded-Ssl: on\n" in r.text 60 | 61 | 62 | ##### Other headers 63 | 64 | def test_X_Real_IP_is_generated(docker_compose, nginxproxy): 65 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 66 | assert r.status_code == 200 67 | assert "X-Real-IP: " in r.text 68 | 69 | def test_Host_is_passed_on(docker_compose, nginxproxy): 70 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 71 | assert r.status_code == 200 72 | assert "Host: web.nginx-proxy.tld" in r.text 73 | 74 | def test_httpoxy_safe(docker_compose, nginxproxy): 75 | """ 76 | See https://httpoxy.org/ 77 | nginx-proxy should suppress the `Proxy` header 78 | """ 79 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'Proxy': 'tcp://some.hacker.com'}) 80 | assert r.status_code == 200 81 | assert "Proxy:" not in r.text 82 | 83 | 84 | @pytest.mark.filterwarnings('ignore::urllib3.exceptions.InsecureRequestWarning') 85 | def test_no_host_server_tokens_off(docker_compose, nginxproxy): 86 | ip = nginxproxy.get_ip() 87 | r = nginxproxy.get(f"https://{ip}/headers", verify=False) 88 | assert r.status_code == 503 89 | assert r.headers["Server"] == "nginx" 90 | 91 | 92 | def test_server_tokens_on(docker_compose, nginxproxy): 93 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers") 94 | assert r.status_code == 200 95 | assert "Host: web.nginx-proxy.tld" in r.text 96 | assert r.headers["Server"].startswith("nginx/") 97 | 98 | 99 | def test_server_tokens_off(docker_compose, nginxproxy): 100 | r = nginxproxy.get("https://web-server-tokens-off.nginx-proxy.tld/headers") 101 | assert r.status_code == 200 102 | assert "Host: web-server-tokens-off.nginx-proxy.tld" in r.text 103 | assert r.headers["Server"] == "nginx" 104 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | Nginx proxy test suite 2 | ====================== 3 | 4 | Install requirements 5 | -------------------- 6 | 7 | You need [python 3.9](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installing/) installed. Then run the commands: 8 | 9 | pip install -r requirements/python-requirements.txt 10 | 11 | 12 | 13 | Prepare the nginx-proxy test image 14 | ---------------------------------- 15 | 16 | make build-nginx-proxy-test-debian 17 | 18 | or if you want to test the alpine flavor: 19 | 20 | make build-nginx-proxy-test-alpine 21 | 22 | Run the test suite 23 | ------------------ 24 | 25 | pytest 26 | 27 | need more verbosity ? 28 | 29 | pytest -s 30 | 31 | 32 | Run one single test module 33 | -------------------------- 34 | 35 | pytest test_nominal.py 36 | 37 | 38 | Write a test module 39 | ------------------- 40 | 41 | This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.py](conftest.py) file will be automatically loaded by pytest and will provide you with two useful pytest [fixtures](https://docs.pytest.org/en/latest/explanation/fixtures.html): 42 | 43 | - docker_compose 44 | - nginxproxy 45 | 46 | 47 | ### docker_compose fixture 48 | 49 | When using the `docker_compose` fixture in a test, pytest will try to find a yml file named after your test module filename. For instance, if your test module is `test_example.py`, then the `docker_compose` fixture will try to load a `test_example.yml` [docker compose file](https://docs.docker.com/compose/compose-file/). 50 | 51 | Once the docker compose file found, the fixture will remove all containers, run `docker-compose up`, and finally your test will be executed. 52 | 53 | The fixture will run the _docker-compose_ command with the `-f` option to load the given compose file. So you can test your docker compose file syntax by running it yourself with: 54 | 55 | docker-compose -f test_example.yml up -d 56 | 57 | In the case you are running pytest from within a docker container, the `docker_compose` fixture will make sure the container running pytest is attached to all docker networks. That way, your test will be able to reach any of them. 58 | 59 | In your tests, you can use the `docker_compose` variable to query and command the docker daemon as it provides you with a [client from the docker python module](https://docker-py.readthedocs.io/en/4.4.4/client.html#client-reference). 60 | 61 | Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways: 62 | 63 | Any domain name containing the substring `nginx-proxy` will resolve to the IP address of the container that was created from the `nginxproxy/nginx-proxy:test` image. So all the following domain names will resolve to the nginx-proxy container in tests: 64 | - `nginx-proxy` 65 | - `nginx-proxy.com` 66 | - `www.nginx-proxy.com` 67 | - `www.nginx-proxy.test` 68 | - `www.nginx-proxy` 69 | - `whatever.nginx-proxyooooooo` 70 | - ... 71 | 72 | Any domain name ending with `XXX.container.docker` will resolve to the IP address of the XXX container. 73 | - `web1.container.docker` will resolve to the IP address of the `web1` container 74 | - `f00.web1.container.docker` will resolve to the IP address of the `web1` container 75 | - `anything.whatever.web2.container.docker` will resolve to the IP address of the `web2` container 76 | 77 | Otherwise, domain names are resoved as usual using your system DNS resolver. 78 | 79 | 80 | ### nginxproxy fixture 81 | 82 | The `nginxproxy` fixture will provide you with a replacement for the python [requests](https://pypi.python.org/pypi/requests/) module. This replacement will just repeat up to 30 times a requests if it receives the HTTP error 404 or 502. This error occurs when you try to send queries to nginx-proxy too early after the container creation. 83 | 84 | Also this requests replacement is preconfigured to use the Certificate Authority root certificate [certs/ca-root.crt](certs/) to validate https connections. 85 | 86 | Furthermore, the nginxproxy methods accept an additional keyword parameter: `ipv6` which forces requests made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not supported by the system or docker, that particular test will be skipped. 87 | 88 | def test_forwards_to_web1_ipv6(docker_compose, nginxproxy): 89 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True) 90 | assert r.status_code == 200 91 | assert r.text == "answer from port 81\n" 92 | 93 | 94 | 95 | ### The web docker image 96 | 97 | When you run the `make build-webserver` command, you built a [`web`](requirements/README.md) docker image which is convenient for running a small web server in a container. This image can produce containers that listens on multiple ports at the same time. 98 | 99 | ### Testing TLS 100 | 101 | If you need to create server certificates, use the [`certs/create_server_certificate.sh`](certs/) script. Pytest will be able to validate any certificate issued from this script. 102 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub.yml: -------------------------------------------------------------------------------- 1 | name: DockerHub 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 * * 1' 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - '*.*.*' 12 | paths-ignore: 13 | - 'test/*' 14 | - '.gitignore' 15 | - 'docker-compose-separate-containers.yml' 16 | - 'docker-compose.yml' 17 | - 'LICENSE' 18 | - 'Makefile' 19 | - '*.md' 20 | 21 | jobs: 22 | multiarch-build-debian: 23 | runs-on: ubuntu-latest 24 | steps: 25 | 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Retrieve version 32 | run: echo "GIT_DESCRIBE=$(git describe --tags)" >> $GITHUB_ENV 33 | 34 | - name: Get Docker tags for Debian based image 35 | id: docker_meta_debian 36 | uses: docker/metadata-action@v3 37 | with: 38 | images: | 39 | ghcr.io/nginx-proxy/nginx-proxy 40 | nginxproxy/nginx-proxy 41 | jwilder/nginx-proxy 42 | tags: | 43 | type=semver,pattern={{version}} 44 | type=semver,pattern={{major}}.{{minor}} 45 | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} 46 | labels: | 47 | org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder 48 | org.opencontainers.image.version=${{ env.GIT_DESCRIBE }} 49 | 50 | - name: Set up QEMU 51 | uses: docker/setup-qemu-action@v1 52 | 53 | - name: Set up Docker Buildx 54 | uses: docker/setup-buildx-action@v1 55 | 56 | - name: Login to DockerHub 57 | uses: docker/login-action@v1 58 | with: 59 | username: ${{ secrets.DOCKERHUB_USERNAME }} 60 | password: ${{ secrets.DOCKERHUB_TOKEN }} 61 | 62 | - name: Log in to GitHub Container Registry 63 | uses: docker/login-action@v1 64 | with: 65 | registry: ghcr.io 66 | username: ${{ github.actor }} 67 | password: ${{ secrets.GITHUB_TOKEN }} 68 | 69 | - name: Build and push the Debian based image 70 | id: docker_build_debian 71 | uses: docker/build-push-action@v2 72 | with: 73 | context: . 74 | file: Dockerfile 75 | build-args: NGINX_PROXY_VERSION=${{ env.GIT_DESCRIBE }} 76 | platforms: linux/amd64,linux/arm64,linux/arm/v7 77 | push: true 78 | tags: ${{ steps.docker_meta_debian.outputs.tags }} 79 | labels: ${{ steps.docker_meta_debian.outputs.labels }} 80 | 81 | - name: Images digests 82 | run: echo ${{ steps.docker_build_debian.outputs.digest }} 83 | 84 | multiarch-build-alpine: 85 | runs-on: ubuntu-latest 86 | steps: 87 | 88 | - name: Checkout 89 | uses: actions/checkout@v2 90 | with: 91 | fetch-depth: 0 92 | 93 | - name: Retrieve version 94 | run: echo "GIT_DESCRIBE=$(git describe --tags)" >> $GITHUB_ENV 95 | 96 | - name: Get Docker tags for Alpine based image 97 | id: docker_meta_alpine 98 | uses: docker/metadata-action@v3 99 | with: 100 | images: | 101 | ghcr.io/nginx-proxy/nginx-proxy 102 | nginxproxy/nginx-proxy 103 | jwilder/nginx-proxy 104 | tags: | 105 | type=semver,suffix=-alpine,pattern={{version}} 106 | type=semver,suffix=-alpine,pattern={{major}}.{{minor}} 107 | type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' }} 108 | labels: | 109 | org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder 110 | org.opencontainers.image.version=${{ env.GIT_DESCRIBE }} 111 | flavor: latest=false 112 | 113 | - name: Set up QEMU 114 | uses: docker/setup-qemu-action@v1 115 | 116 | - name: Set up Docker Buildx 117 | uses: docker/setup-buildx-action@v1 118 | 119 | - name: Login to DockerHub 120 | uses: docker/login-action@v1 121 | with: 122 | username: ${{ secrets.DOCKERHUB_USERNAME }} 123 | password: ${{ secrets.DOCKERHUB_TOKEN }} 124 | 125 | - name: Log in to GitHub Container Registry 126 | uses: docker/login-action@v1 127 | with: 128 | registry: ghcr.io 129 | username: ${{ github.actor }} 130 | password: ${{ secrets.GITHUB_TOKEN }} 131 | 132 | - name: Build and push the Alpine based image 133 | id: docker_build_alpine 134 | uses: docker/build-push-action@v2 135 | with: 136 | context: . 137 | file: Dockerfile.alpine 138 | build-args: NGINX_PROXY_VERSION=${{ env.GIT_DESCRIBE }} 139 | platforms: linux/amd64,linux/arm64,linux/arm/v7 140 | push: true 141 | tags: ${{ steps.docker_meta_alpine.outputs.tags }} 142 | labels: ${{ steps.docker_meta_alpine.outputs.labels }} 143 | 144 | - name: Images digests 145 | run: echo ${{ steps.docker_build_alpine.outputs.digest }} 146 | -------------------------------------------------------------------------------- /test/certs/create_server_certificate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 4 | 5 | if [[ "$#" -eq 0 ]]; then 6 | cat <<-EOF 7 | 8 | To generate a server certificate, provide the domain name as a parameter: 9 | $(basename $0) www.my-domain.tdl 10 | $(basename $0) www.my-domain.tdl alternate.domain.tld 11 | 12 | You can also create certificates for wildcard domains: 13 | $(basename $0) '*.my-domain.tdl' 14 | 15 | EOF 16 | exit 0 17 | else 18 | DOMAIN="$1" 19 | ALTERNATE_DOMAINS="DNS:$( echo "$@" | sed 's/ /,DNS:/g')" 20 | fi 21 | 22 | 23 | ############################################################################### 24 | # Create a nginx container (which conveniently provides the `openssl` command) 25 | ############################################################################### 26 | 27 | CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.19.10) 28 | # Configure openssl 29 | docker exec $CONTAINER bash -c ' 30 | mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null 31 | echo 1000 > /ca/serial 32 | touch /ca/index.txt 33 | cat > /ca/openssl.cnf <<-"OESCRIPT" 34 | [ ca ] 35 | # `man ca` 36 | default_ca = CA_default 37 | 38 | [ CA_default ] 39 | # Directory and file locations. 40 | dir = /ca 41 | certs = $dir/certs 42 | crl_dir = $dir/crl 43 | new_certs_dir = $dir/newcerts 44 | database = $dir/index.txt 45 | serial = $dir/serial 46 | RANDFILE = $dir/private/.rand 47 | 48 | # The root key and root certificate. 49 | private_key = /work/ca-root.key 50 | certificate = /work/ca-root.crt 51 | 52 | # SHA-1 is deprecated, so use SHA-2 instead. 53 | default_md = sha256 54 | 55 | name_opt = ca_default 56 | cert_opt = ca_default 57 | default_days = 10000 58 | preserve = no 59 | policy = policy_loose 60 | 61 | [ policy_loose ] 62 | countryName = optional 63 | stateOrProvinceName = optional 64 | localityName = optional 65 | organizationName = optional 66 | organizationalUnitName = optional 67 | commonName = supplied 68 | emailAddress = optional 69 | 70 | [ req ] 71 | # Options for the `req` tool (`man req`). 72 | default_bits = 2048 73 | distinguished_name = req_distinguished_name 74 | string_mask = utf8only 75 | 76 | # SHA-1 is deprecated, so use SHA-2 instead. 77 | default_md = sha256 78 | 79 | # Extension to add when the -x509 option is used. 80 | x509_extensions = v3_ca 81 | 82 | [ req_distinguished_name ] 83 | # See . 84 | countryName = Country Name (2 letter code) 85 | stateOrProvinceName = State or Province Name 86 | localityName = Locality Name 87 | 0.organizationName = Organization Name 88 | organizationalUnitName = Organizational Unit Name 89 | commonName = Common Name 90 | emailAddress = Email Address 91 | 92 | [ v3_ca ] 93 | # Extensions for a typical CA (`man x509v3_config`). 94 | subjectKeyIdentifier = hash 95 | authorityKeyIdentifier = keyid:always,issuer 96 | basicConstraints = critical, CA:true 97 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign 98 | 99 | [ server_cert ] 100 | # Extensions for server certificates (`man x509v3_config`). 101 | basicConstraints = CA:FALSE 102 | nsCertType = server 103 | nsComment = server certificate generated for test purpose (nginx-proxy test suite) 104 | subjectKeyIdentifier = hash 105 | authorityKeyIdentifier = keyid,issuer:always 106 | keyUsage = critical, digitalSignature, keyEncipherment 107 | extendedKeyUsage = serverAuth 108 | 109 | [ san_env ] 110 | subjectAltName=${ENV::SAN} 111 | OESCRIPT 112 | ' 113 | 114 | # shortcut for calling `openssl` inside the container 115 | function openssl { 116 | docker exec $CONTAINER openssl "$@" 117 | } 118 | 119 | function exitfail { 120 | echo 121 | echo ERROR: "$@" 122 | docker rm -f $CONTAINER 123 | exit 1 124 | } 125 | 126 | 127 | ############################################################################### 128 | # Setup Certificate authority 129 | ############################################################################### 130 | 131 | if ! [[ -f "$DIR/ca-root.key" ]]; then 132 | echo 133 | echo "> Create a Certificate Authority root key: $DIR/ca-root.key" 134 | openssl genrsa -out ca-root.key 2048 135 | [[ $? -eq 0 ]] || exitfail failed to generate CA root key 136 | fi 137 | 138 | # Create a CA root certificate 139 | if ! [[ -f "$DIR/ca-root.crt" ]]; then 140 | echo 141 | echo "> Create a CA root certificate: $DIR/ca-root.crt" 142 | openssl req -config /ca/openssl.cnf \ 143 | -key ca-root.key \ 144 | -new -x509 -days 3650 -subj "/O=nginx-proxy test suite/CN=www.nginx-proxy.tld" -extensions v3_ca \ 145 | -out ca-root.crt 146 | [[ $? -eq 0 ]] || exitfail failed to generate CA root certificate 147 | 148 | # Verify certificate 149 | openssl x509 -noout -text -in ca-root.crt 150 | fi 151 | 152 | 153 | ############################################################################### 154 | # create server key and certificate signed by the certificate authority 155 | ############################################################################### 156 | 157 | echo 158 | echo "> Create a host key: $DIR/$DOMAIN.key" 159 | openssl genrsa -out "$DOMAIN.key" 2048 160 | 161 | echo 162 | echo "> Create a host certificate signing request" 163 | 164 | SAN="$ALTERNATE_DOMAINS" openssl req -config /ca/openssl.cnf \ 165 | -key "$DOMAIN.key" \ 166 | -new -out "/ca/$DOMAIN.csr" -days 1000 -extensions san_env -subj "/CN=$DOMAIN" 167 | [[ $? -eq 0 ]] || exitfail failed to generate server certificate signing request 168 | 169 | echo 170 | echo "> Create server certificate: $DIR/$DOMAIN.crt" 171 | SAN="$ALTERNATE_DOMAINS" openssl ca -config /ca/openssl.cnf -batch \ 172 | -extensions server_cert \ 173 | -extensions san_env \ 174 | -in "/ca/$DOMAIN.csr" \ 175 | -out "$DOMAIN.crt" 176 | [[ $? -eq 0 ]] || exitfail failed to generate server certificate 177 | 178 | 179 | # Verify host certificate 180 | #openssl x509 -noout -text -in "$DOMAIN.crt" 181 | 182 | 183 | docker rm -f $CONTAINER >/dev/null 184 | -------------------------------------------------------------------------------- /test/test_ssl/test_dhparam.py: -------------------------------------------------------------------------------- 1 | import re 2 | import subprocess 3 | import os 4 | 5 | import backoff 6 | import docker 7 | import pytest 8 | 9 | docker_client = docker.from_env() 10 | 11 | 12 | ############################################################################### 13 | # 14 | # Tests helpers 15 | # 16 | ############################################################################### 17 | 18 | @backoff.on_exception(backoff.constant, AssertionError, interval=2, max_tries=15, jitter=None) 19 | def assert_log_contains(expected_log_line, container_name="nginxproxy"): 20 | """ 21 | Check that the nginx-proxy container log contains a given string. 22 | The backoff decorator will retry the check 15 times with a 2 seconds delay. 23 | 24 | :param expected_log_line: string to search for 25 | :return: None 26 | :raises: AssertError if the expected string is not found in the log 27 | """ 28 | sut_container = docker_client.containers.get(container_name) 29 | docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False) 30 | assert bytes(expected_log_line, encoding="utf8") in docker_logs 31 | 32 | 33 | def require_openssl(required_version): 34 | """ 35 | This function checks that the required version of OpenSSL is present, and skips the test if not. 36 | Use it as a test function decorator: 37 | 38 | @require_openssl("2.3.4") 39 | def test_something(): 40 | ... 41 | 42 | :param required_version: minimal required version as a string: "1.2.3" 43 | """ 44 | 45 | def versiontuple(v): 46 | clean_v = re.sub(r"[^\d\.]", "", v) 47 | return tuple(map(int, (clean_v.split(".")))) 48 | 49 | try: 50 | command_output = subprocess.check_output(["openssl", "version"]) 51 | except OSError: 52 | return pytest.mark.skip("openssl command is not available in test environment") 53 | else: 54 | if not command_output: 55 | raise Exception("Could not get openssl version") 56 | openssl_version = str(command_output.split()[1]) 57 | return pytest.mark.skipif( 58 | versiontuple(openssl_version) < versiontuple(required_version), 59 | reason=f"openssl v{openssl_version} is less than required version {required_version}") 60 | 61 | 62 | @require_openssl("1.0.2") 63 | def negotiate_cipher(sut_container, additional_params='', grep='Cipher is'): 64 | host = f"{sut_container.attrs['NetworkSettings']['IPAddress']}:443" 65 | 66 | try: 67 | # Enforce TLS 1.2 as newer versions don't support custom dhparam or ciphersuite preference. 68 | # The empty `echo` is to provide `openssl` user input, so that the process exits: https://stackoverflow.com/a/28567565 69 | # `shell=True` enables using a single string to execute as a shell command. 70 | # `text=True` prevents the need to compare against byte strings. 71 | # `stderr=subprocess.PIPE` removes the output to stderr being interleaved with test case status (output during exceptions). 72 | return subprocess.check_output( 73 | f"echo '' | openssl s_client -connect {host} -tls1_2 {additional_params} | grep '{grep}'", 74 | shell=True, 75 | text=True, 76 | stderr=subprocess.PIPE, 77 | ) 78 | except subprocess.CalledProcessError as e: 79 | # Output a more helpful error, the original exception in this case isn't that helpful. 80 | # `from None` to ignore undesired output from exception chaining. 81 | raise Exception("Failed to process CLI request:\n" + e.stderr) from None 82 | 83 | 84 | # The default `dh_bits` can vary due to configuration. 85 | # `additional_params` allows for adjusting the request to a specific `VIRTUAL_HOST`, 86 | # where DH size can differ from the configured global default DH size. 87 | def can_negotiate_dhe_ciphersuite(sut_container, dh_bits=4096, additional_params=''): 88 | openssl_params = f"-cipher 'EDH' {additional_params}" 89 | 90 | r = negotiate_cipher(sut_container, openssl_params) 91 | assert "New, TLSv1.2, Cipher is DHE-RSA-AES256-GCM-SHA384\n" == r 92 | 93 | r2 = negotiate_cipher(sut_container, openssl_params, "Server Temp Key") 94 | assert f"Server Temp Key: DH, {dh_bits} bits" in r2 95 | 96 | 97 | def cannot_negotiate_dhe_ciphersuite(sut_container): 98 | # Fail to negotiate a DHE cipher suite: 99 | r = negotiate_cipher(sut_container, "-cipher 'EDH'") 100 | assert "New, (NONE), Cipher is (NONE)\n" == r 101 | 102 | # Correctly establish a connection (TLS 1.2): 103 | r2 = negotiate_cipher(sut_container) 104 | assert "New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384\n" == r2 105 | 106 | r3 = negotiate_cipher(sut_container, grep="Server Temp Key") 107 | assert "X25519" in r3 108 | 109 | 110 | # To verify self-signed certificates, the file path to their CA cert must be provided. 111 | # Use the `fqdn` arg to specify the `VIRTUAL_HOST` to request for verification for that cert. 112 | # 113 | # Resolves the following stderr warnings regarding self-signed cert verification and missing SNI: 114 | # `Can't use SSL_get_servername` 115 | # `verify error:num=20:unable to get local issuer certificate` 116 | # `verify error:num=21:unable to verify the first certificate` 117 | # 118 | # The stderr output is hidden due to running the openssl command with `stderr=subprocess.PIPE`. 119 | def can_verify_chain_of_trust(sut_container, ca_cert, fqdn): 120 | openssl_params = f"-CAfile '{ca_cert}' -servername '{fqdn}'" 121 | 122 | r = negotiate_cipher(sut_container, openssl_params, "Verify return code") 123 | assert "Verify return code: 0 (ok)" in r 124 | 125 | 126 | def should_be_equivalent_content(sut_container, expected, actual): 127 | expected_checksum = sut_container.exec_run(f"md5sum {expected}").output.split()[0] 128 | actual_checksum = sut_container.exec_run(f"md5sum {actual}").output.split()[0] 129 | 130 | assert expected_checksum == actual_checksum 131 | 132 | 133 | # Parse array of container ENV, splitting at the `=` and returning the value, otherwise `None` 134 | def get_env(sut_container, var): 135 | env = sut_container.attrs['Config']['Env'] 136 | 137 | for e in env: 138 | if e.startswith(var): 139 | return e.split('=')[1] 140 | 141 | return None 142 | 143 | 144 | ############################################################################### 145 | # 146 | # Tests 147 | # 148 | ############################################################################### 149 | 150 | def test_default_dhparam_is_ffdhe4096(docker_compose): 151 | container_name="dh-default" 152 | sut_container = docker_client.containers.get(container_name) 153 | assert sut_container.status == "running" 154 | 155 | assert_log_contains("Setting up DH Parameters..", container_name) 156 | 157 | # `dhparam.pem` contents should match the default (ffdhe4096.pem): 158 | should_be_equivalent_content( 159 | sut_container, 160 | "/app/dhparam/ffdhe4096.pem", 161 | "/etc/nginx/dhparam/dhparam.pem" 162 | ) 163 | 164 | can_negotiate_dhe_ciphersuite(sut_container, 4096) 165 | 166 | 167 | # Overrides default DH group via ENV `DHPARAM_BITS=3072`: 168 | def test_can_change_dhparam_group(docker_compose): 169 | container_name="dh-env" 170 | sut_container = docker_client.containers.get(container_name) 171 | assert sut_container.status == "running" 172 | 173 | assert_log_contains("Setting up DH Parameters..", container_name) 174 | 175 | # `dhparam.pem` contents should not match the default (ffdhe4096.pem): 176 | should_be_equivalent_content( 177 | sut_container, 178 | "/app/dhparam/ffdhe3072.pem", 179 | "/etc/nginx/dhparam/dhparam.pem" 180 | ) 181 | 182 | can_negotiate_dhe_ciphersuite(sut_container, 3072) 183 | 184 | 185 | def test_fail_if_dhparam_group_not_supported(docker_compose): 186 | container_name="invalid-group-1024" 187 | sut_container = docker_client.containers.get(container_name) 188 | assert sut_container.status == "exited" 189 | 190 | DHPARAM_BITS = get_env(sut_container, "DHPARAM_BITS") 191 | assert DHPARAM_BITS == "1024" 192 | 193 | assert_log_contains( 194 | f"ERROR: Unsupported DHPARAM_BITS size: {DHPARAM_BITS}. Use: 2048, 3072, or 4096 (default).", 195 | container_name 196 | ) 197 | 198 | 199 | # Overrides default DH group by providing a custom `/etc/nginx/dhparam/dhparam.pem`: 200 | def test_custom_dhparam_is_supported(docker_compose): 201 | container_name="dh-file" 202 | sut_container = docker_client.containers.get(container_name) 203 | assert sut_container.status == "running" 204 | 205 | assert_log_contains( 206 | "Warning: A custom dhparam.pem file was provided. Best practice is to use standardized RFC7919 DHE groups instead.", 207 | container_name 208 | ) 209 | 210 | # `dhparam.pem` contents should not match the default (ffdhe4096.pem): 211 | should_be_equivalent_content( 212 | sut_container, 213 | "/app/dhparam/ffdhe3072.pem", 214 | "/etc/nginx/dhparam/dhparam.pem" 215 | ) 216 | 217 | can_negotiate_dhe_ciphersuite(sut_container, 3072) 218 | 219 | 220 | # Only `web2` has a site-specific DH param file (which overrides all other DH config) 221 | # Other tests here use `web5` explicitly, or implicitly (via ENV `DEFAULT_HOST`, otherwise first HTTPS server) 222 | def test_custom_dhparam_is_supported_per_site(docker_compose): 223 | container_name="dh-file" 224 | sut_container = docker_client.containers.get(container_name) 225 | assert sut_container.status == "running" 226 | 227 | # A site specific `dhparam.pem` with DH group size of 2048-bit. 228 | # DH group size should not match the: 229 | # - 4096-bit default. 230 | # - 3072-bit default, overriden by file. 231 | should_be_equivalent_content( 232 | sut_container, 233 | "/app/dhparam/ffdhe2048.pem", 234 | "/etc/nginx/certs/web2.nginx-proxy.tld.dhparam.pem" 235 | ) 236 | 237 | # `-servername` required for nginx-proxy to respond with site-specific DH params used: 238 | can_negotiate_dhe_ciphersuite(sut_container, 2048, '-servername web2.nginx-proxy.tld') 239 | 240 | # --Unrelated to DH support-- 241 | # - `web5` is missing a certificate, but falls back to available `/etc/nginx/certs/nginx-proxy.tld.crt` via `nginx.tmpl` "closest" result. 242 | # - `web2` has it's own cert provisioned at `/etc/nginx/certs/web2.nginx-proxy.tld.crt`. 243 | can_verify_chain_of_trust( 244 | sut_container, 245 | ca_cert = f"{os.getcwd()}/certs/ca-root.crt", 246 | fqdn = 'web2.nginx-proxy.tld' 247 | ) 248 | 249 | 250 | # NOTE: These two tests will fail without the ENV `DEFAULT_HOST` to prevent 251 | # accidentally falling back to `web2` as the default server, which has explicit DH params configured. 252 | # Only copying DH params is skipped, not explicit usage via user providing custom files. 253 | def test_can_skip_dhparam(docker_compose): 254 | container_name="dh-skip" 255 | sut_container = docker_client.containers.get(container_name) 256 | assert sut_container.status == "running" 257 | 258 | assert_log_contains("Skipping Diffie-Hellman parameters setup.", container_name) 259 | 260 | cannot_negotiate_dhe_ciphersuite(sut_container) 261 | 262 | 263 | def test_can_skip_dhparam_backward_compatibility(docker_compose): 264 | container_name="dh-skip-backward" 265 | sut_container = docker_client.containers.get(container_name) 266 | assert sut_container.status == "running" 267 | 268 | assert_log_contains("Warning: The DHPARAM_GENERATION environment variable is deprecated, please consider using DHPARAM_SKIP set to true instead.", container_name) 269 | assert_log_contains("Skipping Diffie-Hellman parameters setup.", container_name) 270 | 271 | cannot_negotiate_dhe_ciphersuite(sut_container) 272 | 273 | 274 | def test_web5_https_works(docker_compose, nginxproxy): 275 | r = nginxproxy.get("https://web5.nginx-proxy.tld/port", allow_redirects=False) 276 | assert r.status_code == 200 277 | assert "answer from port 85\n" in r.text 278 | --------------------------------------------------------------------------------