├── entry.sh ├── Procfile ├── updatessl.sh ├── Dockerfile.alpine ├── Dockerfile ├── README.md ├── .github └── workflows │ └── dockerhub.yml └── nginx.tmpl /entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f /etc/nginx/socks/* 4 | 5 | 6 | 7 | exec /app/docker-entrypoint.sh "$@" 8 | 9 | 10 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | dockergen: docker-gen -watch -notify "/app/updatessl.sh updatessl" /app/nginx.tmpl /etc/nginx/conf.d/default.conf 2 | nginx: nginx 3 | -------------------------------------------------------------------------------- /updatessl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | _SCRIPT_="$0" 4 | 5 | ACME_BIN="/acme.sh/acme.sh --home /acme.sh --config-home /acmecerts" 6 | 7 | DEFAULT_CONF="/etc/nginx/conf.d/default.conf" 8 | 9 | 10 | CERTS="/etc/nginx/certs" 11 | 12 | 13 | updatessl() { 14 | nginx -t && nginx -s reload 15 | if grep ACME_DOMAINS $DEFAULT_CONF ; then 16 | for d_list in $(grep ACME_DOMAINS $DEFAULT_CONF | cut -d ' ' -f 2); 17 | do 18 | d=$(echo "$d_list" | cut -d , -f 1) 19 | $ACME_BIN --issue --server letsencrypt -k ec-256 \ 20 | -d $d_list \ 21 | --nginx \ 22 | --fullchain-file "$CERTS/$d.crt" \ 23 | --key-file "$CERTS/$d.key" \ 24 | --reloadcmd "nginx -t && nginx -s reload" 25 | done 26 | 27 | #generate nginx conf again. 28 | docker-gen /app/nginx.tmpl /etc/nginx/conf.d/default.conf 29 | else 30 | echo "skip updatessl" 31 | fi 32 | nginx -t && nginx -s reload 33 | } 34 | 35 | 36 | 37 | 38 | "$@" 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM nginxproxy/nginx-proxy:alpine 2 | 3 | RUN apk add nginx-module-njs 4 | 5 | ENV AUTO_UPGRADE=1 6 | ENV LE_WORKING_DIR=/acme.sh 7 | ENV LE_CONFIG_HOME=/acmecerts 8 | RUN wget -O- https://get.acme.sh | sh && crontab -l | sed 's#> /dev/null##' | crontab - \ 9 | && $LE_WORKING_DIR/acme.sh --set-default-ca --server letsencrypt 10 | 11 | VOLUME ["/acmecerts"] 12 | EXPOSE 443 13 | 14 | COPY nginx.tmpl /app/ 15 | COPY Procfile /app/ 16 | RUN echo "cron: crond -f" >>/app/Procfile 17 | COPY updatessl.sh /app/ 18 | 19 | RUN chmod +x /app/updatessl.sh 20 | 21 | RUN mkdir -p /etc/nginx/stream.d && echo "stream { \ 22 | include /etc/nginx/stream.d/*.conf; \ 23 | }" >> /etc/nginx/nginx.conf 24 | 25 | 26 | RUN sed -i '1s|^|load_module modules/ngx_http_js_module.so;\n|' /etc/nginx/nginx.conf \ 27 | && sed -i '1s|^|load_module modules/ngx_stream_js_module.so;\n|' /etc/nginx/nginx.conf 28 | 29 | RUN mkdir -p /etc/nginx/socks 30 | 31 | 32 | VOLUME ["/etc/nginx/stream.d"] 33 | 34 | 35 | COPY entry.sh /app/ 36 | RUN chmod +x /app/entry.sh 37 | 38 | 39 | ENTRYPOINT ["/app/entry.sh"] 40 | CMD ["forego", "start", "-r"] 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginxproxy/nginx-proxy:latest 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y -q --no-install-recommends \ 5 | cron curl nginx-module-njs \ 6 | && apt-get clean \ 7 | && rm -r /var/lib/apt/lists/* 8 | 9 | 10 | ENV AUTO_UPGRADE=1 11 | ENV LE_WORKING_DIR=/acme.sh 12 | ENV LE_CONFIG_HOME=/acmecerts 13 | RUN curl https://get.acme.sh | sh && crontab -l | sed 's#> /dev/null##' | crontab - \ 14 | && $LE_WORKING_DIR/acme.sh --set-default-ca --server letsencrypt 15 | 16 | VOLUME ["/acmecerts"] 17 | EXPOSE 443 18 | 19 | COPY nginx.tmpl /app/ 20 | COPY Procfile /app/ 21 | RUN echo "cron: cron -f" >>/app/Procfile 22 | 23 | COPY updatessl.sh /app/ 24 | 25 | RUN chmod +x /app/updatessl.sh 26 | 27 | RUN mkdir -p /etc/nginx/stream.d && echo "stream { \ 28 | include /etc/nginx/stream.d/*.conf; \ 29 | }" >> /etc/nginx/nginx.conf 30 | 31 | RUN sed -i '1s|^|load_module modules/ngx_http_js_module.so;\n|' /etc/nginx/nginx.conf \ 32 | && sed -i '1s|^|load_module modules/ngx_stream_js_module.so;\n|' /etc/nginx/nginx.conf 33 | 34 | RUN mkdir -p /etc/nginx/socks 35 | 36 | 37 | VOLUME ["/etc/nginx/stream.d"] 38 | 39 | COPY entry.sh /app/ 40 | RUN chmod +x /app/entry.sh 41 | 42 | ENTRYPOINT ["/app/entry.sh"] 43 | 44 | CMD ["forego", "start", "-r"] 45 | 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Based on https://github.com/nginx-proxy/nginx-proxy 2 | 3 | A new env varaible `ENABLE_ACME` is added to use acme.sh to generate free ssl cert from letsencrypt. 4 | 5 | All the other options are the same as the upstream project. 6 | It's very easy to use: 7 | 8 | ### 1. Run nginx reverse proxy 9 | 10 | ```sh 11 | docker run \ 12 | -p 80:80 \ 13 | -p 443:443 \ 14 | -it -d --rm \ 15 | -v /var/run/docker.sock:/tmp/docker.sock:ro \ 16 | -v $(pwd)/proxy/certs:/etc/nginx/certs \ 17 | -v $(pwd)/proxy/acme:/acmecerts \ 18 | -v $(pwd)/proxy/conf.d:/etc/nginx/conf.d \ 19 | -v $(pwd)/vhost.d:/etc/nginx/vhost.d \ 20 | -v $(pwd)/stream.d:/etc/nginx/stream.d \ 21 | -v $(pwd)/dhparam:/etc/nginx/dhparam \ 22 | --name proxy \ 23 | neilpang/letsproxy 24 | ``` 25 | 26 | It's recommended to run with `--net=host` option, like: 27 | 28 | ```sh 29 | docker run \ 30 | -it -d --rm \ 31 | -v /var/run/docker.sock:/tmp/docker.sock:ro \ 32 | -v $(pwd)/proxy/certs:/etc/nginx/certs \ 33 | -v $(pwd)/proxy/acme:/acmecerts \ 34 | -v $(pwd)/proxy/conf.d:/etc/nginx/conf.d \ 35 | -v $(pwd)/vhost.d:/etc/nginx/vhost.d \ 36 | -v $(pwd)/stream.d:/etc/nginx/stream.d \ 37 | -v $(pwd)/dhparam:/etc/nginx/dhparam \ 38 | --name proxy \ 39 | --net=host \ 40 | neilpang/letsproxy 41 | ``` 42 | 43 | For a docker compose v2 or v3 project, every project has a dedicated network, so, you must use `--net=host` option, so that it can proxy any projects on you machine. 44 | 45 | 46 | #### Docker Compose 47 | ```yaml 48 | version: '2' 49 | 50 | services: 51 | letsproxy: 52 | image: neilpang/letsproxy 53 | ports: 54 | - "80:80" 55 | - "443:443" 56 | volumes: 57 | - /var/run/docker.sock:/tmp/docker.sock:ro 58 | - ./proxy/certs:/etc/nginx/certs 59 | - ./proxy/acme:/acmecerts 60 | - ./proxy/conf.d:/etc/nginx/conf.d 61 | - ./proxy/vhost.d:/etc/nginx/vhost.d 62 | - ./proxy/stream.d:/etc/nginx/stream.d 63 | - ./proxy/dhparam:/etc/nginx/dhparam 64 | network_mode: "host" 65 | ``` 66 | 67 | 68 | ### 2. Run an internal webserver 69 | 70 | ```sh 71 | docker run -itd --rm \ 72 | -e VIRTUAL_HOST=foo.bar.com \ 73 | -e ENABLE_ACME=true \ 74 | httpd 75 | 76 | ``` 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub.yml: -------------------------------------------------------------------------------- 1 | name: DockerHub 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | multiarch-build-debian: 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Get Docker tags for Debian based image 21 | id: docker_meta_debian 22 | uses: crazy-max/ghaction-docker-meta@v2 23 | with: 24 | images: | 25 | neilpang/letsproxy 26 | tags: | 27 | type=semver,pattern={{version}} 28 | type=semver,pattern={{major}}.{{minor}} 29 | type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} 30 | - name: Set up QEMU 31 | uses: docker/setup-qemu-action@v3 32 | 33 | - name: Set up Docker Buildx 34 | uses: docker/setup-buildx-action@v3 35 | 36 | - name: Login to DockerHub 37 | uses: docker/login-action@v3 38 | with: 39 | username: ${{ secrets.DOCKERHUB_USERNAME }} 40 | password: ${{ secrets.DOCKERHUB_TOKEN }} 41 | 42 | - name: Build and push the Debian based image 43 | id: docker_build_debian 44 | uses: docker/build-push-action@v6 45 | with: 46 | file: Dockerfile 47 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/s390x 48 | push: true 49 | tags: ${{ steps.docker_meta_debian.outputs.tags }} 50 | labels: ${{ steps.docker_meta_debian.outputs.labels }} 51 | 52 | - name: Images digests 53 | run: echo ${{ steps.docker_build_debian.outputs.digest }} 54 | 55 | multiarch-build-alpine: 56 | runs-on: ubuntu-latest 57 | steps: 58 | 59 | - name: Checkout 60 | uses: actions/checkout@v4 61 | with: 62 | fetch-depth: 0 63 | 64 | - name: Get Docker tags for Alpine based image 65 | id: docker_meta_alpine 66 | uses: crazy-max/ghaction-docker-meta@v2 67 | with: 68 | images: | 69 | neilpang/letsproxy 70 | tags: | 71 | type=semver,suffix=-alpine,pattern={{version}} 72 | type=semver,suffix=-alpine,pattern={{major}}.{{minor}} 73 | type=raw,value=alpine,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} 74 | flavor: latest=false 75 | 76 | - name: Set up QEMU 77 | uses: docker/setup-qemu-action@v3 78 | 79 | - name: Set up Docker Buildx 80 | uses: docker/setup-buildx-action@v3 81 | 82 | - name: Login to DockerHub 83 | uses: docker/login-action@v3 84 | with: 85 | username: ${{ secrets.DOCKERHUB_USERNAME }} 86 | password: ${{ secrets.DOCKERHUB_TOKEN }} 87 | 88 | - name: Build and push the Alpine based image 89 | id: docker_build_alpine 90 | uses: docker/build-push-action@v6 91 | with: 92 | file: Dockerfile.alpine 93 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/s390x 94 | push: true 95 | tags: ${{ steps.docker_meta_alpine.outputs.tags }} 96 | labels: ${{ steps.docker_meta_alpine.outputs.labels }} 97 | 98 | - name: Images digests 99 | run: echo ${{ steps.docker_build_alpine.outputs.digest }} 100 | -------------------------------------------------------------------------------- /nginx.tmpl: -------------------------------------------------------------------------------- 1 | {{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }} 2 | 3 | {{ $nginx_proxy_version := coalesce $.Env.NGINX_PROXY_VERSION "" }} 4 | {{ $external_http_port := coalesce $.Env.HTTP_PORT "80" }} 5 | {{ $external_https_port := coalesce $.Env.HTTPS_PORT "443" }} 6 | {{ $debug_all := $.Env.DEBUG }} 7 | {{ $sha1_upstream_name := parseBool (coalesce $.Env.SHA1_UPSTREAM_NAME "false") }} 8 | {{ $default_root_response := coalesce $.Env.DEFAULT_ROOT "404" }} 9 | 10 | {{ define "ssl_policy" }} 11 | {{ if eq .ssl_policy "Mozilla-Modern" }} 12 | ssl_protocols TLSv1.3; 13 | {{/* nginx currently lacks ability to choose ciphers in TLS 1.3 in configuration, see https://trac.nginx.org/nginx/ticket/1529 /*}} 14 | {{/* a possible workaround can be modify /etc/ssl/openssl.cnf to change it globally (see https://trac.nginx.org/nginx/ticket/1529#comment:12 ) /*}} 15 | {{/* explicitly set ngnix default value in order to allow single servers to override the global http value */}} 16 | ssl_ciphers HIGH:!aNULL:!MD5; 17 | ssl_prefer_server_ciphers off; 18 | {{ else if eq .ssl_policy "Mozilla-Intermediate" }} 19 | ssl_protocols TLSv1.2 TLSv1.3; 20 | 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'; 21 | ssl_prefer_server_ciphers off; 22 | {{ else if eq .ssl_policy "Mozilla-Old" }} 23 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 24 | 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'; 25 | ssl_prefer_server_ciphers on; 26 | {{ else if eq .ssl_policy "AWS-TLS-1-2-2017-01" }} 27 | ssl_protocols TLSv1.2 TLSv1.3; 28 | ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES128-SHA256:AES256-GCM-SHA384:AES256-SHA256'; 29 | ssl_prefer_server_ciphers on; 30 | {{ else if eq .ssl_policy "AWS-TLS-1-1-2017-01" }} 31 | ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; 32 | 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'; 33 | ssl_prefer_server_ciphers on; 34 | {{ else if eq .ssl_policy "AWS-2016-08" }} 35 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 36 | 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'; 37 | ssl_prefer_server_ciphers on; 38 | {{ else if eq .ssl_policy "AWS-2015-05" }} 39 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 40 | 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'; 41 | ssl_prefer_server_ciphers on; 42 | {{ else if eq .ssl_policy "AWS-2015-03" }} 43 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 44 | 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'; 45 | ssl_prefer_server_ciphers on; 46 | {{ else if eq .ssl_policy "AWS-2015-02" }} 47 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; 48 | 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'; 49 | ssl_prefer_server_ciphers on; 50 | {{ end }} 51 | {{ end }} 52 | 53 | {{ define "location" }} 54 | location {{ .Path }} { 55 | {{ if eq .NetworkTag "internal" }} 56 | # Only allow traffic from internal clients 57 | include /etc/nginx/network_internal.conf; 58 | {{ end }} 59 | 60 | {{ if eq .Proto "uwsgi" }} 61 | include uwsgi_params; 62 | uwsgi_pass {{ trim .Proto }}://{{ trim .Upstream }}; 63 | {{ else if eq .Proto "fastcgi" }} 64 | root {{ trim .VhostRoot }}; 65 | include fastcgi_params; 66 | fastcgi_pass {{ trim .Upstream }}; 67 | {{ else if eq .Proto "grpc" }} 68 | grpc_pass {{ trim .Proto }}://{{ trim .Upstream }}; 69 | {{ else }} 70 | proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}{{ trim .Dest }}; 71 | {{ end }} 72 | 73 | {{ if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }} 74 | auth_basic "Restricted {{ .Host }}"; 75 | auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }}; 76 | {{ end }} 77 | 78 | {{ if (exists (printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) )) }} 79 | include {{ printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) }}; 80 | {{ else if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} 81 | include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}}; 82 | {{ else if (exists "/etc/nginx/vhost.d/default_location") }} 83 | include /etc/nginx/vhost.d/default_location; 84 | {{ end }} 85 | } 86 | {{ end }} 87 | 88 | {{ define "upstream" }} 89 | {{ $networks := .Networks }} 90 | {{ $debug_all := .Debug }} 91 | upstream {{ .Upstream }} { 92 | {{ $server_found := "false" }} 93 | {{ range $container := .Containers }} 94 | 95 | {{ $enable_acme := eq (or ($container.Env.ENABLE_ACME) "") "true" }} 96 | {{ if $enable_acme }} 97 | 98 | #ACME_DOMAINS {{$container.Env.VIRTUAL_HOST}} 99 | 100 | {{ end }} 101 | 102 | {{ $debug := (eq (coalesce $container.Env.DEBUG $debug_all "false") "true") }} 103 | {{/* If only 1 port exposed, use that as a default, else 80 */}} 104 | {{ $defaultPort := (when (eq (len $container.Addresses) 1) (first $container.Addresses) (dict "Port" "80")).Port }} 105 | {{ $port := (coalesce $container.Env.VIRTUAL_PORT $defaultPort) }} 106 | {{ $address := where $container.Addresses "Port" $port | first }} 107 | {{ if $debug }} 108 | # Exposed ports: {{ $container.Addresses }} 109 | # Default virtual port: {{ $defaultPort }} 110 | # VIRTUAL_PORT: {{ $container.Env.VIRTUAL_PORT }} 111 | {{ if not $address }} 112 | # /!\ Virtual port not exposed 113 | {{ end }} 114 | {{ end }} 115 | {{ range $knownNetwork := $networks }} 116 | {{ range $containerNetwork := $container.Networks }} 117 | {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} 118 | ## Can be connected with "{{ $containerNetwork.Name }}" network 119 | {{ if $address }} 120 | {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} 121 | {{ if and $container.Node.ID $address.HostPort }} 122 | {{ $server_found = "true" }} 123 | # {{ $container.Node.Name }}/{{ $container.Name }} 124 | server {{ $container.Node.Address.IP }}:{{ $address.HostPort }}; 125 | {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} 126 | {{ else if $containerNetwork }} 127 | {{ $server_found = "true" }} 128 | # {{ $container.Name }} 129 | server {{ $containerNetwork.IP }}:{{ $address.Port }}; 130 | {{ end }} 131 | {{ else if $containerNetwork }} 132 | # {{ $container.Name }} 133 | {{ if $containerNetwork.IP }} 134 | {{ $server_found = "true" }} 135 | server {{ $containerNetwork.IP }}:{{ $port }}; 136 | {{ else }} 137 | # /!\ No IP for this network! 138 | {{ end }} 139 | {{ end }} 140 | {{ else }} 141 | # Cannot connect to network '{{ $containerNetwork.Name }}' of this container 142 | {{ end }} 143 | {{ end }} 144 | {{ end }} 145 | {{ end }} 146 | {{/* nginx-proxy/nginx-proxy#1105 */}} 147 | {{ if (eq $server_found "false") }} 148 | # Fallback entry 149 | server 127.0.0.1 down; 150 | {{ end }} 151 | } 152 | {{ end }} 153 | 154 | 155 | 156 | client_max_body_size 4096M; 157 | 158 | 159 | {{ if ne $nginx_proxy_version "" }} 160 | # nginx-proxy version : {{ $nginx_proxy_version }} 161 | {{ end }} 162 | 163 | # If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the 164 | # scheme used to connect to this server 165 | map $http_x_forwarded_proto $proxy_x_forwarded_proto { 166 | default $http_x_forwarded_proto; 167 | '' $scheme; 168 | } 169 | 170 | # If we receive X-Forwarded-Port, pass it through; otherwise, pass along the 171 | # server port the client connected to 172 | map $http_x_forwarded_port $proxy_x_forwarded_port { 173 | default $http_x_forwarded_port; 174 | '' $server_port; 175 | } 176 | 177 | # If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any 178 | # Connection header that may have been passed to this server 179 | map $http_upgrade $proxy_connection { 180 | default upgrade; 181 | '' close; 182 | } 183 | 184 | # Apply fix for very long server names 185 | server_names_hash_bucket_size 128; 186 | 187 | # Default dhparam 188 | {{ if (exists "/etc/nginx/dhparam/dhparam.pem") }} 189 | ssl_dhparam /etc/nginx/dhparam/dhparam.pem; 190 | {{ end }} 191 | 192 | # Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto 193 | map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl { 194 | default off; 195 | https on; 196 | } 197 | 198 | gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 199 | 200 | log_format vhost '$host $remote_addr - $remote_user [$time_local] ' 201 | '"$request" $status $body_bytes_sent ' 202 | '"$http_referer" "$http_user_agent" ' 203 | '"$upstream_addr"'; 204 | 205 | access_log off; 206 | 207 | {{/* Get the SSL_POLICY defined by this container, falling back to "Mozilla-Intermediate" */}} 208 | {{ $ssl_policy := or ($.Env.SSL_POLICY) "Mozilla-Intermediate" }} 209 | {{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }} 210 | error_log /dev/stderr; 211 | 212 | {{ if $.Env.RESOLVERS }} 213 | resolver {{ $.Env.RESOLVERS }}; 214 | {{ end }} 215 | 216 | {{ if (exists "/etc/nginx/proxy.conf") }} 217 | include /etc/nginx/proxy.conf; 218 | {{ else }} 219 | # HTTP 1.1 support 220 | proxy_http_version 1.1; 221 | proxy_buffering off; 222 | proxy_set_header Host $http_host; 223 | proxy_set_header Upgrade $http_upgrade; 224 | proxy_set_header Connection $proxy_connection; 225 | proxy_set_header X-Real-IP $remote_addr; 226 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 227 | proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; 228 | proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; 229 | proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; 230 | proxy_set_header X-Original-URI $request_uri; 231 | 232 | # Mitigate httpoxy attack (see README for details) 233 | proxy_set_header Proxy ""; 234 | {{ end }} 235 | 236 | {{ $access_log := (or (and (not $.Env.DISABLE_ACCESS_LOGS) "access_log /var/log/nginx/access.log vhost;") "") }} 237 | 238 | {{ $enable_ipv6 := eq (or ($.Env.ENABLE_IPV6) "") "true" }} 239 | server { 240 | server_name _; # This is just an invalid value which will never trigger on a real hostname. 241 | server_tokens off; 242 | listen {{ $external_http_port }}; 243 | {{ if $enable_ipv6 }} 244 | listen [::]:{{ $external_http_port }}; 245 | {{ end }} 246 | {{ $access_log }} 247 | include /etc/nginx/vhost.d/default; 248 | location / { 249 | return 444; 250 | } 251 | } 252 | 253 | {{ if (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} 254 | server { 255 | server_name _; # This is just an invalid value which will never trigger on a real hostname. 256 | server_tokens off; 257 | listen {{ $external_https_port }} ssl http2; 258 | {{ if $enable_ipv6 }} 259 | listen [::]:{{ $external_https_port }} ssl http2; 260 | {{ end }} 261 | {{ $access_log }} 262 | include /etc/nginx/vhost.d/default; 263 | location / { 264 | return 444; 265 | } 266 | ssl_session_cache shared:SSL:50m; 267 | ssl_session_tickets off; 268 | ssl_certificate /etc/nginx/certs/default.crt; 269 | ssl_certificate_key /etc/nginx/certs/default.key; 270 | } 271 | {{ end }} 272 | 273 | {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} 274 | 275 | {{ $host := trim $host }} 276 | {{ $is_regexp := hasPrefix "~" $host }} 277 | {{ $upstream_name := when (or $is_regexp $sha1_upstream_name) (sha1 $host) $host }} 278 | 279 | {{ $paths := groupBy $containers "Env.VIRTUAL_PATH" }} 280 | {{ $nPaths := len $paths }} 281 | 282 | {{ if eq $nPaths 0 }} 283 | # {{ $host }} 284 | {{ template "upstream" (dict "Upstream" $upstream_name "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} 285 | {{ else }} 286 | {{ range $path, $containers := $paths }} 287 | {{ $sum := sha1 $path }} 288 | {{ $upstream := printf "%s-%s" $upstream_name $sum }} 289 | # {{ $host }}{{ $path }} 290 | {{ template "upstream" (dict "Upstream" $upstream "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} 291 | {{ end }} 292 | {{ end }} 293 | 294 | {{ $default_host := or ($.Env.DEFAULT_HOST) "" }} 295 | {{ $default_server := index (dict $host "" $default_host "default_server") $host }} 296 | 297 | {{/* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "" */}} 298 | {{ $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }} 299 | 300 | 301 | {{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}} 302 | {{ $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) (or $.Env.HTTPS_METHOD "redirect") }} 303 | 304 | {{/* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default) */}} 305 | {{ $ssl_policy := or (first (groupByKeys $containers "Env.SSL_POLICY")) "" }} 306 | 307 | {{/* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000" */}} 308 | {{ $hsts := or (first (groupByKeys $containers "Env.HSTS")) (or $.Env.HSTS "max-age=31536000") }} 309 | 310 | {{/* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}} 311 | {{ $vhost_root := or (first (groupByKeys $containers "Env.VIRTUAL_ROOT")) "/var/www/public" }} 312 | 313 | 314 | {{/* Get the first cert name defined by containers w/ the same vhost */}} 315 | {{ $certName := (first (groupByKeys $containers "Env.CERT_NAME")) }} 316 | 317 | {{ $enable_socks := (first (groupByKeys $containers "Env.ENABLE_SOCKS")) }} 318 | 319 | {{/* Get the best matching cert by name for the vhost. */}} 320 | {{ $vhostCert := (closest (dir "/etc/nginx/certs") (printf "%s.crt" $host))}} 321 | 322 | {{/* vhostCert is actually a filename so remove any suffixes since they are added later */}} 323 | {{ $vhostCert := trimSuffix ".crt" $vhostCert }} 324 | {{ $vhostCert := trimSuffix ".key" $vhostCert }} 325 | 326 | {{/* Use the cert specified on the container or fallback to the best vhost match */}} 327 | {{ $cert := (coalesce $certName $vhostCert) }} 328 | 329 | {{ $is_https := (and (ne $https_method "nohttps") (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert))) }} 330 | 331 | {{ if $is_https }} 332 | 333 | {{ if eq $https_method "redirect" }} 334 | server { 335 | server_name {{ $host }}; 336 | {{ if $server_tokens }} 337 | server_tokens {{ $server_tokens }}; 338 | {{ end }} 339 | listen {{ $external_http_port }} {{ $default_server }}; 340 | {{ if eq $enable_socks "true" }} 341 | listen unix:/etc/nginx/socks/{{ $host }}.{{ $external_http_port }}.sock; 342 | {{ end }} 343 | {{ if $enable_ipv6 }} 344 | listen [::]:{{ $external_http_port }} {{ $default_server }}; 345 | {{ end }} 346 | {{ $access_log }} 347 | 348 | 349 | 350 | location / { 351 | {{ if eq $external_https_port "443" }} 352 | return 301 https://$host$request_uri; 353 | {{ else }} 354 | return 301 https://$host:{{ $external_https_port }}$request_uri; 355 | {{ end }} 356 | } 357 | } 358 | {{ end }} 359 | 360 | server { 361 | server_name {{ $host }}; 362 | {{ if $server_tokens }} 363 | server_tokens {{ $server_tokens }}; 364 | {{ end }} 365 | listen {{ $external_https_port }} ssl http2 {{ $default_server }}; 366 | {{ if eq $enable_socks "true" }} 367 | listen unix:/etc/nginx/socks/{{ $host }}.{{ $external_https_port }}.sock ssl http2 {{ $default_server }}; 368 | {{ end }} 369 | {{ if $enable_ipv6 }} 370 | listen [::]:{{ $external_https_port }} ssl http2 {{ $default_server }}; 371 | {{ end }} 372 | {{ $access_log }} 373 | 374 | {{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }} 375 | 376 | ssl_session_timeout 5m; 377 | ssl_session_cache shared:SSL:50m; 378 | ssl_session_tickets off; 379 | 380 | ssl_certificate /etc/nginx/certs/{{ (printf "%s.crt" $cert) }}; 381 | ssl_certificate_key /etc/nginx/certs/{{ (printf "%s.key" $cert) }}; 382 | 383 | {{ if (exists (printf "/etc/nginx/certs/%s.dhparam.pem" $cert)) }} 384 | ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }}; 385 | {{ end }} 386 | 387 | {{ if (exists (printf "/etc/nginx/certs/%s.chain.pem" $cert)) }} 388 | ssl_stapling on; 389 | resolver 8.8.4.4 8.8.8.8 valid=300s; 390 | resolver_timeout 10s; 391 | 392 | ssl_stapling_verify on; 393 | ssl_trusted_certificate {{ printf "/etc/nginx/certs/%s.chain.pem" $cert }}; 394 | {{ end }} 395 | 396 | {{ if (not (or (eq $https_method "noredirect") (eq $hsts "off"))) }} 397 | add_header Strict-Transport-Security "{{ trim $hsts }}" always; 398 | {{ end }} 399 | 400 | {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} 401 | include {{ printf "/etc/nginx/vhost.d/%s" $host }}; 402 | {{ else if (exists "/etc/nginx/vhost.d/default") }} 403 | include /etc/nginx/vhost.d/default; 404 | {{ end }} 405 | 406 | {{ if eq $nPaths 0 }} 407 | {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} 408 | {{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }} 409 | 410 | {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} 411 | {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} 412 | {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "VhostRoot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} 413 | {{ else }} 414 | {{ range $path, $container := $paths }} 415 | {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}} 416 | {{ $proto := trim (or (first (groupByKeys $container "Env.VIRTUAL_PROTO")) "http") }} 417 | 418 | {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} 419 | {{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }} 420 | {{ $sum := sha1 $path }} 421 | {{ $upstream := printf "%s-%s" $upstream_name $sum }} 422 | {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} 423 | {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} 424 | {{ end }} 425 | {{ if (not (contains $paths "/")) }} 426 | location / { 427 | return {{ $default_root_response }}; 428 | } 429 | {{ end }} 430 | {{ end }} 431 | } 432 | 433 | {{ end }} 434 | 435 | {{ if or (not $is_https) (eq $https_method "noredirect") }} 436 | 437 | server { 438 | server_name {{ $host }}; 439 | {{ if $server_tokens }} 440 | server_tokens {{ $server_tokens }}; 441 | {{ end }} 442 | listen {{ $external_http_port }} {{ $default_server }}; 443 | {{ if eq $enable_socks "true" }} 444 | listen unix:/etc/nginx/socks/{{ $host }}.{{ $external_http_port }}.sock; 445 | {{ end }} 446 | 447 | {{ if $enable_ipv6 }} 448 | listen [::]:{{ $external_http_port }} {{ $default_server }}; 449 | {{ end }} 450 | {{ $access_log }} 451 | 452 | {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} 453 | include {{ printf "/etc/nginx/vhost.d/%s" $host }}; 454 | {{ else if (exists "/etc/nginx/vhost.d/default") }} 455 | include /etc/nginx/vhost.d/default; 456 | {{ end }} 457 | 458 | {{ if eq $nPaths 0 }} 459 | {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} 460 | {{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }} 461 | 462 | {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} 463 | {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} 464 | {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "VhostRoot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} 465 | {{ else }} 466 | {{ range $path, $container := $paths }} 467 | {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}} 468 | {{ $proto := trim (or (first (groupByKeys $container "Env.VIRTUAL_PROTO")) "http") }} 469 | 470 | {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} 471 | {{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }} 472 | {{ $sum := sha1 $path }} 473 | {{ $upstream := printf "%s-%s" $upstream_name $sum }} 474 | {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} 475 | {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} 476 | {{ end }} 477 | {{ if (not (contains $paths "/")) }} 478 | location / { 479 | return {{ $default_root_response }}; 480 | } 481 | {{ end }} 482 | {{ end }} 483 | } 484 | 485 | {{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} 486 | server { 487 | server_name {{ $host }}; 488 | {{ if $server_tokens }} 489 | server_tokens {{ $server_tokens }}; 490 | {{ end }} 491 | listen {{ $external_https_port }} ssl http2 {{ $default_server }}; 492 | {{ if eq $enable_socks "true" }} 493 | listen unix:/etc/nginx/socks/{{ $host }}.{{ $external_https_port }}.sock ssl http2 {{ $default_server }}; 494 | {{ end }} 495 | {{ if $enable_ipv6 }} 496 | listen [::]:{{ $external_https_port }} ssl http2 {{ $default_server }}; 497 | {{ end }} 498 | {{ $access_log }} 499 | include /etc/nginx/vhost.d/default; 500 | location / { 501 | return 444; 502 | } 503 | 504 | ssl_certificate /etc/nginx/certs/default.crt; 505 | ssl_certificate_key /etc/nginx/certs/default.key; 506 | } 507 | {{ end }} 508 | 509 | {{ end }} 510 | {{ end }} 511 | --------------------------------------------------------------------------------