Compare commits

..

1 commit
main ... 2424

Author SHA1 Message Date
Nicolas Duchon
f1f395c10b
feat: execute upstream nginx entrypoint 2024-05-04 20:47:07 +02:00
265 changed files with 1412 additions and 3701 deletions

View file

@ -1,85 +0,0 @@
name: Build and publish Docker images on demand
on:
workflow_dispatch:
inputs:
image_tag:
description: "Image tag"
type: string
required: true
jobs:
multiarch-build:
name: Build and publish ${{ matrix.base }} image with tag ${{ inputs.image_tag }}
strategy:
matrix:
base: [alpine, debian]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Retrieve nginx-proxy version
id: nginx-proxy_version
run: echo "VERSION=$(git describe --tags)" >> "$GITHUB_OUTPUT"
- name: Retrieve docker-gen version
id: docker-gen_version
run: sed -n -e 's;^FROM nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT"
- name: Get Docker tags
id: docker_meta
uses: docker/metadata-action@v5
with:
images: |
nginxproxy/nginx-proxy
tags: |
type=raw,value=${{ inputs.image_tag }},enable=${{ matrix.base == 'debian' }}
type=raw,value=${{ inputs.image_tag }},suffix=-alpine,enable=${{ matrix.base == 'alpine' }}
labels: |
org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
org.opencontainers.image.version=${{ steps.nginx-proxy_version.outputs.VERSION }}
flavor: |
latest=false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push the image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile.${{ matrix.base }}
build-args: |
NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}
platforms: linux/amd64,linux/arm64,linux/s390x,linux/arm/v7
sbom: true
push: true
provenance: mode=max
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Images digests
run: echo ${{ steps.docker_build.outputs.digest }}

View file

@ -81,7 +81,7 @@ jobs:
- name: Build and push the image - name: Build and push the image
id: docker_build id: docker_build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: Dockerfile.${{ matrix.base }} file: Dockerfile.${{ matrix.base }}

View file

@ -25,10 +25,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python 3.12 - name: Set up Python 3.9
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: 3.12 python-version: 3.9
- name: Install dependencies - name: Install dependencies
run: | run: |
@ -36,9 +36,6 @@ jobs:
pip install -r python-requirements.txt pip install -r python-requirements.txt
working-directory: test/requirements working-directory: test/requirements
- name: Pull nginx:alpine image
run: docker pull nginx:alpine
- name: Build Docker web server image - name: Build Docker web server image
run: make build-webserver run: make build-webserver

1
.gitignore vendored
View file

@ -1,4 +1,3 @@
**/__pycache__/ **/__pycache__/
**/.cache/ **/.cache/
.idea/ .idea/
wip

View file

@ -1,9 +1,9 @@
FROM docker.io/nginxproxy/docker-gen:0.14.5 AS docker-gen FROM nginxproxy/docker-gen:0.12.1 AS docker-gen
FROM docker.io/nginxproxy/forego:0.18.2 AS forego FROM nginxproxy/forego:0.18.1 AS forego
# Build the final image # Build the final image
FROM docker.io/library/nginx:1.27.3-alpine FROM nginx:1.26.0-alpine
ARG NGINX_PROXY_VERSION ARG NGINX_PROXY_VERSION
# Add DOCKER_GEN_VERSION environment variable because # Add DOCKER_GEN_VERSION environment variable because
@ -17,13 +17,10 @@ ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
RUN apk add --no-cache --virtual .run-deps bash openssl RUN apk add --no-cache --virtual .run-deps bash openssl
# Configure Nginx # Configure Nginx
RUN echo -e "\ninclude /etc/nginx/toplevel.conf.d/*.conf;" >> /etc/nginx/nginx.conf \ RUN sed -i 's/worker_connections.*;$/worker_connections 10240;/' /etc/nginx/nginx.conf \
&& sed -i 's/worker_connections.*;$/worker_connections 10240;/' /etc/nginx/nginx.conf \
&& sed -i -e '/^\}$/{s//\}\nworker_rlimit_nofile 20480;/;:a' -e '$!N;$!ba' -e '}' /etc/nginx/nginx.conf \ && sed -i -e '/^\}$/{s//\}\nworker_rlimit_nofile 20480;/;:a' -e '$!N;$!ba' -e '}' /etc/nginx/nginx.conf \
&& mkdir -p '/etc/nginx/toplevel.conf.d' \
&& mkdir -p '/etc/nginx/dhparam' \ && mkdir -p '/etc/nginx/dhparam' \
&& mkdir -p '/etc/nginx/certs' \ && mkdir -p '/etc/nginx/certs'
&& mkdir -p '/usr/share/nginx/html/errors'
# Install Forego + docker-gen # Install Forego + docker-gen
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego

View file

@ -1,9 +1,9 @@
FROM docker.io/nginxproxy/docker-gen:0.14.5-debian AS docker-gen FROM nginxproxy/docker-gen:0.12.1-debian AS docker-gen
FROM docker.io/nginxproxy/forego:0.18.2-debian AS forego FROM nginxproxy/forego:0.18.1-debian AS forego
# Build the final image # Build the final image
FROM docker.io/library/nginx:1.27.3 FROM nginx:1.26.0
ARG NGINX_PROXY_VERSION ARG NGINX_PROXY_VERSION
# Add DOCKER_GEN_VERSION environment variable because # Add DOCKER_GEN_VERSION environment variable because
@ -14,13 +14,10 @@ ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
DOCKER_HOST=unix:///tmp/docker.sock DOCKER_HOST=unix:///tmp/docker.sock
# Configure Nginx # Configure Nginx
RUN echo "\ninclude /etc/nginx/toplevel.conf.d/*.conf;" >> /etc/nginx/nginx.conf \ RUN sed -i 's/worker_connections.*;$/worker_connections 10240;/' /etc/nginx/nginx.conf \
&& sed -i 's/worker_connections.*;$/worker_connections 10240;/' /etc/nginx/nginx.conf \
&& sed -i -e '/^\}$/{s//\}\nworker_rlimit_nofile 20480;/;:a' -e '$!N;$!ba' -e '}' /etc/nginx/nginx.conf \ && sed -i -e '/^\}$/{s//\}\nworker_rlimit_nofile 20480;/;:a' -e '$!N;$!ba' -e '}' /etc/nginx/nginx.conf \
&& mkdir -p '/etc/nginx/toplevel.conf.d' \
&& mkdir -p '/etc/nginx/dhparam' \ && mkdir -p '/etc/nginx/dhparam' \
&& mkdir -p '/etc/nginx/certs' \ && mkdir -p '/etc/nginx/certs'
&& mkdir -p '/usr/share/nginx/html/errors'
# Install Forego + docker-gen # Install Forego + docker-gen
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego

View file

@ -1,6 +1,6 @@
[![Test](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml/badge.svg)](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml) [![Test](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml/badge.svg)](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml)
[![GitHub release](https://img.shields.io/github/v/release/nginx-proxy/nginx-proxy)](https://github.com/nginx-proxy/nginx-proxy/releases) [![GitHub release](https://img.shields.io/github/v/release/nginx-proxy/nginx-proxy)](https://github.com/nginx-proxy/nginx-proxy/releases)
[![nginx 1.27.3](https://img.shields.io/badge/nginx-1.27.3-brightgreen.svg?logo=nginx)](https://nginx.org/en/CHANGES) ![nginx 1.26.0](https://img.shields.io/badge/nginx-1.26.0-brightgreen.svg)
[![Docker Image Size](https://img.shields.io/docker/image-size/nginxproxy/nginx-proxy?sort=semver)](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub") [![Docker Image Size](https://img.shields.io/docker/image-size/nginxproxy/nginx-proxy?sort=semver)](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub")
[![Docker stars](https://img.shields.io/docker/stars/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub") [![Docker stars](https://img.shields.io/docker/stars/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub")
[![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub") [![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub")
@ -18,19 +18,9 @@ docker run --detach \
--name nginx-proxy \ --name nginx-proxy \
--publish 80:80 \ --publish 80:80 \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \ --volume /var/run/docker.sock:/tmp/docker.sock:ro \
nginxproxy/nginx-proxy:1.6 nginxproxy/nginx-proxy:1.5
```
docker-compose
```docker-compose
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
restart: always
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/tmp/docker.sock"
``` ```
Then start any containers (here an nginx container) you want proxied with an env var `VIRTUAL_HOST=subdomain.yourdomain.com` Then start any containers (here an nginx container) you want proxied with an env var `VIRTUAL_HOST=subdomain.yourdomain.com`
```console ```console
@ -39,12 +29,7 @@ docker run --detach \
--env VIRTUAL_HOST=foo.bar.com \ --env VIRTUAL_HOST=foo.bar.com \
nginx nginx
``` ```
docker-compose
```docker-compose
environment:
- VIRTUAL_HOST=git.patachina.casacam.net
- VIRTUAL_PORT=3000
```
Provided your DNS is setup to resolve `foo.bar.com` to the host running nginx-proxy, a request to `http://foo.bar.com` will then be routed to a container with the `VIRTUAL_HOST` env var set to `foo.bar.com` (in this case, the **your-proxied-app** container). Provided your DNS is setup to resolve `foo.bar.com` to the host running nginx-proxy, a request to `http://foo.bar.com` will then be routed to a container with the `VIRTUAL_HOST` env var set to `foo.bar.com` (in this case, the **your-proxied-app** container).
The containers being proxied must : The containers being proxied must :
@ -63,7 +48,7 @@ The nginx-proxy images are available in two flavors.
This image is based on the nginx:mainline image, itself based on the debian slim image. This image is based on the nginx:mainline image, itself based on the debian slim image.
```console ```console
docker pull nginxproxy/nginx-proxy:1.6 docker pull nginxproxy/nginx-proxy:1.5
``` ```
#### Alpine based version (`-alpine` suffix) #### Alpine based version (`-alpine` suffix)
@ -71,22 +56,15 @@ docker pull nginxproxy/nginx-proxy:1.6
This image is based on the nginx:alpine image. This image is based on the nginx:alpine image.
```console ```console
docker pull nginxproxy/nginx-proxy:1.6-alpine docker pull nginxproxy/nginx-proxy:1.5-alpine
``` ```
> [!IMPORTANT] #### :warning: a note on `latest` and `alpine`:
>
> #### A note on `latest` and `alpine`: It is not recommended to use the `latest` (`nginxproxy/nginx-proxy`, `nginxproxy/nginx-proxy:latest`) or `alpine` (`nginxproxy/nginx-proxy:alpine`) tag for production setups.
>
> It is not recommended to use the `latest` (`nginxproxy/nginx-proxy`, `nginxproxy/nginx-proxy:latest`) or `alpine` (`nginxproxy/nginx-proxy:alpine`) tag for production setups. [Those tags point](https://hub.docker.com/r/nginxproxy/nginx-proxy/tags) to the latest commit in the `main` branch. They do not carry any promise of stability, and using them will probably put your nginx-proxy setup at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes). You should always specify the version you want to use explicitly to ensure your setup doesn't break when the image is updated.
>
> [Those tags point](https://hub.docker.com/r/nginxproxy/nginx-proxy/tags) to the latest commit in the `main` branch. They do not carry any promise of stability, and using them will probably put your nginx-proxy setup at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes). You should always specify the version you want to use explicitly to ensure your setup doesn't break when the image is updated.
### Additional documentation ### Additional documentation
Please check the [docs section](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs). Please check the [docs section](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs).
### Powered by
[![GoLand logo](https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand_icon.svg)](https://www.jetbrains.com/go/)
[![PyCharm logo](https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm_icon.svg)](https://www.jetbrains.com/pycharm/)

View file

@ -110,12 +110,17 @@ if [[ $* == 'forego start -r' ]]; then
_setup_dhparam _setup_dhparam
if [ -z "${TRUST_DOWNSTREAM_PROXY}" ]; then if [[ -z "${TRUST_DOWNSTREAM_PROXY}" ]]; then
cat >&2 <<-EOT cat >&2 <<-EOT
Warning: TRUST_DOWNSTREAM_PROXY is not set; defaulting to "true". For security, you should explicitly set TRUST_DOWNSTREAM_PROXY to "false" if there is not a trusted reverse proxy in front of this proxy. Warning: TRUST_DOWNSTREAM_PROXY is not set; defaulting to "true". For security, you should explicitly set TRUST_DOWNSTREAM_PROXY to "false" if there is not a trusted reverse proxy in front of this proxy.
Warning: The default value of TRUST_DOWNSTREAM_PROXY might change to "false" in a future version of nginx-proxy. If you require TRUST_DOWNSTREAM_PROXY to be enabled, explicitly set it to "true". Warning: The default value of TRUST_DOWNSTREAM_PROXY might change to "false" in a future version of nginx-proxy. If you require TRUST_DOWNSTREAM_PROXY to be enabled, explicitly set it to "true".
EOT EOT
fi fi
# Execute upstream nginx entrypoint if present
if [[ -f /docker-entrypoint.sh ]]; then
/docker-entrypoint.sh nginx -v
fi
fi fi
exec "$@" exec "$@"

View file

@ -1,5 +1,4 @@
volumes: version: "2"
nginx_conf:
services: services:
nginx: nginx:
@ -8,15 +7,17 @@ services:
ports: ports:
- "80:80" - "80:80"
volumes: volumes:
- nginx_conf:/etc/nginx/conf.d:ro - /etc/nginx/conf.d
dockergen: dockergen:
image: nginxproxy/docker-gen image: nginxproxy/docker-gen
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl
/etc/nginx/conf.d/default.conf
volumes_from:
- nginx
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
- nginx_conf:/etc/nginx/conf.d
whoami: whoami:
image: jwilder/whoami image: jwilder/whoami

View file

@ -1,3 +1,5 @@
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy image: nginxproxy/nginx-proxy

View file

@ -11,7 +11,6 @@
- [HTTP/2 and HTTP/3](#http2-and-http3) - [HTTP/2 and HTTP/3](#http2-and-http3)
- [Headers](#headers) - [Headers](#headers)
- [Custom Nginx Configuration](#custom-nginx-configuration) - [Custom Nginx Configuration](#custom-nginx-configuration)
- [TCP and UDP stream](#tcp-and-udp-stream)
- [Unhashed vs SHA1 upstream names](#unhashed-vs-sha1-upstream-names) - [Unhashed vs SHA1 upstream names](#unhashed-vs-sha1-upstream-names)
- [Separate Containers](#separate-containers) - [Separate Containers](#separate-containers)
- [Docker Compose](#docker-compose) - [Docker Compose](#docker-compose)
@ -54,110 +53,6 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie
1. From the container's exposed port if there is only one 1. From the container's exposed port if there is only one
1. From the default port 80 when none of the above methods apply 1. From the default port 80 when none of the above methods apply
### Multiple ports
If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PROTO`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container.
The YAML syntax should be easier to write on Docker compose files, while the JSON syntax can be used for CLI invocation.
The expected format is the following:
```yaml
hostname:
path:
port: int
proto: string
dest: string
```
For each hostname entry, `path`, `port`, `proto` and `dest` are optional and are assigned default values when missing:
- `path` = "/"
- `port` = default port
- `proto` = "http"
- `dest` = ""
#### Multiple ports routed to different hostnames
The following example use an hypothetical container running services over HTTP on port 80, 8000 and 9000:
```yaml
services:
multiport-container:
image: somerepo/somecontainer
container_name: multiport-container
environment:
VIRTUAL_HOST_MULTIPORTS: |-
www.example.org:
service1.example.org:
"/":
port: 8000
service2.example.org:
"/":
port: 9000
# There is no path dict specified for www.example.org, so it get the default values:
# www.example.org:
# "/":
# port: 80 (default port)
# dest: ""
# JSON equivalent:
# VIRTUAL_HOST_MULTIPORTS: |-
# {
# "www.example.org": {},
# "service1.example.org": { "/": { "port": 8000, "dest": "" } },
# "service2.example.org": { "/": { "port": 9000, "dest": "" } }
# }
```
This would result in the following proxy config:
- `www.example.org` -> `multiport-container:80` over `HTTP`
- `service1.example.org` -> `multiport-container:8000` over `HTTP`
- `service2.example.org` -> `multiport-container:9000` over `HTTP`
#### Multiple ports routed to same hostname and different paths
The following example use an hypothetical container running services over HTTP on port 80 and 8000 and over HTTPS on port 9443:
```yaml
services:
multiport-container:
image: somerepo/somecontainer
container_name: multiport-container
environment:
VIRTUAL_HOST_MULTIPORTS: |-
www.example.org:
"/":
"/service1":
port: 8000
dest: "/"
"/service2":
port: 9443
proto: "https"
dest: "/"
# port and dest are not specified on the / path, so this path is routed to the
# default port with the default dest value (empty string) and default proto (http)
# JSON equivalent:
# VIRTUAL_HOST_MULTIPORTS: |-
# {
# "www.example.org": {
# "/": {},
# "/service1": { "port": 8000, "dest": "/" },
# "/service2": { "port": 9443, "proto": "https", "dest": "/" }
# }
# }
```
This would result in the following proxy config:
- `www.example.org` -> `multiport-container:80` over `HTTP`
- `www.example.org/service1` -> `multiport-container:8000` over `HTTP`
- `www.example.org/service2` -> `multiport-container:9443` over `HTTPS`
⬆️ [back to table of contents](#table-of-contents) ⬆️ [back to table of contents](#table-of-contents)
## Path-based Routing ## Path-based Routing
@ -167,8 +62,7 @@ It is also possible to specify multiple paths with regex locations like `VIRTUAL
The full request URI will be forwarded to the serving container in the `X-Original-URI` header. The full request URI will be forwarded to the serving container in the `X-Original-URI` header.
> [!NOTE] **NOTE**: Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or having an option to prepend this path. The application does not need to expect this path in the request.
> Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or having an option to prepend this path. The application does not need to expect this path in the request.
### VIRTUAL_DEST ### VIRTUAL_DEST
@ -187,9 +81,8 @@ In this example, the incoming request `http://example.tld/app1/foo` will be prox
### Per-VIRTUAL_PATH location configuration ### Per-VIRTUAL_PATH location configuration
The same options as from [Per-VIRTUAL_HOST location configuration](#Per-VIRTUAL_HOST-location-configuration) are available on a `VIRTUAL_PATH` basis. The same options as from [Per-VIRTUAL_HOST location configuration](#Per-VIRTUAL_HOST-location-configuration) are available on a `VIRTUAL_PATH` basis.
The only difference is that the filename gets an additional block `HASH=$(echo -n $VIRTUAL_PATH | sha1sum | awk '{ print $1 }')`. This is the sha1-hash of the `VIRTUAL_PATH` (no newline). This is done for filename sanitization purposes. The only difference is that the filename gets an additional block `HASH=$(echo -n $VIRTUAL_PATH | sha1sum | awk '{ print $1 }')`. This is the sha1-hash of the `VIRTUAL_PATH` (no newline). This is done filename sanitization purposes.
The used filename is `${VIRTUAL_HOST}_${HASH}_location`
The used filename is `${VIRTUAL_HOST}_${PATH_HASH}_location`, or when `VIRTUAL_HOST` is a regex, `${VIRTUAL_HOST_HASH}_${PATH_HASH}_location`.
The filename of the previous example would be `example.tld_8610f6c344b4096614eab6e09d58885349f42faf_location`. The filename of the previous example would be `example.tld_8610f6c344b4096614eab6e09d58885349f42faf_location`.
@ -248,7 +141,7 @@ Proxyed containers running in host network mode **must** use the [`VIRTUAL_PORT`
If you allow traffic from the public internet to access your `nginx-proxy` container, you may want to restrict some containers to the internal network only, so they cannot be accessed from the public internet. On containers that should be restricted to the internal network, you should set the environment variable `NETWORK_ACCESS=internal`. By default, the _internal_ network is defined as `127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16`. To change the list of networks considered internal, mount a file on the `nginx-proxy` at `/etc/nginx/network_internal.conf` with these contents, edited to suit your needs: If you allow traffic from the public internet to access your `nginx-proxy` container, you may want to restrict some containers to the internal network only, so they cannot be accessed from the public internet. On containers that should be restricted to the internal network, you should set the environment variable `NETWORK_ACCESS=internal`. By default, the _internal_ network is defined as `127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16`. To change the list of networks considered internal, mount a file on the `nginx-proxy` at `/etc/nginx/network_internal.conf` with these contents, edited to suit your needs:
```nginx ```Nginx
# These networks are considered "internal" # These networks are considered "internal"
allow 127.0.0.0/8; allow 127.0.0.0/8;
allow 10.0.0.0/8; allow 10.0.0.0/8;
@ -261,7 +154,6 @@ deny all;
When internal-only access is enabled, external clients will be denied with an `HTTP 403 Forbidden` When internal-only access is enabled, external clients will be denied with an `HTTP 403 Forbidden`
> [!NOTE]
> If there is a load-balancer / reverse proxy in front of `nginx-proxy` that hides the client IP (example: AWS Application/Elastic Load Balancer), you will need to use the nginx `realip` module (already installed) to extract the client's IP from the HTTP request headers. Please see the [nginx realip module configuration](http://nginx.org/en/docs/http/ngx_http_realip_module.html) for more details. This configuration can be added to a new config file and mounted in `/etc/nginx/conf.d/`. > If there is a load-balancer / reverse proxy in front of `nginx-proxy` that hides the client IP (example: AWS Application/Elastic Load Balancer), you will need to use the nginx `realip` module (already installed) to extract the client's IP from the HTTP request headers. Please see the [nginx realip module configuration](http://nginx.org/en/docs/http/ngx_http_realip_module.html) for more details. This configuration can be added to a new config file and mounted in `/etc/nginx/conf.d/`.
⬆️ [back to table of contents](#table-of-contents) ⬆️ [back to table of contents](#table-of-contents)
@ -272,8 +164,7 @@ When internal-only access is enabled, external clients will be denied with an `H
If you would like the reverse proxy to connect to your backend using HTTPS instead of HTTP, set `VIRTUAL_PROTO=https` on the backend container. If you would like the reverse proxy to connect to your backend using HTTPS instead of HTTP, set `VIRTUAL_PROTO=https` on the backend container.
> [!NOTE] > Note: If you use `VIRTUAL_PROTO=https` and your backend container exposes port 80 and 443, `nginx-proxy` will use HTTPS on port 80. This is almost certainly not what you want, so you should also include `VIRTUAL_PORT=443`.
> If you use `VIRTUAL_PROTO=https` and your backend container exposes port 80 and 443, `nginx-proxy` will use HTTPS on port 80. This is almost certainly not what you want, so you should also include `VIRTUAL_PORT=443`.
### uWSGI Upstream ### uWSGI Upstream
@ -289,9 +180,12 @@ If you use fastcgi,you can set `VIRTUAL_ROOT=xxx` for your root directory
### Upstream Server HTTP Load Balancing Support ### Upstream Server HTTP Load Balancing Support
> **Warning**
> This feature is experimental. The behavior may change (or the feature may be removed entirely) without warning in a future release, even if the release is not a new major version. If you use this feature, or if you would like to use this feature but you require changes to it first, please [provide feedback in #2195](https://github.com/nginx-proxy/nginx-proxy/discussions/2195). Once we have collected enough feedback we will promote this feature to officially supported.
If you have multiple containers with the same `VIRTUAL_HOST` and `VIRTUAL_PATH` settings, nginx will spread the load across all of them. To change the load balancing algorithm from nginx's default (round-robin), set the `com.github.nginx-proxy.nginx-proxy.loadbalance` label on one or more of your application containers to the desired load balancing directive. See the [`ngx_http_upstream_module` documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html) for available directives. If you have multiple containers with the same `VIRTUAL_HOST` and `VIRTUAL_PATH` settings, nginx will spread the load across all of them. To change the load balancing algorithm from nginx's default (round-robin), set the `com.github.nginx-proxy.nginx-proxy.loadbalance` label on one or more of your application containers to the desired load balancing directive. See the [`ngx_http_upstream_module` documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html) for available directives.
> [!NOTE] > **Note**
> >
> - Don't forget the terminating semicolon (`;`). > - Don't forget the terminating semicolon (`;`).
> - If you are using Docker Compose, remember to escape any dollar sign (`$`) characters (`$` becomes `$$`). > - If you are using Docker Compose, remember to escape any dollar sign (`$`) characters (`$` becomes `$$`).
@ -308,7 +202,6 @@ services:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
environment: environment:
HTTPS_METHOD: nohttps HTTPS_METHOD: nohttps
myapp: myapp:
image: jwilder/whoami image: jwilder/whoami
expose: expose:
@ -324,7 +217,10 @@ services:
### Upstream Server HTTP Keep-Alive Support ### Upstream Server HTTP Keep-Alive Support
By default `nginx-proxy` will enable HTTP keep-alive between itself and backend server(s) and set the maximum number of idle connections to twice the number of servers listed in the corresponding `upstream{}` block, [per nginx recommendation](https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives). To manually set the maximum number of idle connections or disable HTTP keep-alive entirely, use the `com.github.nginx-proxy.nginx-proxy.keepalive` label on the server's container (setting it to `disabled` will disable HTTP keep-alive). > **Warning**
> This feature is experimental. The behavior may change (or the feature may be removed entirely) without warning in a future release, even if the release is not a new major version. If you use this feature, or if you would like to use this feature but you require changes to it first, please [provide feedback in #2194](https://github.com/nginx-proxy/nginx-proxy/discussions/2194). Once we have collected enough feedback we will promote this feature to officially supported.
To enable HTTP keep-alive between `nginx-proxy` and backend server(s), set the `com.github.nginx-proxy.nginx-proxy.keepalive` label on the server's container either to `auto` or to the desired maximum number of idle connections. The `auto` setting will dynamically set the maximum number of idle connections to twice the number of servers listed in the corresponding `upstream{}` block, [per nginx recommendation](https://www.nginx.com/blog/avoiding-top-10-nginx-configuration-mistakes/#no-keepalives).
See the [nginx keepalive documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) and the [Docker label documentation](https://docs.docker.com/config/labels-custom-metadata/) for details. See the [nginx keepalive documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) and the [Docker label documentation](https://docs.docker.com/config/labels-custom-metadata/) for details.
@ -332,7 +228,7 @@ See the [nginx keepalive documentation](https://nginx.org/en/docs/http/ngx_http_
## Basic Authentication Support ## Basic Authentication Support
In order to be able to secure your virtual host, you have to create a file named as its equivalent `VIRTUAL_HOST` variable (or if using a regex `VIRTUAL_HOST`, as the sha1 hash of the regex) in directory In order to be able to secure your virtual host, you have to create a file named as its equivalent `VIRTUAL_HOST` variable in directory
`/etc/nginx/htpasswd/{$VIRTUAL_HOST}` `/etc/nginx/htpasswd/{$VIRTUAL_HOST}`
```console ```console
@ -409,7 +305,7 @@ docker run --detach \
## SSL Support ## SSL Support
SSL is supported using single host, wildcard and SAN certificates using naming conventions for certificates or optionally [specifying a cert name as an environment variable](#san-certificates). SSL is supported using single host, wildcard and SNI certificates using naming conventions for certificates or optionally specifying a cert name (for SNI) as an environment variable.
To enable SSL: To enable SSL:
@ -425,20 +321,13 @@ If you are running the container in a virtualized environment (Hyper-V, VirtualB
[acme-companion](https://github.com/nginx-proxy/acme-companion) is a lightweight companion container for the nginx-proxy. It allows the automated creation/renewal of SSL certificates using the ACME protocol. [acme-companion](https://github.com/nginx-proxy/acme-companion) is a lightweight companion container for the nginx-proxy. It allows the automated creation/renewal of SSL certificates using the ACME protocol.
By default nginx-proxy generates location blocks to handle ACME HTTP Challenge. This behavior can be changed with environment variable `ACME_HTTP_CHALLENGE_LOCATION`. It accepts these values:
- `true`: default behavior, handle ACME HTTP Challenge in all cases.
- `false`: do not handle ACME HTTP Challenge at all.
- `legacy`: legacy behavior for compatibility with older (<= `2.3`) versions of acme-companion, only handle ACME HTTP challenge when there is a certificate for the domain and `HTTPS_METHOD=redirect`.
### Diffie-Hellman Groups ### Diffie-Hellman Groups
[RFC7919 groups](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A) with key lengths of 2048, 3072, and 4096 bits are [provided by `nginx-proxy`](https://github.com/nginx-proxy/nginx-proxy/dhparam). The ENV `DHPARAM_BITS` can be set to `2048` or `3072` to change from the default 4096-bit key. The DH key file will be located in the container at `/etc/nginx/dhparam/dhparam.pem`. Mounting a different `dhparam.pem` file at that location will override the RFC7919 key. [RFC7919 groups](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A) with key lengths of 2048, 3072, and 4096 bits are [provided by `nginx-proxy`](https://github.com/nginx-proxy/nginx-proxy/dhparam). The ENV `DHPARAM_BITS` can be set to `2048` or `3072` to change from the default 4096-bit key. The DH key file will be located in the container at `/etc/nginx/dhparam/dhparam.pem`. Mounting a different `dhparam.pem` file at that location will override the RFC7919 key.
To use custom `dhparam.pem` files per-virtual-host, the files should be named after the virtual host with a `dhparam` suffix and `.pem` extension. For example, a container with `VIRTUAL_HOST=foo.bar.com` should have a `foo.bar.com.dhparam.pem` file in the `/etc/nginx/certs` directory. To use custom `dhparam.pem` files per-virtual-host, the files should be named after the virtual host with a `dhparam` suffix and `.pem` extension. For example, a container with `VIRTUAL_HOST=foo.bar.com` should have a `foo.bar.com.dhparam.pem` file in the `/etc/nginx/certs` directory.
> [!WARNING] > COMPATIBILITY WARNING: The default generated `dhparam.pem` key is 4096 bits for A+ security. Some older clients (like Java 6 and 7) do not support DH keys with over 1024 bits. In order to support these clients, you must provide your own `dhparam.pem`.
> The default generated `dhparam.pem` key is 4096 bits for A+ security. Some older clients (like Java 6 and 7) do not support DH keys with over 1024 bits. In order to support these clients, you must provide your own `dhparam.pem`.
In the separate container setup, no pre-generated key will be available and neither the [nginxproxy/docker-gen](https://hub.docker.com/r/nginxproxy/docker-gen) image, nor the offical [nginx](https://registry.hub.docker.com/_/nginx/) image will provide one. If you still want A+ security in a separate container setup, you should mount an RFC7919 DH key file to the nginx container at `/etc/nginx/dhparam/dhparam.pem`. In the separate container setup, no pre-generated key will be available and neither the [nginxproxy/docker-gen](https://hub.docker.com/r/nginxproxy/docker-gen) image, nor the offical [nginx](https://registry.hub.docker.com/_/nginx/) image will provide one. If you still want A+ security in a separate container setup, you should mount an RFC7919 DH key file to the nginx container at `/etc/nginx/dhparam/dhparam.pem`.
@ -450,12 +339,9 @@ docker run -e DHPARAM_SKIP=true ....
### Wildcard Certificates ### Wildcard Certificates
Wildcard certificates and keys should be named after the parent domain name with a `.crt` and `.key` extension. For example: Wildcard certificates and keys should be named after the domain name with a `.crt` and `.key` extension. For example `VIRTUAL_HOST=foo.bar.com` would use cert name `bar.com.crt` and `bar.com.key`.
- `VIRTUAL_HOST=foo.bar.com` would use cert name `bar.com.crt` and `bar.com.key` if `foo.bar.com.crt` and `foo.bar.com.key` are not available ### SNI
- `VIRTUAL_HOST=sub.foo.bar.com` use cert name `foo.bar.com.crt` and `foo.bar.com.key` if `sub.foo.bar.com.crt` and `sub.foo.bar.com.key` are not available, but won't use `bar.com.crt` and `bar.com.key`.
### SAN Certificates
If your certificate(s) supports multiple domain names, you can start a container with `CERT_NAME=<name>` to identify the certificate to be used. For example, a certificate for `*.foo.com` and `*.bar.com` could be named `shared.crt` and `shared.key`. A container running with `VIRTUAL_HOST=foo.bar.com` and `CERT_NAME=shared` will then use this shared cert. If your certificate(s) supports multiple domain names, you can start a container with `CERT_NAME=<name>` to identify the certificate to be used. For example, a certificate for `*.foo.com` and `*.bar.com` could be named `shared.crt` and `shared.key`. A container running with `VIRTUAL_HOST=foo.bar.com` and `CERT_NAME=shared` will then use this shared cert.
@ -467,10 +353,7 @@ To enable OCSP Stapling for a domain, `nginx-proxy` looks for a PEM certificate
The default SSL cipher configuration is based on the [Mozilla intermediate profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29) version 5.0 which should provide compatibility with clients back to Firefox 27, Android 4.4.2, Chrome 31, Edge, IE 11 on Windows 7, Java 8u31, OpenSSL 1.0.1, Opera 20, and Safari 9. Note that the DES-based TLS ciphers were removed for security. The configuration also enables HSTS, PFS, OCSP stapling and SSL session caches. Currently TLS 1.2 and 1.3 are supported. The default SSL cipher configuration is based on the [Mozilla intermediate profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29) version 5.0 which should provide compatibility with clients back to Firefox 27, Android 4.4.2, Chrome 31, Edge, IE 11 on Windows 7, Java 8u31, OpenSSL 1.0.1, Opera 20, and Safari 9. Note that the DES-based TLS ciphers were removed for security. The configuration also enables HSTS, PFS, OCSP stapling and SSL session caches. Currently TLS 1.2 and 1.3 are supported.
If you don't require backward compatibility, you can use the [Mozilla modern profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility) profile instead by including the environment variable `SSL_POLICY=Mozilla-Modern` to the nginx-proxy container or to your container. This profile is compatible with clients back to Firefox 63, Android 10.0, Chrome 70, Edge 75, Java 11, OpenSSL 1.1.1, Opera 57, and Safari 12.1. If you don't require backward compatibility, you can use the [Mozilla modern profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility) profile instead by including the environment variable `SSL_POLICY=Mozilla-Modern` to the nginx-proxy container or to your container. This profile is compatible with clients back to Firefox 63, Android 10.0, Chrome 70, Edge 75, Java 11, OpenSSL 1.1.1, Opera 57, and Safari 12.1. Note that this profile is **not** compatible with any version of Internet Explorer.
> [!NOTE]
> This profile is **not** compatible with any version of Internet Explorer.
Complete list of policies available through the `SSL_POLICY` environment variable, including the [AWS ELB Security Policies](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies) and [AWS Classic ELB security policies](https://docs.aws.amazon.com/fr_fr/elasticloadbalancing/latest/classic/elb-security-policy-table.html): Complete list of policies available through the `SSL_POLICY` environment variable, including the [AWS ELB Security Policies](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies) and [AWS Classic ELB security policies](https://docs.aws.amazon.com/fr_fr/elasticloadbalancing/latest/classic/elb-security-policy-table.html):
@ -491,7 +374,6 @@ Complete list of policies available through the `SSL_POLICY` environment variabl
<a href="https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility" target="_blank"> <a href="https://wiki.mozilla.org/Security/Server_Side_TLS#Old_backward_compatibility" target="_blank">
<code>Mozilla-Old</code> <code>Mozilla-Old</code>
</a> </a>
(this policy should use a 1024 bits DH key for compatibility but this container provides a 4096 bits key. The <a href="#diffie-hellman-groups">Diffie-Hellman Groups</a> section details different methods of bypassing this, either globally or per virtual-host.)
</li> </li>
</ul> </ul>
</details> </details>
@ -574,91 +456,50 @@ Complete list of policies available through the `SSL_POLICY` environment variabl
</details> </details>
</br> </br>
Note that the `Mozilla-Old` policy should use a 1024 bits DH key for compatibility but this container provides a 4096 bits key. The [Diffie-Hellman Groups](#diffie-hellman-groups) section details different methods of bypassing this, either globally or per virtual-host.
The default behavior for the proxy when port 80 and 443 are exposed is as follows: The default behavior for the proxy when port 80 and 443 are exposed is as follows:
- If a virtual host has a usable cert, port 80 will redirect to 443 for that virtual host so that HTTPS is always preferred when available. - If a virtual host has a usable cert, port 80 will redirect to 443 for that virtual host so that HTTPS is always preferred when available.
- If the virtual host does not have a usable cert, but `default.crt` and `default.key` exist, those will be used as the virtual host's certificate. - If the virtual host does not have a usable cert, but `default.crt` and `default.key` exist, those will be used as the virtual host's certificate and the client browser will receive a 500 error.
- If the virtual host does not have a usable cert, and `default.crt` and `default.key` do not exist, or if the virtual host is configured not to trust the default certificate, SSL handshake will be rejected (see [Default and Missing Certificate](#default-and-missing-certificate) below). - If the virtual host does not have a usable cert, and `default.crt` and `default.key` do not exist, TLS negotiation will fail (see [Missing Certificate](#missing-certificate) below).
The redirection from HTTP to HTTPS use by default a [`301`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301) response for every HTTP methods (except `CONNECT` and `TRACE` which are disabled on nginx). If you wish to use a custom redirection response for the `OPTIONS`, `POST`, `PUT`, `PATCH` and `DELETE` HTTP methods, you can either do it globally with the environment variable `NON_GET_REDIRECT` on the proxy container or per virtual host with the `com.github.nginx-proxy.nginx-proxy.non-get-redirect` label on proxied containers. Valid values are [`307`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307) and [`308`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308).
To serve traffic in both SSL and non-SSL modes without redirecting to SSL, you can include the environment variable `HTTPS_METHOD=noredirect` (the default is `HTTPS_METHOD=redirect`). You can also disable the non-SSL site entirely with `HTTPS_METHOD=nohttp`, or disable the HTTPS site with `HTTPS_METHOD=nohttps`. `HTTPS_METHOD` can be specified on each container for which you want to override the default behavior or on the proxy container to set it globally. If `HTTPS_METHOD=noredirect` is used, Strict Transport Security (HSTS) is disabled to prevent HTTPS users from being redirected by the client. If you cannot get to the HTTP site after changing this setting, your browser has probably cached the HSTS policy and is automatically redirecting you back to HTTPS. You will need to clear your browser's HSTS cache or use an incognito window / different browser. To serve traffic in both SSL and non-SSL modes without redirecting to SSL, you can include the environment variable `HTTPS_METHOD=noredirect` (the default is `HTTPS_METHOD=redirect`). You can also disable the non-SSL site entirely with `HTTPS_METHOD=nohttp`, or disable the HTTPS site with `HTTPS_METHOD=nohttps`. `HTTPS_METHOD` can be specified on each container for which you want to override the default behavior or on the proxy container to set it globally. If `HTTPS_METHOD=noredirect` is used, Strict Transport Security (HSTS) is disabled to prevent HTTPS users from being redirected by the client. If you cannot get to the HTTP site after changing this setting, your browser has probably cached the HSTS policy and is automatically redirecting you back to HTTPS. You will need to clear your browser's HSTS cache or use an incognito window / different browser.
By default, [HTTP Strict Transport Security (HSTS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) is enabled with `max-age=31536000` for HTTPS sites. You can disable HSTS with the environment variable `HSTS=off` or use a custom HSTS configuration like `HSTS=max-age=31536000; includeSubDomains; preload`. By default, [HTTP Strict Transport Security (HSTS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) is enabled with `max-age=31536000` for HTTPS sites. You can disable HSTS with the environment variable `HSTS=off` or use a custom HSTS configuration like `HSTS=max-age=31536000; includeSubDomains; preload`.
> [!WARNING] _WARNING_: HSTS will force your users to visit the HTTPS version of your site for the `max-age` time - even if they type in `http://` manually. The only way to get to an HTTP site after receiving an HSTS response is to clear your browser's HSTS cache.
> HSTS will force your users to visit the HTTPS version of your site for the max-age time - even if they type in http:// manually. The only way to get to an HTTP site after receiving an HSTS response is to clear your browser's HSTS cache.
### Default and Missing Certificate ### Missing Certificate
If no matching certificate is found for a given virtual host, nginx-proxy will configure nginx to use the default certificate (`default.crt` with `default.key`). If HTTPS is enabled for a virtual host but its certificate is missing, nginx-proxy will configure nginx to use the default certificate (`default.crt` with `default.key`) and return a 500 error.
If the default certificate is also missing, nginx-proxy will: If the default certificate is also missing, nginx-proxy will configure nginx to accept HTTPS connections but fail the TLS negotiation. Client browsers will render a TLS error page. As of March 2023, web browsers display the following error messages:
- force enable HTTP; i.e. `HTTPS_METHOD` will switch to `noredirect` if it was set to `nohttp` or `redirect`. If this switch to HTTP is not wanted set `ENABLE_HTTP_ON_MISSING_CERT=false` (default is `true`). - Chrome:
- configure nginx to reject the SSL handshake for this vhost. Client browsers will render a TLS error page. As of October 2024, web browsers display the following error messages:
#### Chrome: > This site can't provide a secure connection
> This site cant be reached
> >
> The web page at https://example.test/ might be temporarily down or it may have moved permanently to a new web address. > example.test sent an invalid response.
> >
> `ERR_SSL_UNRECOGNIZED_NAME_ALERT` > Try running Connectivity Diagnostics.
>
> `ERR_SSL_PROTOCOL_ERROR`
#### Firefox: - Firefox:
> Secure Connection Failed > Secure Connection Failed
> >
> An error occurred during a connection to example.test. SSL peer has no certificate for the requested DNS name. > An error occurred during a connection to example.test.
> Peer reports it experienced an internal error.
> >
> Error code: `SSL_ERROR_UNRECOGNIZED_NAME_ALERT` > Error code: `SSL_ERROR_INTERNAL_ERROR_ALERT` "TLS error".
>
> - The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
> - Please contact the website owners to inform them of this problem.
#### Safari:
> Safari Can't Open the Page
>
> Safari can't open the page "https://example.test" because Safari can't establish a secure connection to the server "example.test".
> [!NOTE]
> Prior to version `1.7`, nginx-proxy never trusted the default certificate: when the default certificate was present, virtual hosts that did not have a usable per-virtual-host cert used the default cert but always returned a 500 error over HTTPS. If you want to restore this behaviour, you can do it globally by setting the enviroment variable `TRUST_DEFAULT_CERT` to `false` on the proxy container, or per-virtual-host by setting the label `com.github.nginx-proxy.nginx-proxy.trust-default-cert`to `false` on a proxied container.
### Certificate selection
Summarizing all the above informations, nginx-proxy will select the certificate for a given virtual host using the following sequence:
1. if `CERT_NAME` is used, nginx-proxy will use the corresponding certificate if it exists (eg `foor.bar.com``CERT_NAME.crt`), or disable HTTPS for this virtual host if it does not. See [SAN certificates](#san-certificates).
2. if a certificate exactly matching the virtual host hostname exist, nginx-proxy will use it (eg `foo.bar.com``foo.bar.com.crt`).
3. if the virtual host hostname is a subdomain (eg `foo.bar.com` but not `bar.com`) and a certificate exactly matching its parent domain exist , nginx-proxy will use it (eg `foor.bar.com``bar.com.crt`). See [wildcard certificates](#wildcard-certificates).
4. if the default certificate (`default.crt`) exist and is trusted, nginx-proxy will use it (eg `foor.bar.com``default.crt`). See [default and missing certificate](#default-and-missing-certificate).
5. if the default certificate does not exist or isn't trusted, nginx-proxy will disable HTTPS for this virtual host (eg `foor.bar.com` → no HTTPS).
> [!IMPORTANT]
> Using `CERT_NAME` take precedence over the certificate selection process, meaning nginx-proxy will not auto select a correct certificate in step 2 trough 5 if `CERT_NAME` was used with an incorrect value or without corresponding certificate.
> [!NOTE]
> In all the above cases, if a private key file corresponding to the selected certificate (eg `foo.bar.com.key` for the `foor.bar.com.crt` certificate) does not exist, HTTPS will be disabled for this virtual host.
⬆️ [back to table of contents](#table-of-contents) ⬆️ [back to table of contents](#table-of-contents)
## IPv6 Support ## IPv6 Support
### IPv6 Docker Networks You can activate the IPv6 support for the nginx-proxy container by passing the value `true` to the `ENABLE_IPV6` environment variable:
nginx-proxy support both IPv4 and IPv6 on Docker networks.
By default nginx-proxy will prefer IPv4: if a container can be reached over both IPv4 and IPv6, only its IPv4 will be used.
This can be changed globally by setting the environment variable `PREFER_IPV6_NETWORK` to `true` on the proxy container: with this setting the proxy will only use IPv6 for containers that can be reached over both IPv4 and IPv6.
IPv4 and IPv6 are never both used at the same time on containers that use both IP stacks to avoid artificially inflating the effective round robin weight of those containers.
### Listening on IPv6
By default the nginx-proxy container will only listen on IPv4. To enable listening on IPv6 too, set the `ENABLE_IPV6` environment variable to `true`:
```console ```console
docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy
@ -688,7 +529,7 @@ More reading on the potential TCP head-of-line blocking issue with HTTP/2: [HTTP
### HTTP/3 support ### HTTP/3 support
> [!WARNING] > **Warning**
> HTTP/3 support [is still considered experimental in nginx](https://www.nginx.com/blog/binary-packages-for-preview-nginx-quic-http3-implementation/) and as such is considered experimental in nginx-proxy too and is disabled by default. [Feedbacks for the HTTP/3 support are welcome in #2271.](https://github.com/nginx-proxy/nginx-proxy/discussions/2271) > HTTP/3 support [is still considered experimental in nginx](https://www.nginx.com/blog/binary-packages-for-preview-nginx-quic-http3-implementation/) and as such is considered experimental in nginx-proxy too and is disabled by default. [Feedbacks for the HTTP/3 support are welcome in #2271.](https://github.com/nginx-proxy/nginx-proxy/discussions/2271)
HTTP/3 use the QUIC protocol over UDP (unlike HTTP/1.1 and HTTP/2 which work over TCP), so if you want to use HTTP/3 you'll have to explicitely publish the 443/udp port of the proxy in addition to the 443/tcp port: HTTP/3 use the QUIC protocol over UDP (unlike HTTP/1.1 and HTTP/2 which work over TCP), so if you want to use HTTP/3 you'll have to explicitely publish the 443/udp port of the proxy in addition to the 443/tcp port:
@ -740,7 +581,7 @@ If you need to configure Nginx beyond what is possible using environment variabl
If you want to replace the default proxy settings for the nginx container, add a configuration file at `/etc/nginx/proxy.conf`. A file with the default settings would look like this: If you want to replace the default proxy settings for the nginx container, add a configuration file at `/etc/nginx/proxy.conf`. A file with the default settings would look like this:
```nginx ```Nginx
# HTTP 1.1 support # HTTP 1.1 support
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
@ -758,8 +599,7 @@ proxy_set_header X-Original-URI $request_uri;
proxy_set_header Proxy ""; proxy_set_header Proxy "";
``` ```
> [!IMPORTANT] **_NOTE_**: If you provide this file it will replace the defaults; you may want to check the [nginx.tmpl](https://github.com/nginx-proxy/nginx-proxy/blob/main/nginx.tmpl) file to make sure you have all of the needed options.
> If you provide this file it will replace the defaults; you may want to check the [nginx.tmpl](https://github.com/nginx-proxy/nginx-proxy/blob/main/nginx.tmpl) file to make sure you have all of the needed options.
### Proxy-wide ### Proxy-wide
@ -775,227 +615,57 @@ RUN { \
} > /etc/nginx/conf.d/my_proxy.conf } > /etc/nginx/conf.d/my_proxy.conf
``` ```
Or it can be done by mounting in your custom configuration in your `docker run` command or your Docker Compose file: Or it can be done by mounting in your custom configuration in your `docker run` command:
```nginx
# content of the my_proxy.conf file
server_tokens off;
client_max_body_size 100m;
```
<details>
<summary>Docker CLI</summary>
```console ```console
docker run --detach \ docker run -d -p 80:80 -p 443:443 -v /path/to/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy
--name nginx-proxy \
--publish 80:80 \
--publish 443:443 \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
--volume /path/to/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro \
nginxproxy/nginx-proxy
``` ```
</details>
<details>
<summary>Docker Compose file</summary>
```yaml
services:
proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /path/to/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro
```
</details>
### Per-VIRTUAL_HOST ### Per-VIRTUAL_HOST
To add settings on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d`. Unlike in the proxy-wide case, which allows multiple config files with any name ending in `.conf`, the per-`VIRTUAL_HOST` file must be named exactly after the `VIRTUAL_HOST`, or if `VIRTUAL_HOST` is a regex, after the sha1 hash of the regex. To add settings on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d`. Unlike in the proxy-wide case, which allows multiple config files with any name ending in `.conf`, the per-`VIRTUAL_HOST` file must be named exactly after the `VIRTUAL_HOST`.
In order to allow virtual hosts to be dynamically configured as backends are added and removed, it makes the most sense to mount an external directory as `/etc/nginx/vhost.d` as opposed to using derived images or mounting individual configuration files. In order to allow virtual hosts to be dynamically configured as backends are added and removed, it makes the most sense to mount an external directory as `/etc/nginx/vhost.d` as opposed to using derived images or mounting individual configuration files.
For example, if you have a virtual host named `app.example.com`, you could provide a custom configuration for that host as follows: For example, if you have a virtual host named `app.example.com`, you could provide a custom configuration for that host as follows:
1. create your virtual host config file: ```console
docker run -d -p 80:80 -p 443:443 -v /path/to/vhost.d:/etc/nginx/vhost.d:ro -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy
```nginx { echo 'server_tokens off;'; echo 'client_max_body_size 100m;'; } > /path/to/vhost.d/app.example.com
# content of the custom-vhost-config.conf file
client_max_body_size 100m;
``` ```
2. mount it to `/etc/nginx/vhost.d/app.example.com`: If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=example.com,www.example.com`), the virtual host configuration file must exist for each hostname. If you would like to use the same configuration for multiple virtual host names, you can use a symlink:
<details>
<summary>Docker CLI</summary>
```console ```console
docker run --detach \ { echo 'server_tokens off;'; echo 'client_max_body_size 100m;'; } > /path/to/vhost.d/www.example.com
--name nginx-proxy \ ln -s /path/to/vhost.d/www.example.com /path/to/vhost.d/example.com
--publish 80:80 \
--publish 443:443 \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
--volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/app.example.com:ro \
nginxproxy/nginx-proxy
``` ```
</details>
<details>
<summary>Docker Compose file</summary>
```yaml
services:
proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/app.example.com:ro
```
</details>
If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=example.com,www.example.com`), the virtual host configuration file must exist for each hostname:
<details>
<summary>Docker CLI</summary>
```console
docker run --detach \
--name nginx-proxy \
--publish 80:80 \
--publish 443:443 \
--volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/example.com:ro \
--volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/www.example.com:ro \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
nginxproxy/nginx-proxy
```
</details>
<details>
<summary>Docker Compose file</summary>
```yaml
services:
proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/example.com:ro
- /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/www.example.com:ro
```
</details>
### Per-VIRTUAL_HOST default configuration ### Per-VIRTUAL_HOST default configuration
If you want most of your virtual hosts to use a default single configuration and then override on a few specific ones, add those settings to the `/etc/nginx/vhost.d/default` file. This file will be used on any virtual host which does not have a [per-VIRTUAL_HOST file](#per-virtual_host) associated with it. If you want most of your virtual hosts to use a default single configuration and then override on a few specific ones, add those settings to the `/etc/nginx/vhost.d/default` file. This file will be used on any virtual host which does not have a `/etc/nginx/vhost.d/{VIRTUAL_HOST}` file associated with it.
### Per-VIRTUAL_HOST location configuration ### Per-VIRTUAL_HOST location configuration
To add settings to the "location" block on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d` just like the per-`VIRTUAL_HOST` section except with the suffix `_location` (like this section, if your `VIRTUAl_HOST` is a regex, use the sha1 hash of the regex instead, with the suffix `_location` appended). To add settings to the "location" block on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d` just like the previous section except with the suffix `_location`.
For example, if you have a virtual host named `app.example.com` and you have configured a proxy_cache `my-cache` in another custom file, you could tell it to use a proxy cache as follows: For example, if you have a virtual host named `app.example.com` and you have configured a proxy_cache `my-cache` in another custom file, you could tell it to use a proxy cache as follows:
1. create your virtual host location config file: ```console
docker run -d -p 80:80 -p 443:443 -v /path/to/vhost.d:/etc/nginx/vhost.d:ro -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy
```nginx { echo 'proxy_cache my-cache;'; echo 'proxy_cache_valid 200 302 60m;'; echo 'proxy_cache_valid 404 1m;' } > /path/to/vhost.d/app.example.com_location
# content of the custom-vhost-location-config.conf file
proxy_cache my-cache;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
``` ```
2. mount it to `/etc/nginx/vhost.d/app.example.com_location`: If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=example.com,www.example.com`), the virtual host configuration file must exist for each hostname. If you would like to use the same configuration for multiple virtual host names, you can use a symlink:
<details>
<summary>Docker CLI</summary>
```console ```console
docker run --detach \ { echo 'proxy_cache my-cache;'; echo 'proxy_cache_valid 200 302 60m;'; echo 'proxy_cache_valid 404 1m;' } > /path/to/vhost.d/app.example.com_location
--name nginx-proxy \ ln -s /path/to/vhost.d/www.example.com /path/to/vhost.d/example.com
--publish 80:80 \
--publish 443:443 \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
--volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/app.example.com_location:ro \
nginxproxy/nginx-proxy
``` ```
</details>
<details>
<summary>Docker Compose file</summary>
```yaml
services:
proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/app.example.com_location:ro
```
</details>
If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=example.com,www.example.com`), the virtual host configuration file must exist for each hostname:
<details>
<summary>Docker CLI</summary>
```console
docker run --detach \
--name nginx-proxy \
--publish 80:80 \
--publish 443:443 \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
--volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/example.com_location:ro \
--volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/www.example.com_location:ro \
nginxproxy/nginx-proxy
```
</details>
<details>
<summary>Docker Compose file</summary>
```yaml
services:
proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/example.com_location:ro
- /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/www.example.com_location:ro
```
</details>
### Per-VIRTUAL_HOST location default configuration ### Per-VIRTUAL_HOST location default configuration
If you want most of your virtual hosts to use a default single `location` block configuration and then override on a few specific ones, add those settings to the `/etc/nginx/vhost.d/default_location` file. This file will be used on any virtual host which does not have a [Per-VIRTUAL_HOST location file](#per-virtual_host-location-configuration) associated with it. If you want most of your virtual hosts to use a default single `location` block configuration and then override on a few specific ones, add those settings to the `/etc/nginx/vhost.d/default_location` file. This file will be used on any virtual host which does not have a `/etc/nginx/vhost.d/{VIRTUAL_HOST}_location` file associated with it.
### Overriding `location` blocks ### Overriding `location` blocks
@ -1005,7 +675,7 @@ The `${VIRTUAL_HOST}_${PATH_HASH}_location`, `${VIRTUAL_HOST}_location`, and `de
/etc/nginx/vhost.d/${VIRTUAL_HOST}_${PATH_HASH}_location_override /etc/nginx/vhost.d/${VIRTUAL_HOST}_${PATH_HASH}_location_override
``` ```
where `${VIRTUAL_HOST}` is the name of the virtual host (the `VIRTUAL_HOST` environment variable), or the sha1 hash of `VIRTUAL_HOST` when it's a regex, and `${PATH_HASH}` is the SHA-1 hash of the path, as [described above](#per-virtual_path-location-configuration). where `${VIRTUAL_HOST}` is the name of the virtual host (the `VIRTUAL_HOST` environment variable) and `${PATH_HASH}` is the SHA-1 hash of the path, as [described above](#per-virtual_path-location-configuration).
For convenience, the `_${PATH_HASH}` part can be omitted if the path is `/`: For convenience, the `_${PATH_HASH}` part can be omitted if the path is `/`:
@ -1017,7 +687,7 @@ When an override file exists, the `location` block that is normally created by `
You are responsible for providing a suitable `location` block in your override file as required for your service. By default, `nginx-proxy` uses the `VIRTUAL_HOST` name as the upstream name for your application's Docker container; see [here](#unhashed-vs-sha1-upstream-names) for details. As an example, if your container has a `VIRTUAL_HOST` value of `app.example.com`, then to override the location block for `/` you would create a file named `/etc/nginx/vhost.d/app.example.com_location_override` that contains something like this: You are responsible for providing a suitable `location` block in your override file as required for your service. By default, `nginx-proxy` uses the `VIRTUAL_HOST` name as the upstream name for your application's Docker container; see [here](#unhashed-vs-sha1-upstream-names) for details. As an example, if your container has a `VIRTUAL_HOST` value of `app.example.com`, then to override the location block for `/` you would create a file named `/etc/nginx/vhost.d/app.example.com_location_override` that contains something like this:
```nginx ```
location / { location / {
proxy_pass http://app.example.com; proxy_pass http://app.example.com;
} }
@ -1027,86 +697,13 @@ location / {
Per virtual-host `servers_tokens` directive can be configured by passing appropriate value to the `SERVER_TOKENS` environment variable. Please see the [nginx http_core module configuration](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens) for more details. Per virtual-host `servers_tokens` directive can be configured by passing appropriate value to the `SERVER_TOKENS` environment variable. Please see the [nginx http_core module configuration](https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens) for more details.
### Custom error page
To override the default error page displayed on 50x errors, mount your custom HTML error page inside the container at `/usr/share/nginx/html/errors/50x.html`:
```console
docker run --detach \
--name nginx-proxy \
--publish 80:80 \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
--volume /path/to/error.html:/usr/share/nginx/html/errors/50x.html:ro \
nginxproxy/nginx-proxy
```
> [!NOTE]
> This will not replace your own services error pages.
⬆️ [back to table of contents](#table-of-contents)
## TCP and UDP stream
If you want to proxy non-HTTP traffic, you can use nginx's stream module. Write a configuration file and mount it inside `/etc/nginx/toplevel.conf.d`.
```nginx
# stream.conf
stream {
upstream stream_backend {
server backend1.example.com:12345;
server backend2.example.com:12345;
server backend3.example.com:12346;
# ...
}
server {
listen 12345;
#TCP traffic will be forwarded to the "stream_backend" upstream group
proxy_pass stream_backend;
}
server {
listen 12346;
#TCP traffic will be forwarded to the specified server
proxy_pass backend.example.com:12346;
}
upstream dns_servers {
server 192.168.136.130:53;
server 192.168.136.131:53;
# ...
}
server {
listen 53 udp;
#UDP traffic will be forwarded to the "dns_servers" upstream group
proxy_pass dns_servers;
}
# ...
}
```
```console
docker run --detach \
--name nginx-proxy \
--publish 80:80 \
--publish 12345:12345 \
--publish 12346:12346 \
--publish 53:53:udp \
--volume /var/run/docker.sock:/tmp/docker.sock:ro \
--volume ./stream.conf:/etc/nginx/toplevel.conf.d/stream.conf:ro \
nginxproxy/nginx-proxy
```
> [!NOTE]
> TCP and UDP stream are not core features of nginx-proxy, so the above is provided as an example only, without any guarantee.
⬆️ [back to table of contents](#table-of-contents) ⬆️ [back to table of contents](#table-of-contents)
## Unhashed vs SHA1 upstream names ## Unhashed vs SHA1 upstream names
By default the nginx configuration `upstream` blocks will use this block's corresponding hostname as a predictable name. However, this can cause issues in some setups (see [this issue](https://github.com/nginx-proxy/nginx-proxy/issues/1162)). In those cases you might want to switch to SHA1 names for the `upstream` blocks by setting the `SHA1_UPSTREAM_NAME` environment variable to `true` on the nginx-proxy container. By default the nginx configuration `upstream` blocks will use this block's corresponding hostname as a predictable name. However, this can cause issues in some setups (see [this issue](https://github.com/nginx-proxy/nginx-proxy/issues/1162)). In those cases you might want to switch to SHA1 names for the `upstream` blocks by setting the `SHA1_UPSTREAM_NAME` environment variable to `true` on the nginx-proxy container.
> [!NOTE] Please note that using regular expressions in `VIRTUAL_HOST` will always result in a corresponding `upstream` block with an SHA1 name.
> Using regular expressions in `VIRTUAL_HOST` will always result in a corresponding `upstream` block with an SHA1 name.
⬆️ [back to table of contents](#table-of-contents) ⬆️ [back to table of contents](#table-of-contents)
@ -1157,6 +754,8 @@ docker run -e VIRTUAL_HOST=foo.bar.com ...
## Docker Compose ## Docker Compose
```yaml ```yaml
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy image: nginxproxy/nginx-proxy
@ -1197,7 +796,7 @@ docker exec <nginx-proxy-instance> nginx -T
Pay attention to the `upstream` definition blocks, which should look like this: Pay attention to the `upstream` definition blocks, which should look like this:
```nginx ```Nginx
# foo.example.com # foo.example.com
upstream foo.example.com { upstream foo.example.com {
## Can be connected with "my_network" network ## Can be connected with "my_network" network
@ -1217,114 +816,6 @@ The effective `Port` is retrieved by order of precedence:
1. From the container's exposed port if there is only one 1. From the container's exposed port if there is only one
1. From the default port 80 when none of the above methods apply 1. From the default port 80 when none of the above methods apply
### Debug endpoint
The debug endpoint can be enabled:
- globally by setting the `DEBUG_ENDPOINT` environment variable to `true` on the nginx-proxy container.
- per container by setting the `com.github.nginx-proxy.nginx-proxy.debug-endpoint` label to `true` on a proxied container.
Enabling it will expose the endpoint at `<your.domain.tld>/nginx-proxy-debug`.
Querying the debug endpoint will show the global config, along with the virtual host and per path configs in JSON format.
```yaml
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
environment:
DEBUG_ENDPOINT: "true"
test:
image: nginx
environment:
VIRTUAL_HOST: test.nginx-proxy.tld
```
(on the CLI, using [`jq`](https://jqlang.github.io/jq/) to format the output of `curl` is recommended)
```console
curl -s -H "Host: test.nginx-proxy.tld" localhost/nginx-proxy-debug | jq
```
```json
{
"global": {
"acme_http_challenge": "true",
"default_cert_ok": false,
"default_host": null,
"default_root_response": "404",
"enable_access_log": true,
"enable_debug_endpoint": "true",
"enable_http2": "true",
"enable_http3": "false",
"enable_http_on_missing_cert": "true",
"enable_ipv6": false,
"enable_json_logs": false,
"external_http_port": "80",
"external_https_port": "443",
"hsts": "max-age=31536000",
"https_method": "redirect",
"log_format": null,
"log_format_escape": null,
"nginx_proxy_version": "1.6.3",
"resolvers": "127.0.0.11",
"sha1_upstream_name": false,
"ssl_policy": "Mozilla-Intermediate",
"trust_downstream_proxy": true
},
"request": {
"host": "test.nginx-proxy.tld",
"http2": "",
"http3": "",
"https": "",
"ssl_cipher": "",
"ssl_protocol": ""
},
"vhost": {
"acme_http_challenge_enabled": true,
"acme_http_challenge_legacy": false,
"cert": "",
"cert_ok": false,
"default": false,
"enable_debug_endpoint": true,
"hostname": "test.nginx-proxy.tld",
"hsts": "max-age=31536000",
"http2_enabled": true,
"http3_enabled": false,
"https_method": "noredirect",
"is_regexp": false,
"paths": {
"/": {
"dest": "",
"keepalive": "disabled",
"network_tag": "external",
"ports": {
"legacy": [
{
"Name": "wip-test-1"
}
]
},
"proto": "http",
"upstream": "test.nginx-proxy.tld"
}
},
"server_tokens": "",
"ssl_policy": "",
"upstream_name": "test.nginx-proxy.tld",
"vhost_root": "/var/www/public"
}
}
```
> [!WARNING]
> Please be aware that the debug endpoint work by rendering the JSON response straight to the nginx configuration in plaintext. nginx has an upper limit on the size of the configuration files it can parse, so only activate it when needed, and preferably on a per container basis if your setup has a large number of virtual hosts.
⬆️ [back to table of contents](#table-of-contents) ⬆️ [back to table of contents](#table-of-contents)
## Contributing ## Contributing

View file

@ -3,45 +3,23 @@
{{- /* {{- /*
* Global values. Values are stored in this map rather than in individual * Global values. Values are stored in this map rather than in individual
* global variables so that the values can be easily passed to embedded * global variables so that the values can be easily passed to embedded
* templates (Go templates cannot access variables outside of their own * templates. (Go templates cannot access variables outside of their own
* scope) and displayed in the debug endpoint output. * scope.)
*/}} */}}
{{- $globals := dict }} {{- $globals := dict }}
{{- $_ := set $globals "containers" $ }} {{- $_ := set $globals "containers" $ }}
{{- $_ := set $globals "Env" $.Env }} {{- $_ := set $globals "Env" $.Env }}
{{- $_ := set $globals "Docker" $.Docker }} {{- $_ := set $globals "Docker" $.Docker }}
{{- $_ := set $globals "CurrentContainer" (where $globals.containers "ID" $globals.Docker.CurrentContainerID | first) }} {{- $_ := set $globals "CurrentContainer" (where $globals.containers "ID" $globals.Docker.CurrentContainerID | first) }}
{{- $_ := set $globals "default_cert_ok" (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
{{- $config := dict }} {{- $_ := set $globals "external_http_port" (coalesce $globals.Env.HTTP_PORT "80") }}
{{- $_ := set $config "nginx_proxy_version" $.Env.NGINX_PROXY_VERSION }} {{- $_ := set $globals "external_https_port" (coalesce $globals.Env.HTTPS_PORT "443") }}
{{- $_ := set $config "default_cert_ok" (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} {{- $_ := set $globals "sha1_upstream_name" (parseBool (coalesce $globals.Env.SHA1_UPSTREAM_NAME "false")) }}
{{- $_ := set $config "external_http_port" ($globals.Env.HTTP_PORT | default "80") }} {{- $_ := set $globals "default_root_response" (coalesce $globals.Env.DEFAULT_ROOT "404") }}
{{- $_ := set $config "external_https_port" ($globals.Env.HTTPS_PORT | default "443") }} {{- $_ := set $globals "trust_downstream_proxy" (parseBool (coalesce $globals.Env.TRUST_DOWNSTREAM_PROXY "true")) }}
{{- $_ := set $config "sha1_upstream_name" ($globals.Env.SHA1_UPSTREAM_NAME | default "false" | parseBool) }} {{- $_ := set $globals "access_log" (or (and (not $globals.Env.DISABLE_ACCESS_LOGS) "access_log /var/log/nginx/access.log vhost;") "") }}
{{- $_ := set $config "default_root_response" ($globals.Env.DEFAULT_ROOT | default "404") }} {{- $_ := set $globals "enable_ipv6" (parseBool (coalesce $globals.Env.ENABLE_IPV6 "false")) }}
{{- $_ := set $config "trust_default_cert" ($globals.Env.TRUST_DEFAULT_CERT | default "true") }} {{- $_ := set $globals "ssl_policy" (or ($globals.Env.SSL_POLICY) "Mozilla-Intermediate") }}
{{- $_ := set $config "trust_downstream_proxy" ($globals.Env.TRUST_DOWNSTREAM_PROXY | default "true" | parseBool) }}
{{- $_ := set $config "enable_access_log" ($globals.Env.DISABLE_ACCESS_LOGS | default "false" | parseBool | not) }}
{{- $_ := set $config "enable_ipv6" ($globals.Env.ENABLE_IPV6 | default "false" | parseBool) }}
{{- $_ := set $config "prefer_ipv6_network" ($globals.Env.PREFER_IPV6_NETWORK | default "false" | parseBool) }}
{{- $_ := set $config "ssl_policy" ($globals.Env.SSL_POLICY | default "Mozilla-Intermediate") }}
{{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }}
{{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }}
{{- $_ := set $config "acme_http_challenge" ($globals.Env.ACME_HTTP_CHALLENGE_LOCATION | default "true") }}
{{- $_ := set $config "enable_http2" ($globals.Env.ENABLE_HTTP2 | default "true") }}
{{- $_ := set $config "enable_http3" ($globals.Env.ENABLE_HTTP3 | default "false") }}
{{- $_ := set $config "enable_http_on_missing_cert" ($globals.Env.ENABLE_HTTP_ON_MISSING_CERT | default "true") }}
{{- $_ := set $config "https_method" ($globals.Env.HTTPS_METHOD | default "redirect") }}
{{- $_ := set $config "non_get_redirect" ($globals.Env.NON_GET_REDIRECT | default "301") }}
{{- $_ := set $config "default_host" $globals.Env.DEFAULT_HOST }}
{{- $_ := set $config "resolvers" $globals.Env.RESOLVERS }}
{{- /* LOG_JSON is a shorthand that sets logging defaults to JSON format */}}
{{- $_ := set $config "enable_json_logs" ($globals.Env.LOG_JSON | default "false" | parseBool) }}
{{- $_ := set $config "log_format" $globals.Env.LOG_FORMAT }}
{{- $_ := set $config "log_format_escape" $globals.Env.LOG_FORMAT_ESCAPE }}
{{- $_ := set $globals "config" $config }}
{{- $_ := set $globals "vhosts" (dict) }} {{- $_ := set $globals "vhosts" (dict) }}
{{- $_ := set $globals "networks" (dict) }} {{- $_ := set $globals "networks" (dict) }}
# Networks available to the container running docker-gen (which are assumed to # Networks available to the container running docker-gen (which are assumed to
@ -78,8 +56,7 @@
* The return value will be added to the dot dict with key "ip". * The return value will be added to the dot dict with key "ip".
*/}} */}}
{{- define "container_ip" }} {{- define "container_ip" }}
{{- $ipv4 := "" }} {{- $ip := "" }}
{{- $ipv6 := "" }}
# networks: # networks:
{{- range sortObjectsByKeysAsc $.container.Networks "Name" }} {{- range sortObjectsByKeysAsc $.container.Networks "Name" }}
{{- /* {{- /*
@ -94,17 +71,17 @@
{{- /* Handle containers in host nework mode */}} {{- /* Handle containers in host nework mode */}}
{{- if (index $.globals.networks "host") }} {{- if (index $.globals.networks "host") }}
# both container and proxy are in host network mode, using localhost IP # both container and proxy are in host network mode, using localhost IP
{{- $ipv4 = "127.0.0.1" }} {{- $ip = "127.0.0.1" }}
{{- continue }} {{- continue }}
{{- end }} {{- end }}
{{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }} {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }}
{{- if and . .Gateway (not .Internal) }} {{- if and . .Gateway (not .Internal) }}
# container is in host network mode, using {{ .Name }} gateway IP # container is in host network mode, using {{ .Name }} gateway IP
{{- $ipv4 = .Gateway }} {{- $ip = .Gateway }}
{{- break }} {{- break }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $ipv4 }} {{- if $ip }}
{{- continue }} {{- continue }}
{{- end }} {{- end }}
{{- end }} {{- end }}
@ -114,41 +91,26 @@
{{- end }} {{- end }}
{{- /* {{- /*
* Do not emit multiple `server` directives for this container if it * Do not emit multiple `server` directives for this container if it
* is reachable over multiple networks or multiple IP stacks. This avoids * is reachable over multiple networks. This avoids accidentally
* accidentally inflating the effective round-robin weight of a server due * inflating the effective round-robin weight of a server due to the
* to the redundant upstream addresses that nginx sees as belonging to * redundant upstream addresses that nginx sees as belonging to
* distinct servers. * distinct servers.
*/}} */}}
{{- if or $ipv4 $ipv6 }} {{- if $ip }}
# {{ .Name }} (ignored; reachable but redundant) # {{ .Name }} (ignored; reachable but redundant)
{{- continue }} {{- continue }}
{{- end }} {{- end }}
# {{ .Name }} (reachable) # {{ .Name }} (reachable)
{{- if and . .IP }} {{- if and . .IP }}
{{- $ipv4 = .IP }} {{- $ip = .IP }}
{{- end }} {{- else }}
{{- if and . .GlobalIPv6Address }} # /!\ No IP for this network!
{{- $ipv6 = .GlobalIPv6Address }}
{{- end }}
{{- if and (empty $ipv4) (empty $ipv6) }}
# /!\ No IPv4 or IPv6 for this network!
{{- end }} {{- end }}
{{- else }} {{- else }}
# (none) # (none)
{{- end }} {{- end }}
{{ if and $ipv6 $.globals.config.prefer_ipv6_network }} # IP address: {{ if $ip }}{{ $ip }}{{ else }}(none usable){{ end }}
# IPv4 address: {{ if $ipv4 }}{{ $ipv4 }} (ignored; reachable but IPv6 prefered){{ else }}(none usable){{ end }} {{- $_ := set $ "ip" $ip }}
# IPv6 address: {{ $ipv6 }}
{{- $_ := set $ "ip" (printf "[%s]" $ipv6) }}
{{- else }}
# IPv4 address: {{ if $ipv4 }}{{ $ipv4 }}{{ else }}(none usable){{ end }}
# IPv6 address: {{ if $ipv6 }}{{ $ipv6 }}{{ if $ipv4 }} (ignored; reachable but IPv4 prefered){{ end }}{{ else }}(none usable){{ end }}
{{- if $ipv4 }}
{{- $_ := set $ "ip" $ipv4 }}
{{- else if $ipv6}}
{{- $_ := set $ "ip" (printf "[%s]" $ipv6) }}
{{- end }}
{{- end }}
{{- end }} {{- end }}
{{- /* {{- /*
@ -163,10 +125,10 @@
*/}} */}}
{{- define "container_port" }} {{- define "container_port" }}
{{- /* If only 1 port exposed, use that as a default, else 80. */}} {{- /* If only 1 port exposed, use that as a default, else 80. */}}
# exposed ports (first ten):{{ range $index, $address := (sortObjectsByKeysAsc $.container.Addresses "Port") }}{{ if lt $index 10 }} {{ $address.Port }}/{{ $address.Proto }}{{ end }}{{ else }} (none){{ end }} # exposed ports:{{ range sortObjectsByKeysAsc $.container.Addresses "Port" }} {{ .Port }}/{{ .Proto }}{{ else }} (none){{ end }}
{{- $default_port := when (eq (len $.container.Addresses) 1) (first $.container.Addresses).Port "80" }} {{- $default_port := when (eq (len $.container.Addresses) 1) (first $.container.Addresses).Port "80" }}
# default port: {{ $default_port }} # default port: {{ $default_port }}
{{- $port := eq $.port "default" | ternary $default_port $.port }} {{- $port := when (eq $.port "legacy") (or $.container.Env.VIRTUAL_PORT $default_port) $.port }}
# using port: {{ $port }} # using port: {{ $port }}
{{- $addr_obj := where $.container.Addresses "Port" $port | first }} {{- $addr_obj := where $.container.Addresses "Port" $port | first }}
{{- if and $addr_obj $addr_obj.HostPort }} {{- if and $addr_obj $addr_obj.HostPort }}
@ -193,7 +155,7 @@
ssl_prefer_server_ciphers off; ssl_prefer_server_ciphers off;
{{- else if eq .ssl_policy "Mozilla-Old" }} {{- else if eq .ssl_policy "Mozilla-Old" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-TLS13-1-3-2021-06" }} {{- else if eq .ssl_policy "AWS-TLS13-1-3-2021-06" }}
ssl_protocols TLSv1.3; ssl_protocols TLSv1.3;
@ -222,11 +184,11 @@
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-TLS13-1-1-2021-06" }} {{- else if eq .ssl_policy "AWS-TLS13-1-1-2021-06" }}
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-TLS13-1-0-2021-06" }} {{- else if eq .ssl_policy "AWS-TLS13-1-0-2021-06" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-FS-1-2-Res-2020-10" }} {{- else if eq .ssl_policy "AWS-FS-1-2-Res-2020-10" }}
ssl_protocols TLSv1.2; ssl_protocols TLSv1.2;
@ -242,11 +204,11 @@
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-FS-1-1-2019-08" }} {{- else if eq .ssl_policy "AWS-FS-1-1-2019-08" }}
ssl_protocols TLSv1.1 TLSv1.2; ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-FS-2018-06" }} {{- else if eq .ssl_policy "AWS-FS-2018-06" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-TLS-1-2-Ext-2018-06" }} {{- else if eq .ssl_policy "AWS-TLS-1-2-Ext-2018-06" }}
ssl_protocols TLSv1.2; ssl_protocols TLSv1.2;
@ -258,23 +220,23 @@
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-TLS-1-1-2017-01" }} {{- else if eq .ssl_policy "AWS-TLS-1-1-2017-01" }}
ssl_protocols TLSv1.1 TLSv1.2; ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-2016-08" }} {{- else if eq .ssl_policy "AWS-2016-08" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-2015-05" }} {{- else if eq .ssl_policy "AWS-2015-05" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DES-CBC3-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DES-CBC3-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-2015-03" }} {{- else if eq .ssl_policy "AWS-2015-03" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- else if eq .ssl_policy "AWS-2015-02" }} {{- else if eq .ssl_policy "AWS-2015-02" }}
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:@SECLEVEL=0'; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
{{- end }} {{- end }}
{{- end }} {{- end }}
@ -302,14 +264,8 @@
include uwsgi_params; include uwsgi_params;
uwsgi_pass {{ trim $proto }}://{{ trim $upstream }}; uwsgi_pass {{ trim $proto }}://{{ trim $upstream }};
{{- else if eq $proto "fastcgi" }} {{- else if eq $proto "fastcgi" }}
{{- if (exists "/etc/nginx/fastcgi.conf") }}
include fastcgi.conf;
{{- else if (exists "/etc/nginx/fastcgi_params") }}
include fastcgi_params;
{{- else }}
# neither /etc/nginx/fastcgi.conf nor /etc/nginx/fastcgi_params found, fastcgi won't work
{{- end }}
root {{ trim .VhostRoot }}; root {{ trim .VhostRoot }};
include fastcgi_params;
fastcgi_pass {{ trim $upstream }}; fastcgi_pass {{ trim $upstream }};
{{- if ne $keepalive "disabled" }} {{- if ne $keepalive "disabled" }}
fastcgi_keep_conn on; fastcgi_keep_conn on;
@ -327,7 +283,7 @@
auth_basic "Restricted {{ .Host }}{{ .Path }}"; auth_basic "Restricted {{ .Host }}{{ .Path }}";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s_%s" .Host (sha1 .Path)) }}; auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s_%s" .Host (sha1 .Path)) }};
{{- else if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }} {{- else if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }}
auth_basic "Restricted {{ .HostIsRegexp | ternary "access" .Host }}"; auth_basic "Restricted {{ .Host }}";
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }}; auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }};
{{- end }} {{- end }}
@ -382,76 +338,65 @@ upstream {{ $vpath.upstream }} {
} }
{{- end }} {{- end }}
{{- /* debug "endpoint" location template */}}
{{- define "debug_location" }}
{{- $debug_paths := dict }}
{{- range $path, $vpath := .VHost.paths }}
{{- $tmp_ports := dict }}
{{- range $port, $containers := $vpath.ports }}
{{- $tmp_containers := list }}
{{- range $container := $containers }}
{{- $tmp_containers = dict "Name" $container.Name | append $tmp_containers }}
{{- end }}
{{- $_ := set $tmp_ports $port $tmp_containers }}
{{- end }}
{{- $debug_vpath := deepCopy $vpath | merge (dict "ports" $tmp_ports) }}
{{- $_ := set $debug_paths $path $debug_vpath }}
{{- end }}
{{- $debug_vhost := deepCopy .VHost }}
{{- /* If it's a regexp, do not render the Hostname to the response to avoid rendering config breaking characters */}}
{{- $_ := set $debug_vhost "hostname" (.VHost.is_regexp | ternary "Hostname is a regexp and unsafe to include in the debug response." .Hostname) }}
{{- $_ := set $debug_vhost "paths" $debug_paths }}
{{- $debug_response := dict
"global" .GlobalConfig
"request" (dict
"host" "$host"
"https" "$https"
"http2" "$http2"
"http3" "$http3"
"ssl_cipher" "$ssl_cipher"
"ssl_protocol" "$ssl_protocol"
)
"vhost" $debug_vhost
}}
{{- /* {{- /*
* The maximum line length in an nginx config is 4096 characters. * Template used as a function to collect virtual path properties from
* If we're nearing this limit (with headroom for the rest * the given containers. These properties are "returned" by storing their
* of the directive), strip vhost.paths from the response. * values into the provided dot dict.
*
* The provided dot dict is expected to have the following entries:
* - "Containers": List of container's RuntimeContainer struct.
* - "Upstream_name"
* - "Has_virtual_paths": boolean
* - "Path"
*
* The return values will be added to the dot dict with keys:
* - "dest"
* - "proto"
* - "network_tag"
* - "upstream"
* - "loadbalance"
* - "keepalive"
*/}} */}}
{{- if gt (toJson $debug_response | len) 4000 }} {{- define "get_path_info" }}
{{- $_ := unset $debug_vhost "paths" }} {{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}}
{{- $_ := set $debug_response "warning" "Virtual paths configuration for this hostname is too large and has been stripped from response." }} {{- $proto := trim (or (first (groupByKeys $.Containers "Env.VIRTUAL_PROTO")) "http") }}
{{- end }} {{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}}
{{- $network_tag := or (first (groupByKeys $.Containers "Env.NETWORK_ACCESS")) "external" }}
location /nginx-proxy-debug { {{- $loadbalance := first (keys (groupByLabel $.Containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }}
default_type application/json; {{- $keepalive := coalesce (first (keys (groupByLabel $.Containers "com.github.nginx-proxy.nginx-proxy.keepalive"))) "disabled" }}
return 200 '{{ toJson $debug_response }}{{ "\\n" }}';
}
{{- end }}
{{- define "access_log" }} {{- $upstream := $.Upstream_name }}
{{- when .Enable "access_log /var/log/nginx/access.log vhost;" "" }} {{- $dest := "" }}
{{- if $.Has_virtual_paths }}
{{- $sum := sha1 $.Path }}
{{- $upstream = printf "%s-%s" $upstream $sum }}
{{- $dest = or (first (groupByKeys $.Containers "Env.VIRTUAL_DEST")) "" }}
{{- end }}
{{- $_ := set $ "proto" $proto }}
{{- $_ := set $ "network_tag" $network_tag }}
{{- $_ := set $ "upstream" $upstream }}
{{- $_ := set $ "dest" $dest }}
{{- $_ := set $ "loadbalance" $loadbalance }}
{{- $_ := set $ "keepalive" $keepalive }}
{{- end }} {{- end }}
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the # If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server # scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto { map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default {{ if $globals.config.trust_downstream_proxy }}$http_x_forwarded_proto{{ else }}$scheme{{ end }}; default {{ if $globals.trust_downstream_proxy }}$http_x_forwarded_proto{{ else }}$scheme{{ end }};
'' $scheme; '' $scheme;
} }
map $http_x_forwarded_host $proxy_x_forwarded_host { map $http_x_forwarded_host $proxy_x_forwarded_host {
default {{ if $globals.config.trust_downstream_proxy }}$http_x_forwarded_host{{ else }}$host{{ end }}; default {{ if $globals.trust_downstream_proxy }}$http_x_forwarded_host{{ else }}$host{{ end }};
'' $host; '' $host;
} }
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the # If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to # server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port { map $http_x_forwarded_port $proxy_x_forwarded_port {
default {{ if $globals.config.trust_downstream_proxy }}$http_x_forwarded_port{{ else }}$server_port{{ end }}; default {{ if $globals.trust_downstream_proxy }}$http_x_forwarded_port{{ else }}$server_port{{ end }};
'' $server_port; '' $server_port;
} }
@ -512,38 +457,27 @@ gzip_types text/plain text/css application/javascript application/json applicati
* LOG_FORMAT_ESCAPE sets the escape part of the log format * LOG_FORMAT_ESCAPE sets the escape part of the log format
* LOG_FORMAT sets the log format * LOG_FORMAT sets the log format
*/}} */}}
{{- $logEscape := $globals.config.log_format_escape | default "default" | printf "escape=%s" }} {{- $logEscape := printf "escape=%s" (or $globals.Env.LOG_FORMAT_ESCAPE "default") }}
{{- $logFormat := $globals.config.log_format | default `$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$upstream_addr"` }} {{- $logFormat := or $globals.Env.LOG_FORMAT `$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$upstream_addr"` }}
{{- if $globals.config.enable_json_logs }} {{- if parseBool (or $globals.Env.LOG_JSON "false") }}
{{- /* LOG_JSON is a shorthand
* that sets logging defaults to JSON format
*/}}
# JSON Logging enabled (via LOG_JSON env variable) # JSON Logging enabled (via LOG_JSON env variable)
{{- $logEscape = $globals.config.log_format_escape | default "json" | printf "escape=%s" }} {{- $logEscape = printf "escape=%s" (or $globals.Env.LOG_FORMAT_ESCAPE "json") }}
{{- $logFormat = $globals.config.log_format | default `{"time_local":"$time_iso8601","client_ip":"$http_x_forwarded_for","remote_addr":"$remote_addr","request":"$request","status":"$status","body_bytes_sent":"$body_bytes_sent","request_time":"$request_time","upstream_response_time":"$upstream_response_time","upstream_addr":"$upstream_addr","http_referrer":"$http_referer","http_user_agent":"$http_user_agent","request_id":"$request_id"}` }} {{- $logFormat = or $globals.Env.LOG_FORMAT `{"time_local":"$time_iso8601","client_ip":"$http_x_forwarded_for","remote_addr":"$remote_addr","request":"$request","status":"$status","body_bytes_sent":"$body_bytes_sent","request_time":"$request_time","upstream_response_time":"$upstream_response_time","upstream_addr":"$upstream_addr","http_referrer":"$http_referer","http_user_agent":"$http_user_agent","request_id":"$request_id"}` }}
{{- end }} {{- end }}
log_format vhost {{ $logEscape }} '{{ $logFormat }}'; log_format vhost {{ $logEscape }} '{{ or $globals.Env.LOG_FORMAT $logFormat }}';
access_log off; access_log off;
{{- /* Lower the SSL policy of the http context {{- template "ssl_policy" (dict "ssl_policy" $globals.ssl_policy) }}
* if at least one vhost use a TLSv1 or TLSv1.1 policy
* so TLSv1 and TLSv1.1 can be enabled on those vhosts
*/}}
{{- $httpContextSslPolicy := $globals.config.ssl_policy }}
{{- $inUseSslPolicies := groupByKeys $globals.containers "Env.SSL_POLICY" }}
{{- range $tls1Policy := list "AWS-TLS13-1-1-2021-06" "AWS-TLS13-1-0-2021-06" "AWS-FS-1-1-2019-08" "AWS-FS-2018-06" "AWS-TLS-1-1-2017-01" "AWS-2016-08" "AWS-2015-05" "AWS-2015-03" "AWS-2015-02" "Mozilla-Old" }}
{{- if has $tls1Policy $inUseSslPolicies }}
# Using Mozilla-Old SSL policy on the http context to allow TLSv1 and TLSv1.1
{{- $httpContextSslPolicy = "Mozilla-Old" }}
{{- break }}
{{- end }}
{{- end }}
{{- template "ssl_policy" (dict "ssl_policy" $httpContextSslPolicy) }}
error_log /dev/stderr; error_log /dev/stderr;
{{- if $globals.config.resolvers }} {{- if $globals.Env.RESOLVERS }}
resolver {{ $globals.config.resolvers }}; resolver {{ $globals.Env.RESOLVERS }};
{{- end }} {{- end }}
{{- if (exists "/etc/nginx/proxy.conf") }} {{- if (exists "/etc/nginx/proxy.conf") }}
@ -566,224 +500,79 @@ proxy_set_header X-Original-URI $request_uri;
proxy_set_header Proxy ""; proxy_set_header Proxy "";
{{- end }} {{- end }}
{{- /* Precompute and store some information about vhost that use VIRTUAL_HOST_MULTIPORTS. */}} {{- /* Precompute some information about each vhost. */}}
{{- range $vhosts_yaml, $containers := groupBy $globals.containers "Env.VIRTUAL_HOST_MULTIPORTS" }}
{{- /* Print a warning in the config if VIRTUAL_HOST_MULTIPORTS can't be parsed. */}}
{{- $parsedVhosts := fromYaml $vhosts_yaml }}
{{- if (empty $parsedVhosts) }}
{{- $containerNames := list }}
{{- range $container := $containers }}
{{- $containerNames = append $containerNames $container.Name }}
{{- end }}
# /!\ WARNING: the VIRTUAL_HOST_MULTIPORTS environment variable used for {{ len $containerNames | plural "this container" "those containers" }} is not a valid YAML string:
# {{ $containerNames | join ", " }}
{{- continue }}
{{- end }}
{{- range $hostname, $vhost := $parsedVhosts }}
{{- $vhost_data := get $globals.vhosts $hostname | default (dict) }}
{{- $paths := $vhost_data.paths | default (dict) }}
{{- if (empty $vhost) }}
{{ $vhost = dict "/" (dict) }}
{{- end }}
{{- range $path, $vpath := $vhost }}
{{- if (empty $vpath) }}
{{- $vpath = dict
"dest" ""
"port" "default"
"proto" "http"
}}
{{- end }}
{{- $dest := $vpath.dest | default "" }}
{{- $port := $vpath.port | default "default" | toString }}
{{- $proto := $vpath.proto | default "http" }}
{{- $path_data := get $paths $path | default (dict) }}
{{- $path_ports := $path_data.ports | default (dict) }}
{{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }}
{{- $_ := set $path_ports $port $path_port_containers }}
{{- $_ := set $path_data "ports" $path_ports }}
{{- if (not (hasKey $path_data "dest")) }}
{{- $_ := set $path_data "dest" $dest }}
{{- end }}
{{- if (not (hasKey $path_data "proto")) }}
{{- $_ := set $path_data "proto" $proto }}
{{- end }}
{{- $_ := set $paths $path $path_data }}
{{- end }}
{{- $_ := set $vhost_data "paths" $paths }}
{{- $_ := set $globals.vhosts $hostname $vhost_data }}
{{- end }}
{{- end }}
{{- /* Precompute and store some information about vhost that use VIRTUAL_HOST. */}}
{{- range $hostname, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }} {{- range $hostname, $containers := groupByMulti $globals.containers "Env.VIRTUAL_HOST" "," }}
{{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}}
{{- $hostname = trim $hostname }} {{- $hostname = trim $hostname }}
{{- if not $hostname }} {{- if not $hostname }}
{{- /* Ignore containers with VIRTUAL_HOST set to the empty string. */}}
{{- continue }} {{- continue }}
{{- end }} {{- end }}
{{- /* Drop containers with both VIRTUAL_HOST and VIRTUAL_HOST_MULTIPORTS set {{- $certName := first (groupByKeys $containers "Env.CERT_NAME") }}
* (VIRTUAL_HOST_MULTIPORTS takes precedence thanks to the previous loop). {{- $vhostCert := closest (dir "/etc/nginx/certs") (printf "%s.crt" $hostname) }}
*/}} {{- $vhostCert = trimSuffix ".crt" $vhostCert }}
{{- range $_, $containers_to_drop := groupBy $containers "Env.VIRTUAL_HOST_MULTIPORTS" }} {{- $vhostCert = trimSuffix ".key" $vhostCert }}
{{- range $container := $containers_to_drop }} {{- $cert := or $certName $vhostCert }}
{{- $containers = without $containers $container }}
{{- end }}
{{- end }}
{{- if (eq (len $containers) 0) }}
{{- continue }}
{{- end }}
{{- $vhost_data := get $globals.vhosts $hostname | default (dict) }}
{{- $paths := $vhost_data.paths | default (dict) }}
{{- $tmp_paths := groupByWithDefault $containers "Env.VIRTUAL_PATH" "/" }}
{{- range $path, $containers := $tmp_paths }}
{{- $dest := groupByKeys $containers "Env.VIRTUAL_DEST" | first | default "" }}
{{- $proto := groupByKeys $containers "Env.VIRTUAL_PROTO" | first | default "http" | trim }}
{{- $path_data := get $paths $path | default (dict) }}
{{- $path_ports := $path_data.ports | default (dict) }}
{{- range $port, $containers := groupByWithDefault $containers "Env.VIRTUAL_PORT" "default" }}
{{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }}
{{- $_ := set $path_ports $port $path_port_containers }}
{{- end }}
{{- $_ := set $path_data "ports" $path_ports }}
{{- if (not (hasKey $path_data "dest")) }}
{{- $_ := set $path_data "dest" $dest }}
{{- end }}
{{- if (not (hasKey $path_data "proto")) }}
{{- $_ := set $path_data "proto" $proto }}
{{- end }}
{{- $_ := set $paths $path $path_data }}
{{- end }}
{{- $_ := set $vhost_data "paths" $paths }}
{{- $_ := set $globals.vhosts $hostname $vhost_data }}
{{- end }}
{{- /* Loop over $globals.vhosts and update it with the remaining informations about each vhost. */}}
{{- range $hostname, $vhost_data := $globals.vhosts }}
{{- $is_regexp := hasPrefix "~" $hostname }}
{{- $upstream_name := or $is_regexp $globals.config.sha1_upstream_name | ternary (sha1 $hostname) $hostname }}
{{- $vhost_containers := list }}
{{- range $path, $vpath_data := $vhost_data.paths }}
{{- $vpath_containers := list }}
{{- range $port, $vport_containers := $vpath_data.ports }}
{{ $vpath_containers = concat $vpath_containers $vport_containers }}
{{- end }}
{{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}}
{{- $network_tag := groupByKeys $vpath_containers "Env.NETWORK_ACCESS" | first | default "external" }}
{{- $loadbalance := groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.loadbalance" | keys | first }}
{{- $keepalive := groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.keepalive" | keys | first | default "auto" }}
{{- $upstream := $upstream_name }}
{{- if (not (eq $path "/")) }}
{{- $sum := sha1 $path }}
{{- $upstream = printf "%s-%s" $upstream $sum }}
{{- end }}
{{- $_ := set $vpath_data "network_tag" $network_tag }}
{{- $_ := set $vpath_data "upstream" $upstream }}
{{- $_ := set $vpath_data "loadbalance" $loadbalance }}
{{- $_ := set $vpath_data "keepalive" $keepalive }}
{{- $_ := set $vhost_data.paths $path $vpath_data }}
{{ $vhost_containers = concat $vhost_containers $vpath_containers }}
{{- end }}
{{- $userIdentifiedCert := groupByKeys $vhost_containers "Env.CERT_NAME" | first }}
{{- $vhostCert := "" }}
{{- if exists (printf "/etc/nginx/certs/%s.crt" $hostname) }}
{{- $vhostCert = $hostname }}
{{- end }}
{{- $parentVhostCert := "" }}
{{- if gt ($hostname | sprigSplit "." | len) 2 }}
{{- $parentHostname := ($hostname | sprigSplitn "." 2)._1 }}
{{- if exists (printf "/etc/nginx/certs/%s.crt" $parentHostname) }}
{{- $parentVhostCert = $parentHostname }}
{{- end }}
{{- end }}
{{- $trust_default_cert := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.trust-default-cert" | keys | first | default $globals.config.trust_default_cert | parseBool }}
{{- $defaultCert := and $trust_default_cert $globals.config.default_cert_ok | ternary "default" "" }}
{{- $cert := or $userIdentifiedCert $vhostCert $parentVhostCert $defaultCert }}
{{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }} {{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }}
{{- $enable_debug_endpoint := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.debug-endpoint" | keys | first | default $globals.config.enable_debug_endpoint | parseBool }} {{- $default := eq $globals.Env.DEFAULT_HOST $hostname }}
{{- $default := eq $globals.config.default_host $hostname }} {{- $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) $globals.Env.HTTPS_METHOD "redirect" }}
{{- $https_method := groupByKeys $vhost_containers "Env.HTTPS_METHOD" | first | default $globals.config.https_method }} {{- $http2_enabled := parseBool (or (first (keys (groupByLabel $containers "com.github.nginx-proxy.nginx-proxy.http2.enable"))) $globals.Env.ENABLE_HTTP2 "true")}}
{{- $enable_http_on_missing_cert := groupByKeys $vhost_containers "Env.ENABLE_HTTP_ON_MISSING_CERT" | first | default $globals.config.enable_http_on_missing_cert | parseBool }} {{- $http3_enabled := parseBool (or (first (keys (groupByLabel $containers "com.github.nginx-proxy.nginx-proxy.http3.enable"))) $globals.Env.ENABLE_HTTP3 "false")}}
{{- /* When no trusted certs (default and/or vhost) are present we want to ensure that HTTP is enabled; hence switching from 'nohttp' or 'redirect' to 'noredirect' */}}
{{- $https_method_disable_http := list "nohttp" "redirect" | has $https_method }}
{{- if and $https_method_disable_http (not $cert_ok) $enable_http_on_missing_cert }}
{{- $https_method = "noredirect" }}
{{- end }}
{{- $non_get_redirect := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.non-get-redirect" | keys | first | default $globals.config.non_get_redirect }}
{{- $http2_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable" | keys | first | default $globals.config.enable_http2 | parseBool }} {{- $is_regexp := hasPrefix "~" $hostname }}
{{- $http3_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable" | keys | first | default $globals.config.enable_http3 | parseBool }} {{- $upstream_name := when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname }}
{{- $acme_http_challenge := groupByKeys $vhost_containers "Env.ACME_HTTP_CHALLENGE_LOCATION" | first | default $globals.config.acme_http_challenge }}
{{- $acme_http_challenge_legacy := eq $acme_http_challenge "legacy" }}
{{- $acme_http_challenge_enabled := false }}
{{- if (not $acme_http_challenge_legacy) }}
{{- $acme_http_challenge_enabled = parseBool $acme_http_challenge }}
{{- end }}
{{- /* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "". */}} {{- /* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "". */}}
{{- $server_tokens := groupByKeys $vhost_containers "Env.SERVER_TOKENS" | first | default "" | trim }} {{- $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }}
{{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}} {{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}}
{{- $ssl_policy := groupByKeys $vhost_containers "Env.SSL_POLICY" | first | default "" }} {{- $ssl_policy := or (first (groupByKeys $containers "Env.SSL_POLICY")) "" }}
{{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}} {{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}}
{{- $hsts := groupByKeys $vhost_containers "Env.HSTS" | first | default $globals.config.hsts }} {{- $hsts := or (first (groupByKeys $containers "Env.HSTS")) (or $globals.Env.HSTS "max-age=31536000") }}
{{- /* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}} {{- /* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}}
{{- $vhost_root := groupByKeys $vhost_containers "Env.VIRTUAL_ROOT" | first | default "/var/www/public" }} {{- $vhost_root := or (first (groupByKeys $containers "Env.VIRTUAL_ROOT")) "/var/www/public" }}
{{- $vhost_data = merge $vhost_data (dict
{{- $tmp_paths := groupBy $containers "Env.VIRTUAL_PATH" }}
{{- $has_virtual_paths := gt (len $tmp_paths) 0}}
{{- if not $has_virtual_paths }}
{{- $tmp_paths = dict "/" $containers }}
{{- end }}
{{- $paths := dict }}
{{- range $path, $containers := $tmp_paths }}
{{- $args := dict "Containers" $containers "Path" $path "Upstream_name" $upstream_name "Has_virtual_paths" $has_virtual_paths }}
{{- template "get_path_info" $args }}
{{- $_ := set $paths $path (dict
"ports" (dict "legacy" $containers)
"dest" $args.dest
"proto" $args.proto
"network_tag" $args.network_tag
"upstream" $args.upstream
"loadbalance" $args.loadbalance
"keepalive" $args.keepalive
) }}
{{- end }}
{{- $_ := set $globals.vhosts $hostname (dict
"cert" $cert "cert" $cert
"cert_ok" $cert_ok "cert_ok" $cert_ok
"enable_debug_endpoint" $enable_debug_endpoint
"default" $default "default" $default
"hsts" $hsts "hsts" $hsts
"https_method" $https_method "https_method" $https_method
"non_get_redirect" $non_get_redirect
"http2_enabled" $http2_enabled "http2_enabled" $http2_enabled
"http3_enabled" $http3_enabled "http3_enabled" $http3_enabled
"is_regexp" $is_regexp "paths" $paths
"acme_http_challenge_legacy" $acme_http_challenge_legacy
"acme_http_challenge_enabled" $acme_http_challenge_enabled
"server_tokens" $server_tokens "server_tokens" $server_tokens
"ssl_policy" $ssl_policy "ssl_policy" $ssl_policy
"trust_default_cert" $trust_default_cert
"upstream_name" $upstream_name
"vhost_root" $vhost_root "vhost_root" $vhost_root
) }} ) }}
{{- $_ := set $globals.vhosts $hostname $vhost_data }}
{{- end }} {{- end }}
{{- /* {{- /*
* If needed, create a catch-all fallback server to send an error code to * If needed, create a catch-all fallback server to send an error code to
* clients that request something from an unknown vhost. * clients that request something from an unknown vhost.
@ -805,7 +594,7 @@ proxy_set_header Proxy "";
{{- $default_https_exists := false }} {{- $default_https_exists := false }}
{{- $http3_enabled := false }} {{- $http3_enabled := false }}
{{- range $vhost := $globals.vhosts }} {{- range $vhost := $globals.vhosts }}
{{- $http := ne $vhost.https_method "nohttp" }} {{- $http := or (ne $vhost.https_method "nohttp") (not $vhost.cert_ok) }}
{{- $https := ne $vhost.https_method "nohttps" }} {{- $https := ne $vhost.https_method "nohttps" }}
{{- $http_exists = or $http_exists $http }} {{- $http_exists = or $http_exists $http }}
{{- $https_exists = or $https_exists $https }} {{- $https_exists = or $https_exists $https }}
@ -813,62 +602,61 @@ proxy_set_header Proxy "";
{{- $default_https_exists = or $default_https_exists (and $https $vhost.default) }} {{- $default_https_exists = or $default_https_exists (and $https $vhost.default) }}
{{- $http3_enabled = or $http3_enabled $vhost.http3_enabled }} {{- $http3_enabled = or $http3_enabled $vhost.http3_enabled }}
{{- end }} {{- end }}
{{- $fallback_http := not $default_http_exists }} {{- $fallback_http := and $http_exists (not $default_http_exists) }}
{{- $fallback_https := not $default_https_exists }} {{- $fallback_https := and $https_exists (not $default_https_exists) }}
{{- /* {{- /*
* If there are no vhosts at all, create fallbacks for both plain http * If there are no vhosts at all, create fallbacks for both plain http
* and https so that clients get something more useful than a connection * and https so that clients get something more useful than a connection
* refused error. * refused error.
*/}} */}}
{{- if and (not $http_exists) (not $https_exists) }} {{- if and (not $http_exists) (not $https_exists) }}
{{- $fallback_http = true }}
{{- $fallback_https = true }} {{- $fallback_https = true }}
{{- end }} {{- end }}
{{- if or $fallback_http $fallback_https }} {{- if or $fallback_http $fallback_https }}
server { server {
server_name _; # This is just an invalid value which will never trigger on a real hostname. server_name _; # This is just an invalid value which will never trigger on a real hostname.
server_tokens off; server_tokens off;
{{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} {{ $globals.access_log }}
http2 on; http2 on;
{{- if $fallback_http }} {{- if $fallback_http }}
listen {{ $globals.config.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} listen {{ $globals.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}}
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} listen [::]:{{ $globals.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if $fallback_https }} {{- if $fallback_https }}
listen {{ $globals.config.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} listen {{ $globals.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}}
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} listen [::]:{{ $globals.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}}
{{- end }} {{- end }}
{{- if $http3_enabled }} {{- if $http3_enabled }}
http3 on; http3 on;
listen {{ $globals.config.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} listen {{ $globals.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}}
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} listen [::]:{{ $globals.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}}
{{- end }} {{- end }}
{{- end }} {{- end }}
ssl_session_cache shared:SSL:50m; ssl_session_cache shared:SSL:50m;
ssl_session_tickets off; ssl_session_tickets off;
{{- end }} {{- end }}
{{- if $globals.config.default_cert_ok }} {{- if $globals.default_cert_ok }}
ssl_certificate /etc/nginx/certs/default.crt; ssl_certificate /etc/nginx/certs/default.crt;
ssl_certificate_key /etc/nginx/certs/default.key; ssl_certificate_key /etc/nginx/certs/default.key;
{{- else }} {{- else }}
# No default certificate found, so reject SSL handshake; # No default.crt certificate found for this vhost, so force nginx to emit a
ssl_reject_handshake on; # TLS error if the client connects via https.
{{- end }} {{- /* See the comment in the main `server` directive for rationale. */}}
ssl_ciphers aNULL;
{{- if (exists "/usr/share/nginx/html/errors/50x.html") }} set $empty "";
error_page 500 502 503 504 /50x.html; ssl_certificate data:$empty;
location /50x.html { ssl_certificate_key data:$empty;
root /usr/share/nginx/html/errors; if ($https) {
internal; return 444;
} }
{{- end }} {{- end }}
location ^~ / {
return 503; return 503;
} }
}
{{- end }} {{- end }}
{{- end }} {{- end }}
@ -880,19 +668,18 @@ server {
{{ template "upstream" (dict "globals" $globals "Path" $path "VPath" $vpath) }} {{ template "upstream" (dict "globals" $globals "Path" $path "VPath" $vpath) }}
{{- end }} {{- end }}
{{- if (eq $vhost.https_method "redirect") }} {{- if and $vhost.cert_ok (eq $vhost.https_method "redirect") }}
server { server {
server_name {{ $hostname }}; server_name {{ $hostname }};
{{- if $vhost.server_tokens }} {{- if $vhost.server_tokens }}
server_tokens {{ $vhost.server_tokens }}; server_tokens {{ $vhost.server_tokens }};
{{- end }} {{- end }}
{{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} {{ $globals.access_log }}
listen {{ $globals.config.external_http_port }} {{ $default_server }}; listen {{ $globals.external_http_port }} {{ $default_server }};
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_http_port }} {{ $default_server }}; listen [::]:{{ $globals.external_http_port }} {{ $default_server }};
{{- end }} {{- end }}
{{- if (or $vhost.acme_http_challenge_legacy $vhost.acme_http_challenge_enabled) }}
# Do not HTTPS redirect Let's Encrypt ACME challenge # Do not HTTPS redirect Let's Encrypt ACME challenge
location ^~ /.well-known/acme-challenge/ { location ^~ /.well-known/acme-challenge/ {
auth_basic off; auth_basic off;
@ -902,79 +689,44 @@ server {
try_files $uri =404; try_files $uri =404;
break; break;
} }
{{- end }}
{{- if $vhost.enable_debug_endpoint }}
{{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }}
{{- end }}
location / { location / {
{{- $redirect_uri := "https://$host$request_uri" }} {{- if eq $globals.external_https_port "443" }}
{{- if ne $globals.config.external_https_port "443" }} return 301 https://$host$request_uri;
{{- $redirect_uri = printf "https://$host:%s$request_uri" $globals.config.external_https_port }} {{- else }}
return 301 https://$host:{{ $globals.external_https_port }}$request_uri;
{{- end }} {{- end }}
if ($request_method ~ (OPTIONS|POST|PUT|PATCH|DELETE)) {
return {{ $vhost.non_get_redirect }} {{ $redirect_uri }};
}
return 301 {{ $redirect_uri }};
} }
} }
{{- end }} {{- end }}
server { server {
{{- if $vhost.is_regexp }}
{{- if or
(printf "/etc/nginx/vhost.d/%s" $hostname | exists)
(printf "/etc/nginx/vhost.d/%s_location" $hostname | exists)
(printf "/etc/nginx/vhost.d/%s_location_override" $hostname | exists)
(printf "/etc/nginx/htpasswd/%s" $hostname | exists)
}}
# https://github.com/nginx-proxy/nginx-proxy/issues/2529#issuecomment-2437609249
# Support for vhost config file(s) named like a regexp ({{ $hostname }}) has been removed from nginx-proxy.
# Please name your vhost config file(s) with the sha1 of the regexp instead ({{ $hostname }} -> {{ sha1 $hostname }}) :
# - /etc/nginx/vhost.d/{{ sha1 $hostname }}
# - /etc/nginx/vhost.d/{{ sha1 $hostname }}_location
# - /etc/nginx/vhost.d/{{ sha1 $hostname }}_location_override
# - /etc/nginx/htpasswd/{{ sha1 $hostname }}
{{- end }}
{{- end }}
server_name {{ $hostname }}; server_name {{ $hostname }};
{{- if $vhost.server_tokens }} {{- if $vhost.server_tokens }}
server_tokens {{ $vhost.server_tokens }}; server_tokens {{ $vhost.server_tokens }};
{{- end }} {{- end }}
{{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} {{ $globals.access_log }}
{{- if $vhost.http2_enabled }} {{- if $vhost.http2_enabled }}
http2 on; http2 on;
{{- end }} {{- end }}
{{- if or (eq $vhost.https_method "nohttps") (eq $vhost.https_method "noredirect") }} {{- if or (eq $vhost.https_method "nohttps") (not $vhost.cert_ok) (eq $vhost.https_method "noredirect") }}
listen {{ $globals.config.external_http_port }} {{ $default_server }}; listen {{ $globals.external_http_port }} {{ $default_server }};
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_http_port }} {{ $default_server }}; listen [::]:{{ $globals.external_http_port }} {{ $default_server }};
{{- end }}
{{- if (and (eq $vhost.https_method "noredirect") $vhost.acme_http_challenge_enabled) }}
location /.well-known/acme-challenge/ {
auth_basic off;
allow all;
root /usr/share/nginx/html;
try_files $uri =404;
break;
}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- if ne $vhost.https_method "nohttps" }} {{- if ne $vhost.https_method "nohttps" }}
listen {{ $globals.config.external_https_port }} ssl {{ $default_server }}; listen {{ $globals.external_https_port }} ssl {{ $default_server }};
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_https_port }} ssl {{ $default_server }}; listen [::]:{{ $globals.external_https_port }} ssl {{ $default_server }};
{{- end }} {{- end }}
{{- if $vhost.http3_enabled }} {{- if $vhost.http3_enabled }}
http3 on; http3 on;
add_header alt-svc 'h3=":{{ $globals.config.external_https_port }}"; ma=86400;'; add_header alt-svc 'h3=":{{ $globals.external_https_port }}"; ma=86400;';
listen {{ $globals.config.external_https_port }} quic {{ $default_server }}; listen {{ $globals.external_https_port }} quic {{ $default_server }};
{{- if $globals.config.enable_ipv6 }} {{- if $globals.enable_ipv6 }}
listen [::]:{{ $globals.config.external_https_port }} quic {{ $default_server }}; listen [::]:{{ $globals.external_https_port }} quic {{ $default_server }};
{{- end }} {{- end }}
{{- end }} {{- end }}
@ -1005,40 +757,54 @@ server {
} }
add_header Strict-Transport-Security $sts_header always; add_header Strict-Transport-Security $sts_header always;
{{- end }} {{- end }}
{{- else if not $vhost.trust_default_cert | and $globals.config.default_cert_ok }} {{- else if $globals.default_cert_ok }}
# No certificate found for this vhost, and the default certificate isn't trusted, so reject SSL handshake. # No certificate found for this vhost, so use the default certificate and
ssl_reject_handshake on; # return an error code if the user connects via https.
ssl_certificate /etc/nginx/certs/default.crt;
ssl_certificate_key /etc/nginx/certs/default.key;
if ($https) {
return 500;
}
{{- else }} {{- else }}
# No certificate for this vhost nor default certificate found, so reject SSL handshake. # No certificate found for this vhost, so force nginx to emit a TLS error if
ssl_reject_handshake on; # the client connects via https.
{{- /*
* The alternative is to not provide an https server for this
* vhost, which would either cause the user to see the wrong
* vhost (if there is another vhost with a certificate) or a
* connection refused error (if there is no other vhost with a
* certificate). A TLS error is easier to troubleshoot, and is
* safer than serving the wrong vhost. Also see
* <https://serverfault.com/a/1044022>.
*/}}
ssl_ciphers aNULL;
set $empty "";
ssl_certificate data:$empty;
ssl_certificate_key data:$empty;
if ($https) {
return 444;
}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- $vhostFileName := $vhost.is_regexp | ternary (sha1 $hostname) $hostname }} {{- if (exists (printf "/etc/nginx/vhost.d/%s" $hostname)) }}
include {{ printf "/etc/nginx/vhost.d/%s" $hostname }};
{{- if (exists (printf "/etc/nginx/vhost.d/%s" $vhostFileName)) }}
include {{ printf "/etc/nginx/vhost.d/%s" $vhostFileName }};
{{- else if (exists "/etc/nginx/vhost.d/default") }} {{- else if (exists "/etc/nginx/vhost.d/default") }}
include /etc/nginx/vhost.d/default; include /etc/nginx/vhost.d/default;
{{- end }} {{- end }}
{{- if $vhost.enable_debug_endpoint }}
{{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }}
{{- end }}
{{- range $path, $vpath := $vhost.paths }} {{- range $path, $vpath := $vhost.paths }}
{{- template "location" (dict {{- template "location" (dict
"Path" $path "Path" $path
"Host" $vhostFileName "Host" $hostname
"HostIsRegexp" $vhost.is_regexp
"VhostRoot" $vhost.vhost_root "VhostRoot" $vhost.vhost_root
"VPath" $vpath "VPath" $vpath
) }} ) }}
{{- end }} {{- end }}
{{- if and (not (contains $vhost.paths "/")) (ne $globals.config.default_root_response "none")}} {{- if and (not (contains $vhost.paths "/")) (ne $globals.default_root_response "none")}}
location / { location / {
return {{ $globals.config.default_root_response }}; return {{ $globals.default_root_response }};
} }
{{- end }} {{- end }}
} }

View file

@ -57,39 +57,13 @@ This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.p
### docker_compose fixture ### docker_compose fixture
When using the `docker_compose` fixture in a test, pytest will try to start the [Docker Compose](https://docs.docker.com/compose/) services corresponding to the current test module, based on the test module filename. 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/).
By default, if your test module file is `test/test_subdir/test_example.py`, then the `docker_compose` fixture will try to load the following files, [merging them](https://docs.docker.com/reference/compose-file/merge/) in this order: Once the docker compose file found, the fixture will remove all containers, run `docker compose up`, and finally your test will be executed.
1. `test/compose.base.yml` 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:
2. `test/test_subdir/compose.base.override.yml` (if it exists)
3. `test/test_subdir/test_example.yml`
The fixture will run the _docker compose_ command with the `-f` option to load the given compose files. So you can test your docker compose file syntax by running it yourself with: docker compose -f test_example.yml up -d
docker compose -f test/compose.base.yml -f test/test_subdir/test_example.yml up -d
The first file contains the base configuration of the nginx-proxy container common to most tests:
```yaml
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:test
container_name: nginx-proxy
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- "80:80"
- "443:443"
```
The second optional file allow you to override this base configuration for all test modules in a subfolder.
The third file contains the services and overrides specific to a given test module.
This automatic merge can be bypassed by using a file named `test_example.base.yml` (instead of `test_example.yml`). When this file exist, it will be the only one used by the test and no merge with other compose files will automatically occur.
The `docker_compose` fixture also set the `PYTEST_MODULE_PATH` environment variable to the absolute path of the current test module directory, so it can be used to mount files or directory relatives to the current test.
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. 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.
@ -97,10 +71,7 @@ In your tests, you can use the `docker_compose` variable to query and command th
Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways: Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways:
Any domain name containing the substring `nginx-proxy` will resolve to `127.0.0.1` if the tests are executed on a Darwin (macOS) system, otherwise the IP address of the container that was created from the `nginxproxy/nginx-proxy:test` image. 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:
So, in tests, all the following domain names will resolve to either localhost or the nginx-proxy container's IP:
- `nginx-proxy` - `nginx-proxy`
- `nginx-proxy.com` - `nginx-proxy.com`
- `www.nginx-proxy.com` - `www.nginx-proxy.com`
@ -109,16 +80,14 @@ So, in tests, all the following domain names will resolve to either localhost or
- `whatever.nginx-proxyooooooo` - `whatever.nginx-proxyooooooo`
- ... - ...
Any domain name ending with `XXX.container.docker` will resolve to `127.0.0.1` if the tests are executed on a Darwin (macOS) system, otherwise the IP address of the container named `XXX`. Any domain name ending with `XXX.container.docker` will resolve to the IP address of the XXX container.
So, on a non-Darwin system:
- `web1.container.docker` will resolve to the IP address of the `web1` container - `web1.container.docker` will resolve to the IP address of the `web1` container
- `f00.web1.container.docker` will resolve to the IP address of the `web1` container - `f00.web1.container.docker` will resolve to the IP address of the `web1` container
- `anything.whatever.web2.container.docker` will resolve to the IP address of the `web2` container - `anything.whatever.web2.container.docker` will resolve to the IP address of the `web2` container
Otherwise, domain names are resoved as usual using your system DNS resolver. Otherwise, domain names are resoved as usual using your system DNS resolver.
### nginxproxy fixture ### nginxproxy fixture
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. 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.

View file

@ -24,7 +24,7 @@ fi
# Create a nginx container (which conveniently provides the `openssl` command) # Create a nginx container (which conveniently provides the `openssl` command)
############################################################################### ###############################################################################
CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.27.3) CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.25.3)
# Configure openssl # Configure openssl
docker exec $CONTAINER bash -c ' docker exec $CONTAINER bash -c '
mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null

View file

@ -1,35 +1,28 @@
import contextlib import contextlib
import logging import logging
import os import os
import pathlib
import platform
import re import re
import shlex import shlex
import socket import socket
import subprocess import subprocess
import time import time
from io import StringIO from typing import List
from typing import Iterator, List, Optional
import backoff import backoff
import docker.errors import docker
import pytest import pytest
import requests import requests
from _pytest.fixtures import FixtureRequest from _pytest._code.code import ReprExceptionInfo
from docker import DockerClient from distutils.version import LooseVersion
from docker.models.containers import Container from docker.models.containers import Container
from docker.models.networks import Network from requests.packages.urllib3.util.connection import HAS_IPV6
from packaging.version import Version
from requests import Response
from urllib3.util.connection import HAS_IPV6
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logging.getLogger('backoff').setLevel(logging.INFO) logging.getLogger('backoff').setLevel(logging.INFO)
logging.getLogger('DNS').setLevel(logging.DEBUG) logging.getLogger('DNS').setLevel(logging.DEBUG)
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN) logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
CA_ROOT_CERTIFICATE = pathlib.Path(__file__).parent.joinpath("certs/ca-root.crt") CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt')
PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "1" PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "1"
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4 FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
@ -47,9 +40,8 @@ test_container = 'nginx-proxy-pytest'
# #
############################################################################### ###############################################################################
@contextlib.contextmanager @contextlib.contextmanager
def ipv6(force_ipv6: bool = True): def ipv6(force_ipv6=True):
""" """
Meant to be used as a context manager to force IPv6 sockets: Meant to be used as a context manager to force IPv6 sockets:
@ -67,19 +59,19 @@ def ipv6(force_ipv6: bool = True):
FORCE_CONTAINER_IPV6 = False FORCE_CONTAINER_IPV6 = False
class RequestsForDocker: class requests_for_docker(object):
""" """
Proxy for calling methods of the requests module. Proxy for calling methods of the requests module.
When an HTTP response failed due to HTTP Error 404 or 502, retry a few times. When a HTTP response failed due to HTTP Error 404 or 502, retry a few times.
Provides method `get_conf` to extract the nginx-proxy configuration content. Provides method `get_conf` to extract the nginx-proxy configuration content.
""" """
def __init__(self): def __init__(self):
self.session = requests.Session() self.session = requests.Session()
if CA_ROOT_CERTIFICATE.is_file(): if os.path.isfile(CA_ROOT_CERTIFICATE):
self.session.verify = CA_ROOT_CERTIFICATE.as_posix() self.session.verify = CA_ROOT_CERTIFICATE
@staticmethod @staticmethod
def get_nginx_proxy_container() -> Container: def get_nginx_proxy_containers() -> List[Container]:
""" """
Return list of containers Return list of containers
""" """
@ -88,69 +80,69 @@ class RequestsForDocker:
pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False) pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False)
elif len(nginx_proxy_containers) == 0: elif len(nginx_proxy_containers) == 0:
pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False) pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False)
return nginx_proxy_containers.pop() return nginx_proxy_containers
def get_conf(self) -> bytes: def get_conf(self):
""" """
Return the nginx config file Return the nginx config file
""" """
nginx_proxy_container = self.get_nginx_proxy_container() nginx_proxy_containers = self.get_nginx_proxy_containers()
return get_nginx_conf_from_container(nginx_proxy_container) return get_nginx_conf_from_container(nginx_proxy_containers[0])
def get_ip(self) -> str: def get_ip(self) -> str:
""" """
Return the nginx container ip address Return the nginx container ip address
""" """
nginx_proxy_container = self.get_nginx_proxy_container() nginx_proxy_containers = self.get_nginx_proxy_containers()
return container_ip(nginx_proxy_container) return container_ip(nginx_proxy_containers[0])
def get(self, *args, **kwargs) -> Response: def get(self, *args, **kwargs):
with ipv6(kwargs.pop('ipv6', False)): with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _get(*_args, **_kwargs): def _get(*args, **kwargs):
return self.session.get(*_args, **_kwargs) return self.session.get(*args, **kwargs)
return _get(*args, **kwargs) return _get(*args, **kwargs)
def post(self, *args, **kwargs) -> Response: def post(self, *args, **kwargs):
with ipv6(kwargs.pop('ipv6', False)): with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _post(*_args, **_kwargs): def _post(*args, **kwargs):
return self.session.post(*_args, **_kwargs) return self.session.post(*args, **kwargs)
return _post(*args, **kwargs) return _post(*args, **kwargs)
def put(self, *args, **kwargs) -> Response: def put(self, *args, **kwargs):
with ipv6(kwargs.pop('ipv6', False)): with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _put(*_args, **_kwargs): def _put(*args, **kwargs):
return self.session.put(*_args, **_kwargs) return self.session.put(*args, **kwargs)
return _put(*args, **kwargs) return _put(*args, **kwargs)
def head(self, *args, **kwargs) -> Response: def head(self, *args, **kwargs):
with ipv6(kwargs.pop('ipv6', False)): with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _head(*_args, **_kwargs): def _head(*args, **kwargs):
return self.session.head(*_args, **_kwargs) return self.session.head(*args, **kwargs)
return _head(*args, **kwargs) return _head(*args, **kwargs)
def delete(self, *args, **kwargs) -> Response: def delete(self, *args, **kwargs):
with ipv6(kwargs.pop('ipv6', False)): with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _delete(*_args, **_kwargs): def _delete(*args, **kwargs):
return self.session.delete(*_args, **_kwargs) return self.session.delete(*args, **kwargs)
return _delete(*args, **kwargs) return _delete(*args, **kwargs)
def options(self, *args, **kwargs) -> Response: def options(self, *args, **kwargs):
with ipv6(kwargs.pop('ipv6', False)): with ipv6(kwargs.pop('ipv6', False)):
@backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None)
def _options(*_args, **_kwargs): def _options(*args, **kwargs):
return self.session.options(*_args, **_kwargs) return self.session.options(*args, **kwargs)
return _options(*args, **kwargs) return _options(*args, **kwargs)
def __getattr__(self, name): def __getattr__(self, name):
return getattr(requests, name) return getattr(requests, name)
def container_ip(container: Container) -> str: def container_ip(container: Container):
""" """
return the IP address of a container. return the IP address of a container.
@ -179,7 +171,7 @@ def container_ip(container: Container) -> str:
return net_info[network_name]["IPAddress"] return net_info[network_name]["IPAddress"]
def container_ipv6(container: Container) -> str: def container_ipv6(container):
""" """
return the IPv6 address of a container. return the IPv6 address of a container.
""" """
@ -196,7 +188,7 @@ def container_ipv6(container: Container) -> str:
return net_info[network_name]["GlobalIPv6Address"] return net_info[network_name]["GlobalIPv6Address"]
def nginx_proxy_dns_resolver(domain_name: str) -> Optional[str]: def nginx_proxy_dns_resolver(domain_name):
""" """
if "nginx-proxy" if found in host, return the ip address of the docker container if "nginx-proxy" if found in host, return the ip address of the docker container
issued from the docker image nginxproxy/nginx-proxy:test. issued from the docker image nginxproxy/nginx-proxy:test.
@ -208,21 +200,21 @@ def nginx_proxy_dns_resolver(domain_name: str) -> Optional[str]:
if 'nginx-proxy' in domain_name: if 'nginx-proxy' in domain_name:
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"}) nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
if len(nginxproxy_containers) == 0: if len(nginxproxy_containers) == 0:
log.warning(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}") log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"}) exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"})
if len(exited_nginxproxy_containers) > 0: if len(exited_nginxproxy_containers) > 0:
exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs() exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
log.warning(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode()) log.warn(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
return None return
nginxproxy_container = nginxproxy_containers[0] nginxproxy_container = nginxproxy_containers[0]
ip = container_ip(nginxproxy_container) ip = container_ip(nginxproxy_container)
log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}") log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
return ip return ip
def docker_container_dns_resolver(domain_name: str) -> Optional[str]: def docker_container_dns_resolver(domain_name):
""" """
if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container
return the ip address of the docker container named XXX. named XXX.
:return: IP or None :return: IP or None
""" """
@ -232,15 +224,15 @@ def docker_container_dns_resolver(domain_name: str) -> Optional[str]:
match = re.search(r'(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name) match = re.search(r'(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
if not match: if not match:
log.debug(f"{domain_name!r} does not match") log.debug(f"{domain_name!r} does not match")
return None return
container_name = match.group('container') container_name = match.group('container')
log.debug(f"looking for container {container_name!r}") log.debug(f"looking for container {container_name!r}")
try: try:
container = docker_client.containers.get(container_name) container = docker_client.containers.get(container_name)
except docker.errors.NotFound: except docker.errors.NotFound:
log.warning(f"container named {container_name!r} not found while resolving {domain_name!r}") log.warn(f"container named {container_name!r} not found while resolving {domain_name!r}")
return None return
log.debug(f"container {container.name!r} found ({container.short_id})") log.debug(f"container {container.name!r} found ({container.short_id})")
ip = container_ip(container) ip = container_ip(container)
@ -252,10 +244,7 @@ def monkey_patch_urllib_dns_resolver():
""" """
Alter the behavior of the urllib DNS resolver so that any domain name Alter the behavior of the urllib DNS resolver so that any domain name
containing substring 'nginx-proxy' will resolve to the IP address containing substring 'nginx-proxy' will resolve to the IP address
of the container created from image 'nginxproxy/nginx-proxy:test', of the container created from image 'nginxproxy/nginx-proxy:test'.
or to 127.0.0.1 on Darwin.
see https://docs.docker.com/desktop/features/networking/#i-want-to-connect-to-a-container-from-the-host
""" """
prv_getaddrinfo = socket.getaddrinfo prv_getaddrinfo = socket.getaddrinfo
dns_cache = {} dns_cache = {}
@ -263,17 +252,12 @@ def monkey_patch_urllib_dns_resolver():
logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}") logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
_args = list(args) _args = list(args)
# Fail early when querying IP directly, and it is forced ipv6 when not supported, # Fail early when querying IP directly and it is forced ipv6 when not supported,
# Otherwise a pytest container not using the host network fails to pass `test_raw-ip-vhost`. # Otherwise a pytest container not using the host network fails to pass `test_raw-ip-vhost`.
if FORCE_CONTAINER_IPV6 and not HAS_IPV6: if FORCE_CONTAINER_IPV6 and not HAS_IPV6:
pytest.skip("This system does not support IPv6") pytest.skip("This system does not support IPv6")
# custom DNS resolvers # custom DNS resolvers
ip = None
# Docker Desktop can't route traffic directly to Linux containers.
if platform.system() == "Darwin":
ip = "127.0.0.1"
if ip is None:
ip = nginx_proxy_dns_resolver(args[0]) ip = nginx_proxy_dns_resolver(args[0])
if ip is None: if ip is None:
ip = docker_container_dns_resolver(args[0]) ip = docker_container_dns_resolver(args[0])
@ -290,12 +274,19 @@ def monkey_patch_urllib_dns_resolver():
socket.getaddrinfo = new_getaddrinfo socket.getaddrinfo = new_getaddrinfo
return prv_getaddrinfo return prv_getaddrinfo
def restore_urllib_dns_resolver(getaddrinfo_func): def restore_urllib_dns_resolver(getaddrinfo_func):
socket.getaddrinfo = getaddrinfo_func socket.getaddrinfo = getaddrinfo_func
def get_nginx_conf_from_container(container: Container) -> bytes: def remove_all_containers():
for container in docker_client.containers.list(all=True):
if PYTEST_RUNNING_IN_CONTAINER and container.name == test_container:
continue # pytest is running within a Docker container, so we do not want to remove that particular container
logging.info(f"removing container {container.name}")
container.remove(v=True, force=True)
def get_nginx_conf_from_container(container):
""" """
return the nginx /etc/nginx/conf.d/default.conf file content from a container return the nginx /etc/nginx/conf.d/default.conf file content from a container
""" """
@ -310,40 +301,20 @@ def get_nginx_conf_from_container(container: Container) -> bytes:
return conffile.read() return conffile.read()
def __prepare_and_execute_compose_cmd(compose_files: List[str], project_name: str, cmd: str): def docker_compose_up(compose_file='docker-compose.yml'):
""" logging.info(f'{DOCKER_COMPOSE} -f {compose_file} up -d')
Prepare and execute the Docker Compose command with the provided compose files and project name.
"""
compose_cmd = StringIO()
compose_cmd.write(DOCKER_COMPOSE)
compose_cmd.write(f" --project-name {project_name}")
for compose_file in compose_files:
compose_cmd.write(f" --file {compose_file}")
compose_cmd.write(f" {cmd}")
logging.info(compose_cmd.getvalue())
try: try:
subprocess.check_output(shlex.split(compose_cmd.getvalue()), stderr=subprocess.STDOUT) subprocess.check_output(shlex.split(f'{DOCKER_COMPOSE} -f {compose_file} up -d'), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
pytest.fail(f"Error while running '{compose_cmd.getvalue()}':\n{e.output}", pytrace=False) pytest.fail(f"Error while runninng '{DOCKER_COMPOSE} -f {compose_file} up -d':\n{e.output}", pytrace=False)
def docker_compose_up(compose_files: List[str], project_name: str): def docker_compose_down(compose_file='docker-compose.yml'):
""" logging.info(f'{DOCKER_COMPOSE} -f {compose_file} down -v')
Execute compose up --detach with the provided compose files and project name. try:
""" subprocess.check_output(shlex.split(f'{DOCKER_COMPOSE} -f {compose_file} down -v'), stderr=subprocess.STDOUT)
if compose_files is None or len(compose_files) == 0: except subprocess.CalledProcessError as e:
pytest.fail(f"No compose file passed to docker_compose_up", pytrace=False) pytest.fail(f"Error while runninng '{DOCKER_COMPOSE} -f {compose_file} down -v':\n{e.output}", pytrace=False)
__prepare_and_execute_compose_cmd(compose_files, project_name, cmd="up --detach")
def docker_compose_down(compose_files: List[str], project_name: str):
"""
Execute compose down --volumes with the provided compose files and project name.
"""
if compose_files is None or len(compose_files) == 0:
pytest.fail(f"No compose file passed to docker_compose_up", pytrace=False)
__prepare_and_execute_compose_cmd(compose_files, project_name, cmd="down --volumes")
def wait_for_nginxproxy_to_be_ready(): def wait_for_nginxproxy_to_be_ready():
@ -362,50 +333,35 @@ def wait_for_nginxproxy_to_be_ready():
@pytest.fixture @pytest.fixture
def docker_compose_files(request: FixtureRequest) -> List[str]: def docker_compose_file(request):
"""Fixture returning the docker compose files to consider: """Fixture naming the docker compose file to consider.
If a YAML file exists with the same name as the test module (with the `.py` extension If a YAML file exists with the same name as the test module (with the `.py` extension replaced
replaced with `.base.yml`, ie `test_foo.py`-> `test_foo.base.yml`) and in the same with `.yml` or `.yaml`), use that. Otherwise, use `docker-compose.yml` in the same directory
directory as the test module, use only that file. as the test module.
Otherwise, merge the following files in this order:
- the `compose.base.yml` file in the parent `test` directory.
- if present in the same directory as the test module, the `compose.base.override.yml` file.
- the YAML file named after the current test module (ie `test_foo.py`-> `test_foo.yml`)
Tests can override this fixture to specify a custom location. Tests can override this fixture to specify a custom location.
""" """
compose_files: List[str] = [] test_module_dir = os.path.dirname(request.module.__file__)
test_module_path = pathlib.Path(request.module.__file__).parent yml_file = os.path.join(test_module_dir, request.module.__name__ + '.yml')
yaml_file = os.path.join(test_module_dir, request.module.__name__ + '.yaml')
default_file = os.path.join(test_module_dir, 'docker-compose.yml')
module_base_file = test_module_path.joinpath(f"{request.module.__name__}.base.yml") if os.path.isfile(yml_file):
if module_base_file.is_file(): docker_compose_file = yml_file
return [module_base_file.as_posix()] elif os.path.isfile(yaml_file):
docker_compose_file = yaml_file
else:
docker_compose_file = default_file
global_base_file = test_module_path.parent.joinpath("compose.base.yml") if not os.path.isfile(docker_compose_file):
if global_base_file.is_file(): logging.error("Could not find any docker compose file named either '{0}.yml', '{0}.yaml' or 'docker-compose.yml'".format(request.module.__name__))
compose_files.append(global_base_file.as_posix())
module_base_override_file = test_module_path.joinpath("compose.base.override.yml") logging.debug(f"using docker compose file {docker_compose_file}")
if module_base_override_file.is_file(): return docker_compose_file
compose_files.append(module_base_override_file.as_posix())
module_compose_file = test_module_path.joinpath(f"{request.module.__name__}.yml")
if module_compose_file.is_file():
compose_files.append(module_compose_file.as_posix())
if not module_base_file.is_file() and not module_compose_file.is_file():
logging.error(
f"Could not find any docker compose file named '{module_base_file.name}' or '{module_compose_file.name}'"
)
logging.debug(f"using docker compose files {compose_files}")
return compose_files
def connect_to_network(network: Network) -> Optional[Network]: def connect_to_network(network):
""" """
If we are running from a container, connect our container to the given network If we are running from a container, connect our container to the given network
@ -415,8 +371,8 @@ def connect_to_network(network: Network) -> Optional[Network]:
try: try:
my_container = docker_client.containers.get(test_container) my_container = docker_client.containers.get(test_container)
except docker.errors.NotFound: except docker.errors.NotFound:
logging.warning(f"container {test_container} not found") logging.warn(f"container {test_container} not found")
return None return
# figure out our container networks # figure out our container networks
my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys()) my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
@ -433,7 +389,7 @@ def connect_to_network(network: Network) -> Optional[Network]:
return network return network
def disconnect_from_network(network: Network = None): def disconnect_from_network(network=None):
""" """
If we are running from a container, disconnect our container from the given network. If we are running from a container, disconnect our container from the given network.
@ -443,7 +399,7 @@ def disconnect_from_network(network: Network = None):
try: try:
my_container = docker_client.containers.get(test_container) my_container = docker_client.containers.get(test_container)
except docker.errors.NotFound: except docker.errors.NotFound:
logging.warning(f"container {test_container} not found") logging.warn(f"container {test_container} not found")
return return
# figure out our container networks # figure out our container networks
@ -455,7 +411,7 @@ def disconnect_from_network(network: Network = None):
network.disconnect(my_container) network.disconnect(my_container)
def connect_to_all_networks() -> List[Network]: def connect_to_all_networks():
""" """
If we are running from a container, connect our container to all current docker networks. If we are running from a container, connect our container to all current docker networks.
@ -471,34 +427,31 @@ def connect_to_all_networks() -> List[Network]:
class DockerComposer(contextlib.AbstractContextManager): class DockerComposer(contextlib.AbstractContextManager):
def __init__(self): def __init__(self):
self._networks = None self._docker_compose_file = None
self._docker_compose_files = None
self._project_name = None
def __exit__(self, *exc_info): def __exit__(self, *exc_info):
self._down() self._down()
def _down(self): def _down(self):
if self._docker_compose_files is None: if self._docker_compose_file is None:
return return
for network in self._networks: for network in self._networks:
disconnect_from_network(network) disconnect_from_network(network)
docker_compose_down(self._docker_compose_files, self._project_name) docker_compose_down(self._docker_compose_file)
self._docker_compose_file = None self._docker_compose_file = None
self._project_name = None
def compose(self, docker_compose_files: List[str], project_name: str): def compose(self, docker_compose_file):
if docker_compose_files == self._docker_compose_files and project_name == self._project_name: if docker_compose_file == self._docker_compose_file:
return return
self._down() self._down()
if docker_compose_files is None or project_name is None: if docker_compose_file is None:
return return
docker_compose_up(docker_compose_files, project_name) remove_all_containers()
docker_compose_up(docker_compose_file)
self._networks = connect_to_all_networks() self._networks = connect_to_all_networks()
wait_for_nginxproxy_to_be_ready() wait_for_nginxproxy_to_be_ready()
time.sleep(3) # give time to containers to be ready time.sleep(3) # give time to containers to be ready
self._docker_compose_files = docker_compose_files self._docker_compose_file = docker_compose_file
self._project_name = project_name
############################################################################### ###############################################################################
@ -509,14 +462,14 @@ class DockerComposer(contextlib.AbstractContextManager):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def docker_composer() -> Iterator[DockerComposer]: def docker_composer():
with DockerComposer() as d: with DockerComposer() as d:
yield d yield d
@pytest.fixture @pytest.fixture
def ca_root_certificate() -> str: def ca_root_certificate():
return CA_ROOT_CERTIFICATE.as_posix() return CA_ROOT_CERTIFICATE
@pytest.fixture @pytest.fixture
@ -527,38 +480,25 @@ def monkey_patched_dns():
@pytest.fixture @pytest.fixture
def docker_compose( def docker_compose(monkey_patched_dns, docker_composer, docker_compose_file):
request: FixtureRequest, """Ensures containers described in a docker compose file are started.
monkeypatch,
monkey_patched_dns, A custom docker compose file name can be specified by overriding the `docker_compose_file`
docker_composer, fixture.
docker_compose_files
) -> Iterator[DockerClient]: Also, in the case where pytest is running from a docker container, this fixture makes sure
our container will be attached to all the docker networks.
""" """
Ensures containers necessary for the test module are started in a compose project, docker_composer.compose(docker_compose_file)
and set the environment variable `PYTEST_MODULE_PATH` to the test module's parent folder.
A list of custom docker compose files path can be specified by overriding
the `docker_compose_file` fixture.
Also, in the case where pytest is running from a docker container, this fixture
makes sure our container will be attached to all the docker networks.
"""
pytest_module_path = pathlib.Path(request.module.__file__).parent
monkeypatch.setenv("PYTEST_MODULE_PATH", pytest_module_path.as_posix())
project_name = request.module.__name__
docker_composer.compose(docker_compose_files, project_name)
yield docker_client yield docker_client
@pytest.fixture @pytest.fixture()
def nginxproxy() -> Iterator[RequestsForDocker]: def nginxproxy():
""" """
Provides the `nginxproxy` object that can be used in the same way the requests module is: Provides the `nginxproxy` object that can be used in the same way the requests module is:
r = nginxproxy.get("https://foo.com") r = nginxproxy.get("http://foo.com")
The difference is that in case an HTTP requests has status code 404 or 502 (which mostly The difference is that in case an HTTP requests has status code 404 or 502 (which mostly
indicates that nginx has just reloaded), we retry up to 30 times the query. indicates that nginx has just reloaded), we retry up to 30 times the query.
@ -567,29 +507,23 @@ def nginxproxy() -> Iterator[RequestsForDocker]:
made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not 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. supported by the system or docker, that particular test will be skipped.
""" """
yield RequestsForDocker() yield requests_for_docker()
@pytest.fixture
def acme_challenge_path() -> str:
"""
Provides fake Let's Encrypt ACME challenge path used in certain tests
"""
return ".well-known/acme-challenge/test-filename"
############################################################################### ###############################################################################
# #
# Py.test hooks # Py.test hooks
# #
############################################################################### ###############################################################################
# pytest hook to display additional stuff in test report # pytest hook to display additionnal stuff in test report
def pytest_runtest_logreport(report): def pytest_runtest_logreport(report):
if report.failed: if report.failed:
if isinstance(report.longrepr, ReprExceptionInfo):
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"}) test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"})
for container in test_containers: for container in test_containers:
report.longrepr.addsection('nginx-proxy logs', container.logs().decode()) report.longrepr.addsection('nginx-proxy logs', container.logs())
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container).decode()) report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
# Py.test `incremental` marker, see http://stackoverflow.com/a/12579625/107049 # Py.test `incremental` marker, see http://stackoverflow.com/a/12579625/107049
@ -616,5 +550,5 @@ try:
except docker.errors.ImageNotFound: except docker.errors.ImageNotFound:
pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing") pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing")
if Version(docker.__version__) < Version("7.0.0"): if LooseVersion(docker.__version__) < LooseVersion("5.0.0"):
pytest.exit("This test suite is meant to work with the python docker module v7.0.0 or later") pytest.exit("This test suite is meant to work with the python docker module v5.0.0 or later")

View file

@ -3,7 +3,7 @@
# # # #
# This script is meant to run the test suite from a Docker container. # # This script is meant to run the test suite from a Docker container. #
# # # #
# This is useful when you want to run the test suite from Mac or # # This is usefull when you want to run the test suite from Mac or #
# Docker Toolbox. # # Docker Toolbox. #
# # # #
############################################################################### ###############################################################################

View file

@ -1,4 +1,4 @@
FROM python:3.12 FROM python:3.9
ENV PYTEST_RUNNING_IN_CONTAINER=1 ENV PYTEST_RUNNING_IN_CONTAINER=1
@ -10,13 +10,14 @@ RUN apt-get update \
&& apt-get install -y \ && apt-get install -y \
ca-certificates \ ca-certificates \
curl \ curl \
gnupg \
&& install -m 0755 -d /etc/apt/keyrings \ && install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \ && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
&& chmod a+r /etc/apt/keyrings/docker.asc && chmod a+r /etc/apt/keyrings/docker.gpg
# Add the Docker repository to Apt sources # Add the Docker repository to Apt sources
RUN echo \ RUN echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null tee /etc/apt/sources.list.d/docker.list > /dev/null

View file

@ -1,6 +1,4 @@
backoff==2.2.1 backoff==2.2.1
docker==7.1.0 docker==7.0.0
packaging==24.2 pytest==8.2.0
pytest==8.3.4 requests==2.31.0
requests==2.32.3
urllib3==2.3.0

View file

@ -1,7 +1,6 @@
# Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable # Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable
FROM python:3-alpine FROM python:3
RUN apk add --no-cache bash
COPY ./webserver.py / COPY ./webserver.py /
COPY ./entrypoint.sh / COPY ./entrypoint.sh /
WORKDIR /opt WORKDIR /opt

View file

@ -5,11 +5,11 @@ trap '[ ${#PIDS[@]} -gt 0 ] && kill -TERM ${PIDS[@]}' TERM
declare -a PIDS declare -a PIDS
for port in $WEB_PORTS; do for port in $WEB_PORTS; do
echo starting a web server listening on port "$port"; echo starting a web server listening on port $port;
/webserver.py "$port" & /webserver.py $port &
PIDS+=($!) PIDS+=($!)
done done
wait "${PIDS[@]}" wait ${PIDS[@]}
trap - TERM trap - TERM
wait "${PIDS[@]}" wait ${PIDS[@]}

View file

@ -28,7 +28,7 @@ class Handler(http.server.SimpleHTTPRequestHandler):
self.send_header("Content-Type", "text/plain") self.send_header("Content-Type", "text/plain")
self.end_headers() self.end_headers()
if len(response_body): if (len(response_body)):
self.wfile.write(response_body.encode()) self.wfile.write(response_body.encode())
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -0,0 +1 @@
This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy.

View file

@ -1,12 +1,17 @@
version: "2"
networks: networks:
netA: netA:
netB: netB:
services: services:
nginx-proxy: reverseproxy:
container_name: reverseproxy container_name: reverseproxy
networks: networks:
- netA - netA
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
webA: webA:
networks: networks:
@ -15,7 +20,7 @@ services:
expose: expose:
- 81 - 81
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: webA.nginx-proxy VIRTUAL_HOST: webA.nginx-proxy
webB: webB:
@ -25,5 +30,5 @@ services:
expose: expose:
- 82 - 82
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: webB.nginx-proxy VIRTUAL_HOST: webB.nginx-proxy

View file

@ -1,3 +1,5 @@
import pytest
def test_unknown_virtual_host(docker_compose, nginxproxy): def test_unknown_virtual_host(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/port") r = nginxproxy.get("http://nginx-proxy/port")
assert r.status_code == 503 assert r.status_code == 503

View file

@ -1,16 +1,12 @@
services: version: "2"
nginx-proxy:
volumes:
- /var/run/docker.sock:/f00.sock:ro
environment:
DOCKER_HOST: unix:///f00.sock
services:
web1: web1:
image: web image: web
expose: expose:
- "81" - "81"
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.tld VIRTUAL_HOST: web1.nginx-proxy.tld
web2: web2:
@ -18,5 +14,12 @@ services:
expose: expose:
- "82" - "82"
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.tld VIRTUAL_HOST: web2.nginx-proxy.tld
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/f00.sock:ro
environment:
DOCKER_HOST: unix:///f00.sock

View file

@ -1,70 +0,0 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4096 (0x1000)
Signature Algorithm: sha256WithRSAEncryption
Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
Validity
Not Before: Jan 10 00:08:52 2017 GMT
Not After : May 28 00:08:52 2044 GMT
Subject: CN=*.nginx-proxy.tld
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1:
27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c:
17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba:
d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27:
2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f:
de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1:
71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81:
0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e:
1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74:
61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37:
78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5:
14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21:
67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24:
7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d:
6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91:
f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f:
ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96:
45:85
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:*.nginx-proxy.tld
Signature Algorithm: sha256WithRSAEncryption
6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06:
a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a:
22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50:
9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc:
03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73:
af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9:
40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7:
6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11:
3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7:
f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86:
3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7:
26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba:
63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50:
e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28:
c7:52:de:f9
-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou
bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7
oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti
jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4
/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9
yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K
90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy
b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs
E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl
PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC
ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31
iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL
vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q==
-----END CERTIFICATE-----

View file

@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a
wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa
rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm
snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn
FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI
E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO
fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a
dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x
fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p
e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn
QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB
uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf
oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k
VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf
MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2
pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M
RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI
ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og
4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD
Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4
pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9
A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH
iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr
iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV
THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H
-----END RSA PRIVATE KEY-----

View file

@ -1,6 +0,0 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/certs:/etc/nginx/certs:ro
- ${PYTEST_MODULE_PATH}/acme_root:/usr/share/nginx/html:ro

View file

@ -1,27 +0,0 @@
def test_redirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 301
def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 200
def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web3.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 404
def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web4.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 200

View file

@ -1,40 +0,0 @@
services:
nginx-proxy:
environment:
ACME_HTTP_CHALLENGE_LOCATION: "false"
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
ACME_HTTP_CHALLENGE_LOCATION: "true"
web3:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web3.nginx-proxy.tld"
HTTPS_METHOD: noredirect
web4:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: "web4.nginx-proxy.tld"
HTTPS_METHOD: noredirect
ACME_HTTP_CHALLENGE_LOCATION: "true"

View file

@ -1,27 +0,0 @@
def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 200
def test_redirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 301
def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web3.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 200
def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web4.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 404

View file

@ -1,36 +0,0 @@
services:
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
ACME_HTTP_CHALLENGE_LOCATION: "false"
web3:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: "web3.nginx-proxy.tld"
HTTPS_METHOD: noredirect
web4:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: "web4.nginx-proxy.tld"
HTTPS_METHOD: noredirect
ACME_HTTP_CHALLENGE_LOCATION: "false"

View file

@ -1,13 +0,0 @@
def test_redirect_acme_challenge_location_legacy(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 200
def test_noredirect_acme_challenge_location_legacy(docker_compose, nginxproxy, acme_challenge_path):
r = nginxproxy.get(
f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
allow_redirects=False
)
assert r.status_code == 404

View file

@ -1,21 +0,0 @@
services:
nginx-proxy:
environment:
ACME_HTTP_CHALLENGE_LOCATION: "legacy"
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: "web1.nginx-proxy.tld"
web2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: "web2.nginx-proxy.tld"
HTTPS_METHOD: noredirect

View file

@ -1,25 +1,21 @@
""" """
Test that nginx-proxy-tester can build successfully Test that nginx-proxy-tester can build successfully
""" """
import pathlib
import re
import docker
import pytest import pytest
import docker
import re
client = docker.from_env() client = docker.from_env()
@pytest.fixture(scope = "session") @pytest.fixture(scope = "session")
def docker_build(request): def docker_build(request):
# Define Dockerfile path # Define Dockerfile path
current_file_path = pathlib.Path(__file__) dockerfile_path = "requirements/"
dockerfile_path = current_file_path.parent.parent.joinpath("requirements")
dockerfile_name = "Dockerfile-nginx-proxy-tester" dockerfile_name = "Dockerfile-nginx-proxy-tester"
# Build the Docker image # Build the Docker image
image, logs = client.images.build( image, logs = client.images.build(
path = dockerfile_path.as_posix(), path = dockerfile_path,
dockerfile = dockerfile_name, dockerfile = dockerfile_name,
rm = True, # Remove intermediate containers rm = True, # Remove intermediate containers
tag = "nginx-proxy-tester-ci", # Tag for the built image tag = "nginx-proxy-tester-ci", # Tag for the built image

10
test/test_composev2.py Normal file
View file

@ -0,0 +1,10 @@
import pytest
def test_unknown_virtual_host(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503
def test_forwards_to_whoami(docker_compose, nginxproxy):
r = nginxproxy.get("http://web.nginx-proxy.example/port")
assert r.status_code == 200
assert r.text == "answer from port 81\n"

15
test/test_composev2.yml Normal file
View file

@ -0,0 +1,15 @@
version: "2"
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
web:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
VIRTUAL_HOST: web.nginx-proxy.example

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Maintenance</title>
<style>
html {
color-scheme: light dark;
}
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Damn, there's some maintenance in progress.</h1>
<p>
Our apologies for this temporary inconvenience. Regular service
performance will be re-established shortly.
</p>
</body>
</html>

View file

@ -1,7 +0,0 @@
import re
def test_custom_error_page(docker_compose, nginxproxy):
r = nginxproxy.get("http://unknown.nginx-proxy.tld")
assert r.status_code == 503
assert re.search(r"Damn, there's some maintenance in progress.", r.text)

View file

@ -1,5 +0,0 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/50x.html:/usr/share/nginx/html/errors/50x.html:ro

View file

@ -1,3 +1,5 @@
import pytest
def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/") r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503 assert r.status_code == 503

View file

@ -1,16 +1,19 @@
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/default_location:ro - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro
web1: web1:
image: web image: web
expose: expose:
- "81" - "81"
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.example VIRTUAL_HOST: web1.nginx-proxy.example
web2: web2:
@ -18,7 +21,7 @@ services:
expose: expose:
- "82" - "82"
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.example VIRTUAL_HOST: web2.nginx-proxy.example
web3: web3:
@ -26,5 +29,5 @@ services:
expose: expose:
- "83" - "83"
environment: environment:
WEB_PORTS: "83" WEB_PORTS: 83
VIRTUAL_HOST: web3.nginx-proxy.example VIRTUAL_HOST: web3.nginx-proxy.example

View file

@ -1,3 +1,5 @@
import pytest
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/") r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503 assert r.status_code == 503

View file

@ -1,15 +1,18 @@
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/proxy.conf:ro - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro
web1: web1:
image: web image: web
expose: expose:
- "81" - "81"
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.example VIRTUAL_HOST: web1.nginx-proxy.example
web2: web2:
@ -17,5 +20,5 @@ services:
expose: expose:
- "82" - "82"
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.example VIRTUAL_HOST: web2.nginx-proxy.example

View file

@ -1,3 +1,5 @@
import pytest
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/") r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503 assert r.status_code == 503
@ -10,13 +12,6 @@ def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
assert "X-test" in r.headers assert "X-test" in r.headers
assert "f00" == r.headers["X-test"] assert "f00" == r.headers["X-test"]
def test_custom_conf_applies_to_regex(docker_compose, nginxproxy):
r = nginxproxy.get("http://regex.foo.nginx-proxy.example/port")
assert r.status_code == 200
assert r.text == "answer from port 83\n"
assert "X-test" in r.headers
assert "bar" == r.headers["X-test"]
def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
r = nginxproxy.get("http://web2.nginx-proxy.example/port") r = nginxproxy.get("http://web2.nginx-proxy.example/port")
assert r.status_code == 200 assert r.status_code == 200

View file

@ -1,16 +1,18 @@
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430_location:ro
web1: web1:
image: web image: web
expose: expose:
- "81" - "81"
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.example VIRTUAL_HOST: web1.nginx-proxy.example
web2: web2:
@ -18,13 +20,5 @@ services:
expose: expose:
- "82" - "82"
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.example VIRTUAL_HOST: web2.nginx-proxy.example
regex:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$

View file

@ -1,3 +1,5 @@
import pytest
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/") r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503 assert r.status_code == 503
@ -10,13 +12,6 @@ def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
assert "X-test" in r.headers assert "X-test" in r.headers
assert "f00" == r.headers["X-test"] assert "f00" == r.headers["X-test"]
def test_custom_conf_applies_to_regex(docker_compose, nginxproxy):
r = nginxproxy.get("http://regex.foo.nginx-proxy.example/port")
assert r.status_code == 200
assert r.text == "answer from port 83\n"
assert "X-test" in r.headers
assert "bar" == r.headers["X-test"]
def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
r = nginxproxy.get("http://web2.nginx-proxy.example/port") r = nginxproxy.get("http://web2.nginx-proxy.example/port")
assert r.status_code == 200 assert r.status_code == 200

View file

@ -1,16 +1,18 @@
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430:ro
web1: web1:
image: web image: web
expose: expose:
- "81" - "81"
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.example VIRTUAL_HOST: web1.nginx-proxy.example
web2: web2:
@ -18,13 +20,5 @@ services:
expose: expose:
- "82" - "82"
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.example VIRTUAL_HOST: web2.nginx-proxy.example
regex:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$

View file

@ -1,3 +1,5 @@
import pytest
def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
r = nginxproxy.get("http://nginx-proxy/") r = nginxproxy.get("http://nginx-proxy/")
assert r.status_code == 503 assert r.status_code == 503

View file

@ -1,15 +1,18 @@
version: "2"
services: services:
nginx-proxy: nginx-proxy:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/conf.d/my_custom_proxy_settings_f00.conf:ro - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro
web1: web1:
image: web image: web
expose: expose:
- "81" - "81"
environment: environment:
WEB_PORTS: "81" WEB_PORTS: 81
VIRTUAL_HOST: web1.nginx-proxy.example VIRTUAL_HOST: web1.nginx-proxy.example
web2: web2:
@ -17,5 +20,5 @@ services:
expose: expose:
- "82" - "82"
environment: environment:
WEB_PORTS: "82" WEB_PORTS: 82
VIRTUAL_HOST: web2.nginx-proxy.example VIRTUAL_HOST: web2.nginx-proxy.example

View file

@ -1,48 +0,0 @@
import json
import pytest
def test_debug_endpoint_is_enabled_globally(docker_compose, nginxproxy):
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
r = nginxproxy.get("http://stripped.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
def test_debug_endpoint_response_contains_expected_values(docker_compose, nginxproxy):
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
try:
jsonResponse = json.loads(r.text)
except ValueError as err:
pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False)
assert jsonResponse["global"]["enable_debug_endpoint"] == "true"
assert jsonResponse["vhost"]["enable_debug_endpoint"] == True
def test_debug_endpoint_paths_stripped_if_response_too_long(docker_compose, nginxproxy):
r = nginxproxy.get("http://stripped.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
try:
jsonResponse = json.loads(r.text)
except ValueError as err:
pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False)
if "paths" in jsonResponse["vhost"]:
pytest.fail("Expected paths to be stripped from debug endpoint response", pytrace=False)
assert jsonResponse["warning"] == "Virtual paths configuration for this hostname is too large and has been stripped from response."
def test_debug_endpoint_hostname_replaced_by_warning_if_regexp(docker_compose, nginxproxy):
r = nginxproxy.get("http://regexp.foo.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
try:
jsonResponse = json.loads(r.text)
except ValueError as err:
pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False)
assert jsonResponse["vhost"]["hostname"] == "Hostname is a regexp and unsafe to include in the debug response."
def test_debug_endpoint_is_disabled_per_container(docker_compose, nginxproxy):
r = nginxproxy.get("http://disabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 404

View file

@ -1,59 +0,0 @@
services:
nginx-proxy:
environment:
DEBUG_ENDPOINT: "true"
debug_enabled:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: enabled.debug.nginx-proxy.example
debug_stripped:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST_MULTIPORTS: |-
stripped.debug.nginx-proxy.example:
"/1":
"/2":
"/3":
"/4":
"/5":
"/6":
"/7":
"/8":
"/9":
"/10":
"/11":
"/12":
"/13":
"/14":
"/15":
"/16":
"/17":
"/18":
"/19":
"/20":
debug_regexp:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: ~^regexp.*\.debug.nginx-proxy.example
debug_disabled:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: disabled.debug.nginx-proxy.example
labels:
com.github.nginx-proxy.nginx-proxy.debug-endpoint: "false"

View file

@ -1,26 +0,0 @@
import json
import pytest
def test_debug_endpoint_is_disabled_globally(docker_compose, nginxproxy):
r = nginxproxy.get("http://disabled1.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 404
r = nginxproxy.get("http://disabled2.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 404
def test_debug_endpoint_is_enabled_per_container(docker_compose, nginxproxy):
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
def test_debug_endpoint_response_contains_expected_values(docker_compose, nginxproxy):
r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug")
assert r.status_code == 200
try:
jsonResponse = json.loads(r.text)
except ValueError as err:
pytest.fail("Failed to parse debug endpoint response as JSON:: %s" % err, pytrace=False)
assert jsonResponse["global"]["enable_debug_endpoint"] == "false"
assert jsonResponse["vhost"]["enable_debug_endpoint"] == True

View file

@ -1,27 +0,0 @@
services:
debug_disabled1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: disabled1.debug.nginx-proxy.example
debug_disabled2:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: disabled2.debug.nginx-proxy.example
debug_enabled:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: enabled.debug.nginx-proxy.example
labels:
com.github.nginx-proxy.nginx-proxy.debug-endpoint: "true"

View file

@ -1,3 +1,6 @@
import pytest
def test_fallback_on_default(docker_compose, nginxproxy): def test_fallback_on_default(docker_compose, nginxproxy):
r = nginxproxy.get("http://unknown.nginx-proxy.tld/port") r = nginxproxy.get("http://unknown.nginx-proxy.tld/port")
assert r.status_code == 200 assert r.status_code == 200

View file

@ -0,0 +1,19 @@
version: "2"
services:
# GIVEN a webserver with VIRTUAL_HOST set to web1.tld
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: 81
VIRTUAL_HOST: web1.tld
# WHEN nginx-proxy runs with DEFAULT_HOST set to web1.tld
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
environment:
DEFAULT_HOST: web1.tld

View file

@ -1,12 +0,0 @@
services:
nginx-proxy:
environment:
DEFAULT_HOST: web1.tld
web1:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: web1.tld

View file

@ -1,5 +1,10 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
environment: environment:
DEFAULT_ROOT: none DEFAULT_ROOT: none

1
test/test_dockergen/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
nginx.tmpl

View file

@ -0,0 +1,10 @@
def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
r = nginxproxy.get("http://unknown.nginx.container.docker/")
assert r.status_code == 503
def test_forwards_to_whoami(docker_compose, nginxproxy):
r = nginxproxy.get("http://whoami.nginx.container.docker/")
assert r.status_code == 200
whoami_container = docker_compose.containers.get("whoami")
assert r.text == f"I'm {whoami_container.id[:12]}\n"

View file

@ -0,0 +1,26 @@
version: "2"
services:
nginx:
image: nginx
container_name: nginx
volumes:
- /etc/nginx/conf.d
dockergen:
image: nginxproxy/docker-gen
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
volumes_from:
- nginx
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
web:
image: web
container_name: whoami
expose:
- "80"
environment:
WEB_PORTS: 80
VIRTUAL_HOST: whoami.nginx.container.docker

View file

@ -1,11 +1,11 @@
import docker import docker
import pytest import pytest
from packaging.version import Version from distutils.version import LooseVersion
raw_version = docker.from_env().version()["Version"] raw_version = docker.from_env().version()["Version"]
pytestmark = pytest.mark.skipif( pytestmark = pytest.mark.skipif(
Version(raw_version) < Version("1.13"), LooseVersion(raw_version) < LooseVersion("1.13"),
reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
) )

View file

@ -1,18 +1,13 @@
volumes: version: "3"
nginx_conf:
services: services:
nginx-proxy-nginx: nginx:
image: nginx image: nginx
container_name: nginx container_name: nginx
volumes: volumes:
- nginx_conf:/etc/nginx/conf.d:ro - nginx_conf:/etc/nginx/conf.d
ports:
- "80:80"
- "443:443"
nginx-proxy-dockergen: dockergen:
image: nginxproxy/docker-gen image: nginxproxy/docker-gen
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
volumes: volumes:
@ -26,5 +21,8 @@ services:
expose: expose:
- "80" - "80"
environment: environment:
WEB_PORTS: "80" WEB_PORTS: 80
VIRTUAL_HOST: whoami.nginx.container.docker VIRTUAL_HOST: whoami.nginx.container.docker
volumes:
nginx_conf: {}

View file

@ -1,15 +0,0 @@
def test_nohttp_missing_cert_disabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://nohttp-missing-cert-disabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 503
def test_nohttp_missing_cert_enabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://nohttp-missing-cert-enabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 200
def test_redirect_missing_cert_disabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://redirect-missing-cert-disabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 301
def test_redirect_missing_cert_enabled(docker_compose, nginxproxy):
r = nginxproxy.get("http://redirect-missing-cert-enabled.nginx-proxy.tld/", allow_redirects=False)
assert r.status_code == 200

View file

@ -1,40 +0,0 @@
services:
nginx-proxy:
environment:
ENABLE_HTTP_ON_MISSING_CERT: "false"
nohttp-missing-cert-disabled:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: nohttp-missing-cert-disabled.nginx-proxy.tld
HTTPS_METHOD: nohttp
nohttp-missing-cert-enabled:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: nohttp-missing-cert-enabled.nginx-proxy.tld
HTTPS_METHOD: nohttp
ENABLE_HTTP_ON_MISSING_CERT: "true"
redirect-missing-cert-disabled:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: redirect-missing-cert-disabled.nginx-proxy.tld
redirect-missing-cert-enabled:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: redirect-missing-cert-enabled.nginx-proxy.tld
ENABLE_HTTP_ON_MISSING_CERT: "true"

View file

@ -7,7 +7,7 @@ import pytest
from docker.errors import NotFound from docker.errors import NotFound
@pytest.fixture @pytest.fixture()
def web1(docker_compose): def web1(docker_compose):
""" """
pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81. pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81.
@ -22,7 +22,7 @@ def web1(docker_compose):
}, },
ports={"81/tcp": None} ports={"81/tcp": None}
) )
docker_compose.networks.get("test_events-net").connect(container) docker_compose.networks.get("test_default").connect(container)
sleep(2) # give it some time to initialize and for docker-gen to detect it sleep(2) # give it some time to initialize and for docker-gen to detect it
yield container yield container
try: try:
@ -30,7 +30,7 @@ def web1(docker_compose):
except NotFound: except NotFound:
pass pass
@pytest.fixture @pytest.fixture()
def web2(docker_compose): def web2(docker_compose):
""" """
pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82. pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82.
@ -47,7 +47,7 @@ def web2(docker_compose):
}, },
ports={"82/tcp": None} ports={"82/tcp": None}
) )
docker_compose.networks.get("test_events-net").connect(container) docker_compose.networks.get("test_default").connect(container)
sleep(2) # give it some time to initialize and for docker-gen to detect it sleep(2) # give it some time to initialize and for docker-gen to detect it
yield container yield container
try: try:

View file

@ -1,9 +1,7 @@
version: "2"
services: services:
nginx-proxy: nginxproxy:
image: nginxproxy/nginx-proxy:test image: nginxproxy/nginx-proxy:test
container_name: nginx-proxy
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- "80:80"
- "443:443"

View file

@ -1,3 +0,0 @@
networks:
default:
name: test_events-net

View file

@ -0,0 +1,17 @@
version: "2"
services:
sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ./custom-fallback.conf:/etc/nginx/conf.d/zzz-custom-fallback.conf:ro
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps

View file

@ -1,8 +1,11 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/nodefault.certs:/etc/nginx/certs:ro - ./nodefault.certs:/etc/nginx/certs:ro
https-and-http: https-and-http:
image: web image: web

View file

@ -1,8 +1,11 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro - ./withdefault.certs:/etc/nginx/certs:ro
environment: environment:
HTTPS_METHOD: redirect HTTPS_METHOD: redirect

View file

@ -1,8 +1,11 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro - ./withdefault.certs:/etc/nginx/certs:ro
environment: environment:
HTTPS_METHOD: nohttp HTTPS_METHOD: nohttp
@ -21,13 +24,3 @@ services:
environment: environment:
WEB_PORTS: "84" WEB_PORTS: "84"
VIRTUAL_HOST: missing-cert.nginx-proxy.test VIRTUAL_HOST: missing-cert.nginx-proxy.test
missing-cert-default-untrusted:
image: web
expose:
- "85"
environment:
WEB_PORTS: "85"
VIRTUAL_HOST: missing-cert.default-untrusted.nginx-proxy.test
labels:
com.github.nginx-proxy.nginx-proxy.trust-default-cert: "false"

View file

@ -1,8 +1,11 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro - ./withdefault.certs:/etc/nginx/certs:ro
environment: environment:
HTTPS_METHOD: nohttp HTTPS_METHOD: nohttp

View file

@ -1,5 +1,10 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
environment: environment:
HTTPS_METHOD: redirect HTTPS_METHOD: redirect

View file

@ -1,8 +1,12 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/custom-fallback.conf:/etc/nginx/conf.d/zzz-custom-fallback.conf:ro environment:
HTTPS_METHOD: nohttps
http-only: http-only:
image: web image: web
@ -11,4 +15,3 @@ services:
environment: environment:
WEB_PORTS: "83" WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps

View file

@ -1,10 +1,11 @@
version: "2"
services: services:
nginx-proxy: sut:
image: nginxproxy/nginx-proxy:test
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro - ./withdefault.certs:/etc/nginx/certs:ro
environment:
TRUST_DEFAULT_CERT: "false"
https-and-http: https-and-http:
image: web image: web

View file

@ -1,38 +1,39 @@
import pathlib import os.path
import re import re
from typing import List, Callable
import backoff import backoff
import pytest import pytest
import requests import requests
from requests import Response
@pytest.fixture @pytest.fixture
def docker_compose_files(compose_file) -> List[str]: def data_dir():
data_dir = pathlib.Path(__file__).parent.joinpath("test_fallback.data") return f"{os.path.splitext(__file__)[0]}.data"
return [
data_dir.joinpath("compose.base.yml"),
data_dir.joinpath(compose_file).as_posix()
]
@pytest.fixture @pytest.fixture
def get(docker_compose, nginxproxy, want_err_re: re.Pattern[str]) -> Callable[[str], Response]: def docker_compose_file(data_dir, compose_file):
return os.path.join(data_dir, compose_file)
@pytest.fixture
def get(docker_compose, nginxproxy, want_err_re):
@backoff.on_exception( @backoff.on_exception(
backoff.constant, backoff.constant,
requests.exceptions.SSLError, requests.exceptions.RequestException,
giveup=lambda e: want_err_re and bool(want_err_re.search(str(e))), giveup=lambda e: want_err_re and want_err_re.search(str(e)),
interval=.3, interval=.3,
max_tries=30, max_tries=30,
jitter=None) jitter=None)
def _get(url) -> Response: def _get(url):
return nginxproxy.get(url, allow_redirects=False) return nginxproxy.get(url, allow_redirects=False)
return _get return _get
INTERNAL_ERR_RE = re.compile("TLSV1_UNRECOGNIZED_NAME") INTERNAL_ERR_RE = re.compile("TLSV1_ALERT_INTERNAL_ERROR")
CONNECTION_REFUSED_RE = re.compile("Connection refused")
@pytest.mark.parametrize("compose_file,url,want_code,want_err_re", [ @pytest.mark.parametrize("compose_file,url,want_code,want_err_re", [
@ -43,23 +44,10 @@ INTERNAL_ERR_RE = re.compile("TLSV1_UNRECOGNIZED_NAME")
("withdefault.yml", "https://https-only.nginx-proxy.test/", 200, None), ("withdefault.yml", "https://https-only.nginx-proxy.test/", 200, None),
("withdefault.yml", "http://http-only.nginx-proxy.test/", 200, None), ("withdefault.yml", "http://http-only.nginx-proxy.test/", 200, None),
("withdefault.yml", "https://http-only.nginx-proxy.test/", 503, None), ("withdefault.yml", "https://http-only.nginx-proxy.test/", 503, None),
("withdefault.yml", "http://missing-cert.nginx-proxy.test/", 301, None), ("withdefault.yml", "http://missing-cert.nginx-proxy.test/", 200, None),
("withdefault.yml", "https://missing-cert.nginx-proxy.test/", 200, None), ("withdefault.yml", "https://missing-cert.nginx-proxy.test/", 500, None),
("withdefault.yml", "http://missing-cert.default-untrusted.nginx-proxy.test/", 200, None),
("withdefault.yml", "https://missing-cert.default-untrusted.nginx-proxy.test/", None, INTERNAL_ERR_RE),
("withdefault.yml", "http://unknown.nginx-proxy.test/", 503, None), ("withdefault.yml", "http://unknown.nginx-proxy.test/", 503, None),
("withdefault.yml", "https://unknown.nginx-proxy.test/", 503, None), ("withdefault.yml", "https://unknown.nginx-proxy.test/", 503, None),
# Same as withdefault.yml, except default.crt is not trusted (TRUST_DEFAULT_CERT=false).
("untrusteddefault.yml", "http://https-and-http.nginx-proxy.test/", 301, None),
("untrusteddefault.yml", "https://https-and-http.nginx-proxy.test/", 200, None),
("untrusteddefault.yml", "http://https-only.nginx-proxy.test/", 503, None),
("untrusteddefault.yml", "https://https-only.nginx-proxy.test/", 200, None),
("untrusteddefault.yml", "http://http-only.nginx-proxy.test/", 200, None),
("untrusteddefault.yml", "https://http-only.nginx-proxy.test/", 503, None),
("untrusteddefault.yml", "http://missing-cert.nginx-proxy.test/", 200, None),
("untrusteddefault.yml", "https://missing-cert.nginx-proxy.test/", None, INTERNAL_ERR_RE),
("untrusteddefault.yml", "http://unknown.nginx-proxy.test/", 503, None),
("untrusteddefault.yml", "https://unknown.nginx-proxy.test/", 503, None),
# Same as withdefault.yml, except there is no default.crt. # Same as withdefault.yml, except there is no default.crt.
("nodefault.yml", "http://https-and-http.nginx-proxy.test/", 301, None), ("nodefault.yml", "http://https-and-http.nginx-proxy.test/", 301, None),
("nodefault.yml", "https://https-and-http.nginx-proxy.test/", 200, None), ("nodefault.yml", "https://https-and-http.nginx-proxy.test/", 200, None),
@ -72,46 +60,45 @@ INTERNAL_ERR_RE = re.compile("TLSV1_UNRECOGNIZED_NAME")
("nodefault.yml", "http://unknown.nginx-proxy.test/", 503, None), ("nodefault.yml", "http://unknown.nginx-proxy.test/", 503, None),
("nodefault.yml", "https://unknown.nginx-proxy.test/", None, INTERNAL_ERR_RE), ("nodefault.yml", "https://unknown.nginx-proxy.test/", None, INTERNAL_ERR_RE),
# HTTPS_METHOD=nohttp on nginx-proxy, HTTPS_METHOD unset on the app container. # HTTPS_METHOD=nohttp on nginx-proxy, HTTPS_METHOD unset on the app container.
("nohttp.yml", "http://https-only.nginx-proxy.test/", 503, None), ("nohttp.yml", "http://https-only.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
("nohttp.yml", "https://https-only.nginx-proxy.test/", 200, None), ("nohttp.yml", "https://https-only.nginx-proxy.test/", 200, None),
("nohttp.yml", "http://unknown.nginx-proxy.test/", 503, None), ("nohttp.yml", "http://unknown.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
("nohttp.yml", "https://unknown.nginx-proxy.test/", 503, None), ("nohttp.yml", "https://unknown.nginx-proxy.test/", 503, None),
# HTTPS_METHOD=redirect on nginx-proxy, HTTPS_METHOD=nohttp on the app container. # HTTPS_METHOD=redirect on nginx-proxy, HTTPS_METHOD=nohttp on the app container.
("nohttp-on-app.yml", "http://https-only.nginx-proxy.test/", 503, None), ("nohttp-on-app.yml", "http://https-only.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
("nohttp-on-app.yml", "https://https-only.nginx-proxy.test/", 200, None), ("nohttp-on-app.yml", "https://https-only.nginx-proxy.test/", 200, None),
("nohttp-on-app.yml", "http://unknown.nginx-proxy.test/", 503, None), ("nohttp-on-app.yml", "http://unknown.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
("nohttp-on-app.yml", "https://unknown.nginx-proxy.test/", 503, None), ("nohttp-on-app.yml", "https://unknown.nginx-proxy.test/", 503, None),
# Same as nohttp.yml, except there are two vhosts with a missing cert, the second # Same as nohttp.yml, except there is a vhost with a missing cert. This causes its
# one being configured not to trust the default certificate. This causes its # HTTPS_METHOD=nohttp setting to effectively become HTTPS_METHOD=noredirect. This means that
# HTTPS_METHOD=nohttp setting to effectively become HTTPS_METHOD=noredirect. # there will be a plain http server solely to support that vhost, so http requests to other
# vhosts get a 503, not a connection refused error.
("nohttp-with-missing-cert.yml", "http://https-only.nginx-proxy.test/", 503, None), ("nohttp-with-missing-cert.yml", "http://https-only.nginx-proxy.test/", 503, None),
("nohttp-with-missing-cert.yml", "https://https-only.nginx-proxy.test/", 200, None), ("nohttp-with-missing-cert.yml", "https://https-only.nginx-proxy.test/", 200, None),
("nohttp-with-missing-cert.yml", "http://missing-cert.nginx-proxy.test/", 503, None), ("nohttp-with-missing-cert.yml", "http://missing-cert.nginx-proxy.test/", 200, None),
("nohttp-with-missing-cert.yml", "https://missing-cert.nginx-proxy.test/", 200, None), ("nohttp-with-missing-cert.yml", "https://missing-cert.nginx-proxy.test/", 500, None),
("nohttp-with-missing-cert.yml", "http://missing-cert.default-untrusted.nginx-proxy.test/", 200, None),
("nohttp-with-missing-cert.yml", "https://missing-cert.default-untrusted.nginx-proxy.test/", None, INTERNAL_ERR_RE),
("nohttp-with-missing-cert.yml", "http://unknown.nginx-proxy.test/", 503, None), ("nohttp-with-missing-cert.yml", "http://unknown.nginx-proxy.test/", 503, None),
("nohttp-with-missing-cert.yml", "https://unknown.nginx-proxy.test/", 503, None), ("nohttp-with-missing-cert.yml", "https://unknown.nginx-proxy.test/", 503, None),
# HTTPS_METHOD=nohttps on nginx-proxy, HTTPS_METHOD unset on the app container. # HTTPS_METHOD=nohttps on nginx-proxy, HTTPS_METHOD unset on the app container.
("nohttps.yml", "http://http-only.nginx-proxy.test/", 200, None), ("nohttps.yml", "http://http-only.nginx-proxy.test/", 200, None),
("nohttps.yml", "https://http-only.nginx-proxy.test/", None, INTERNAL_ERR_RE), ("nohttps.yml", "https://http-only.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
("nohttps.yml", "http://unknown.nginx-proxy.test/", 503, None), ("nohttps.yml", "http://unknown.nginx-proxy.test/", 503, None),
("nohttps.yml", "https://unknown.nginx-proxy.test/", None, INTERNAL_ERR_RE), ("nohttps.yml", "https://unknown.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
# HTTPS_METHOD=redirect on nginx-proxy, HTTPS_METHOD=nohttps on the app container. # HTTPS_METHOD=redirect on nginx-proxy, HTTPS_METHOD=nohttps on the app container.
("nohttps-on-app.yml", "http://http-only.nginx-proxy.test/", 200, None), ("nohttps-on-app.yml", "http://http-only.nginx-proxy.test/", 200, None),
("nohttps-on-app.yml", "https://http-only.nginx-proxy.test/", None, INTERNAL_ERR_RE), ("nohttps-on-app.yml", "https://http-only.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
("nohttps-on-app.yml", "http://unknown.nginx-proxy.test/", 503, None), ("nohttps-on-app.yml", "http://unknown.nginx-proxy.test/", 503, None),
("nohttps-on-app.yml", "https://unknown.nginx-proxy.test/", None, INTERNAL_ERR_RE), ("nohttps-on-app.yml", "https://unknown.nginx-proxy.test/", None, CONNECTION_REFUSED_RE),
# Custom nginx config that has a `server` directive that uses `default_server` and simply # Custom nginx config that has a `server` directive that uses `default_server` and simply
# returns 418. Nginx should successfully start (in particular, the `default_server` in the # returns 418. Nginx should successfully start (in particular, the `default_server` in the
# custom config should not conflict with the fallback server generated by nginx-proxy) and nginx # custom config should not conflict with the fallback server generated by nginx-proxy) and nginx
# should prefer that server for handling requests for unknown vhosts. # should prefer that server for handling requests for unknown vhosts.
("custom-fallback.yml", "http://unknown.nginx-proxy.test/", 418, None), ("custom-fallback.yml", "http://unknown.nginx-proxy.test/", 418, None),
]) ])
def test_fallback(get, compose_file, url, want_code, want_err_re): def test_fallback(get, url, want_code, want_err_re):
if want_err_re is None: if want_err_re is None:
r = get(url) r = get(url)
assert r.status_code == want_code assert r.status_code == want_code
else: else:
with pytest.raises(requests.exceptions.SSLError, match=want_err_re): with pytest.raises(requests.exceptions.RequestException, match=want_err_re):
get(url) get(url)

View file

@ -1,9 +0,0 @@
services:
nginx-proxy:
image: nginxproxy/nginx-proxy:test
container_name: nginx-proxy
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
ports:
- "80:80"
- "443:443"

View file

@ -1,12 +0,0 @@
services:
nginx-proxy:
environment:
HTTPS_METHOD: nohttps
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test

View file

@ -1,49 +0,0 @@
services:
nginx-proxy:
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- ${PYTEST_MODULE_PATH}/test_fallback.data/withdefault.certs:/etc/nginx/certs:ro
https-and-http:
image: web
expose:
- "81"
environment:
WEB_PORTS: "81"
VIRTUAL_HOST: https-and-http.nginx-proxy.test
https-only:
image: web
expose:
- "82"
environment:
WEB_PORTS: "82"
VIRTUAL_HOST: https-only.nginx-proxy.test
HTTPS_METHOD: nohttp
http-only:
image: web
expose:
- "83"
environment:
WEB_PORTS: "83"
VIRTUAL_HOST: http-only.nginx-proxy.test
HTTPS_METHOD: nohttps
missing-cert:
image: web
expose:
- "84"
environment:
WEB_PORTS: "84"
VIRTUAL_HOST: missing-cert.nginx-proxy.test
missing-cert-default-untrusted:
image: web
expose:
- "85"
environment:
WEB_PORTS: "85"
VIRTUAL_HOST: missing-cert.default-untrusted.nginx-proxy.test
labels:
com.github.nginx-proxy.nginx-proxy.trust-default-cert: "false"

Some files were not shown because too many files have changed in this diff Show more