├── Dockerfile ├── LICENSE ├── ReadMe.md ├── config ├── logrotate.d │ └── nginx ├── nginx │ ├── webapp.1.conf │ ├── webapp.2.conf │ └── wellknown.conf └── supervisor │ └── supervisord.conf ├── docker-compose-example.yml ├── docker-compose-production.yml ├── docker-compose.yml ├── docker-entrypoint.sh ├── letsencrypt-run.py ├── letsencrypt-run.sh └── letsencrypt.ini /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for nginx letsencrypt proxy 2 | 3 | FROM ubuntu:16.04 4 | MAINTAINER Cristoffer Fairweather 5 | 6 | ENV DEBIAN_FRONTEND noninteractive 7 | 8 | RUN usermod -u 1000 www-data 9 | RUN groupmod -g 1000 www-data 10 | 11 | RUN apt-get update -qq && \ 12 | apt-get install -yqq \ 13 | nginx \ 14 | supervisor \ 15 | npm \ 16 | curl \ 17 | gettext-base \ 18 | git \ 19 | logrotate && \ 20 | apt-get clean 21 | 22 | # configure nginx 23 | RUN rm -f /etc/nginx/sites-enabled/default && \ 24 | rm -f /etc/nginx/sites-available/default 25 | COPY config/nginx/webapp.1.conf /etc/nginx/sites-available/webapp.1.conf 26 | COPY config/nginx/webapp.2.conf /etc/nginx/sites-available/webapp.2.conf 27 | COPY config/nginx/wellknown.conf /etc/nginx/sites-available/wellknown.conf 28 | # RUN ln -s /etc/nginx/sites-available/webapp.conf /etc/nginx/sites-enabled/webapp.conf 29 | 30 | # config supervisor 31 | RUN mkdir -p /var/log/supervisor 32 | COPY config/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf 33 | 34 | # Create ssl directory 35 | RUN mkdir -p /etc/nginx/ssl 36 | RUN chmod 400 -R /etc/nginx/ssl 37 | 38 | # Let's Encrypt Support 39 | WORKDIR /opt 40 | RUN git clone https://github.com/letsencrypt/letsencrypt 41 | COPY letsencrypt-run.py /opt/ 42 | COPY letsencrypt-run.sh /opt/ 43 | COPY letsencrypt.ini /opt/ 44 | RUN chmod +x letsencrypt-run.* 45 | RUN mkdir -p /var/www/challenges && chmod 777 -R /var/www/challenges 46 | RUN cd /opt/letsencrypt && ./letsencrypt-auto --help 47 | VOLUME /etc/letsencrypt 48 | 49 | # set up logrotate config 50 | COPY config/logrotate.d/nginx /etc/logrotate.d/nginx.tmpl 51 | 52 | # Add entrypoint 53 | COPY docker-entrypoint.sh / 54 | RUN chmod +x /docker-entrypoint.sh 55 | 56 | WORKDIR /opt 57 | EXPOSE 80 443 58 | CMD ["/docker-entrypoint.sh"] 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Annixa LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | Docker Nginx Proxy with Let's Encrypt 2 | ===================================== 3 | 4 | ![GitHub Release Version](https://img.shields.io/github/release/annixa/docker-nginx-letsencrypt-proxy.svg) 5 | ![Docker Hub Pulls](https://img.shields.io/docker/pulls/annixa/docker-nginx-letsencrypt-proxy.svg) 6 | ![Docker Hub Stars](https://img.shields.io/docker/stars/annixa/docker-nginx-letsencrypt-proxy.svg) 7 | ![GitHub Open Issues](https://img.shields.io/github/issues/annixa/docker-nginx-letsencrypt-proxy.svg) 8 | 9 | Docker Nginx Proxy with Let's Encrypt simplifies application integration with Let's Encrypt. 10 | 11 | This project provides a simple nginx configuration and auto-updating Let's Encrypt for integration with existing services. 12 | 13 | Docker Hub image: [docker-nginx-letsencrypt-proxy](https://hub.docker.com/r/annixa/docker-nginx-letsencrypt-proxy/) 14 | 15 | Quick Deploy (`docker-compose.yml`) 16 | ----------------------------------- 17 | > _"Put this in your stack and deploy it."_ 18 | ``` 19 | version: '2' 20 | docker-nginx-letsencrypt-proxy: 21 | build: . 22 | ports: 23 | - 80:80 24 | - 443:443 25 | container_name: docker-nginx-letsencrypt-proxy 26 | log_opt: 27 | max-size: 50k 28 | environment: 29 | - LE_ENABLED=true 30 | # - LE_TEST=true # LE is rate limited. While doing development, be sure to set testing mode so requests don't count against our quota. 31 | - LE_EMAIL=test@test.com # Your email, here 32 | - LE_DOMAIN=domain.com #A comma separated list of your domains, here 33 | - PROXY_DEST=https://www.google.com #A comma separated list of destinations for the proxied services 34 | # - PROXY_PORT=8443 35 | # - SLACK_NOTIFICATIONS_INFRA_URL=https://hooks.slack.com/services/???????? # Be sure to fill this in using your URL for the slack webhook integration 36 | volumes: 37 | - "/etc/letsencrypt" 38 | # links: 39 | # - mycontainer 40 | # If using version 1, link to your container 41 | ``` 42 | 43 | Configuration 44 | ------------- 45 | 46 | The following docker environment variables are required for proper usage: 47 | - `LE_EMAIL`, the email address for use with Let's Encrypt (simply registers your public key for retrieval). 48 | - `LE_DOMAIN`, a comma separated list of domains current configured to point at your server 49 | - `PROXY_DEST`, a comma separated list of destinations for the proxied services; along the lines of `http://mydestination.com` or `http://localhost:8000`. There should be as many destinations as `LE_DOMAIN`s; however, for each without a corresponding destination, the first destination will be used for the remaining `LE_DOMAIN`s. 50 | - `PROXY_PORT`, the port on which the https connections will be served. Defaults to 443 51 | - `SLACK_NOTIFICATIONS_INFRA_URL` (optional), the slack webhook integration URL to receive slack notifications upon certificate update or `letsencrypt-auto` error. 52 | - `LE_ENABLED` (optional, defaults to true), For local, non-public development stacks, set to `false`. This will disable requests to Let's Encrypt for certificates and use self signed certificates instead. 53 | - `LE_TEST` (optional), LE is rate limited. While testing your stack, be sure to set testing mode so requests don't count against your domain quota. Such certificates will not be valid, but are sufficient to test your setup. 54 | - See [https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769](https://community.letsencrypt.org/t/rate-limits-for-lets-encrypt/6769) for more information. 55 | - `TLS_SETTING` (optional), one of `MODERN`, `INTERMEDIATE`, OR `OLD`. All other values will be igored. `MODERN` is default to allow for the best security setting. 56 | - See [https://wiki.mozilla.org/Security/Server_Side_TLS](https://wiki.mozilla.org/Security/Server_Side_TLS) for more details 57 | - See [docker-entrypoint.sh](https://github.com/Annixa/docker-nginx-letsencrypt-proxy/blob/master/docker-entrypoint.sh) for the suites used 58 | - Updated August 6, 2017 59 | - This setting will correspond to the following browser compatibilities: 60 | - `LOGROTATE_SIZE` (optional, defaults to 10k), the size limit of the log files 61 | - `LOGROTATE_FILE_LIMIT` (optional, defaults to 7), the number of log files to keep 62 | - `CRON_SCHEDULE` (optional, defaults to 0 * * * *), the cron schedule for logrotate 63 | 64 | | Configuration | Oldest compatible client | 65 | | ------------- |:------------------------| 66 | | `MODERN` | Firefox 27, Chrome 30, IE 11 on Windows 7, Edge, Opera 17, Safari 9, Android 5.0, Java 8 | 67 | | `INTERMEDIATE` | Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7 | 68 | | `OLD` | Windows XP IE6, Java 6 | 69 | 70 | How It Works 71 | ------------ 72 | 73 | When certificates are updated, the event handler will: 74 | 75 | 1. Move the resulting certificates to `/etc/nginx/ssl` 76 | 1. Tell `supervisor` to restart nginx: `supervisorctl restart nginx` 77 | 1. If `SLACK_NOTIFICATIONS_INFRA_URL` is set, send a notification to your slack channel. 78 | 79 | The premise is simple: 80 | 81 | - The image is configured to request a Let's Encrypt certificate for each of the (comma separated) domains listed in the `LE_DOMAIN` env variable provided in `docker-compose.yml` 82 | - Since Let's Encrypt is rate limited, an env variable of `LE_TEST=true` can be provided during testing (in `docker-compose.yml`). 83 | - `supervisor` handles the running of nginx and the letsencrypt event handler, which is run every hour. 84 | - If the hourly Let's Encrypt script yields an updated certificate, files are copied and `nginx` is restarted using the supervisor control call. 85 | - Provide a `SLACK_NOTIFICATIONS_INFRA_URL` in the `docker-compose.yml` to get a Slack notification of a certificate update! 86 | -------------------------------------------------------------------------------- /config/logrotate.d/nginx: -------------------------------------------------------------------------------- 1 | /var/log/nginx/localhost.*_log { 2 | rotate $LOGROTATE_FILE_LIMIT 3 | size $LOGROTATE_SIZE 4 | missingok 5 | compress 6 | notifempty 7 | sharedscripts 8 | postrotate 9 | supervisorctl restart nginx 10 | endscript 11 | } 12 | -------------------------------------------------------------------------------- /config/nginx/webapp.1.conf: -------------------------------------------------------------------------------- 1 | 2 | server { 3 | listen $PROXY_PORT ssl http2; 4 | listen [::]:$PROXY_PORT ssl http2; 5 | 6 | 7 | ssl_certificate /etc/nginx/ssl/nginx.crt; 8 | ssl_certificate_key /etc/nginx/ssl/nginx.key; 9 | 10 | ssl_prefer_server_ciphers on; 11 | ssl_session_timeout 1d; 12 | ssl_session_cache shared:SSL:50m; 13 | ssl_session_tickets off; 14 | # OCSP Stapling --- 15 | # fetch OCSP records from URL in ssl_certificate and cache them 16 | ssl_stapling on; 17 | ssl_stapling_verify on; 18 | # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits 19 | ssl_dhparam /etc/nginx/ssl/dhparam.pem; 20 | 21 | # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) 22 | add_header Strict-Transport-Security max-age=15768000; 23 | 24 | 25 | sendfile off; 26 | 27 | error_log /var/log/nginx/localhost.error_log info; 28 | access_log /var/log/nginx/localhost.access_log; 29 | 30 | client_max_body_size 5G; 31 | 32 | -------------------------------------------------------------------------------- /config/nginx/webapp.2.conf: -------------------------------------------------------------------------------- 1 | proxy_redirect off; 2 | proxy_set_header Host $host; 3 | proxy_set_header X-Real-IP $remote_addr; 4 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 5 | proxy_set_header X-Forwarded-Proto https; 6 | proxy_set_header X-Forwarded-Port $PROXY_PORT; 7 | 8 | # Websocket Support 9 | proxy_http_version 1.1; 10 | proxy_set_header Upgrade $http_upgrade; 11 | proxy_set_header Connection "upgrade"; 12 | proxy_read_timeout 9000s; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /config/nginx/wellknown.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | server_name $host; 5 | 6 | sendfile off; 7 | 8 | error_log /var/log/nginx/localhost.error_log info; 9 | access_log /var/log/nginx/localhost.access_log; 10 | 11 | client_max_body_size 128M; 12 | 13 | #Let's Encrypt Support 14 | location ^~ /.well-known/acme-challenge/ { 15 | root /var/www/challenges/; 16 | try_files $uri =404; 17 | } 18 | 19 | location = /.well-known/acme-challenge/ { 20 | return 404; 21 | } 22 | 23 | location / { 24 | return 301 https://$host$request_uri$PROXY_PORT_FWD; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/supervisor/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | loglevel=debug 4 | 5 | [program:nginx] 6 | command=/usr/sbin/nginx -g "daemon off;" 7 | 8 | [eventlistener:letsencrypt] 9 | command=/opt/letsencrypt-run.py 10 | events=TICK_3600 11 | -------------------------------------------------------------------------------- /docker-compose-example.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | docker-nginx-letsencrypt-proxy: 4 | image: annixa/docker-nginx-letsencrypt-proxy:build 5 | ports: 6 | - 80:80 7 | - 443:443 8 | container_name: docker-nginx-letsencrypt-proxy 9 | logging: 10 | options: 11 | max-size: 50k 12 | environment: 13 | - LE_ENABLED=false 14 | # - LE_TEST=true # LE is rate limited. While doing development, be sure to set testing mode so requests don't count against our quota. 15 | - LE_EMAIL=test@test.com 16 | - LE_DOMAIN=test.test.com 17 | - PROXY_DEST=http://docker-nginx-letsencrypt-proxy-www/ 18 | - LOGROTATE_SIZE=1k 19 | - LOGROTATE_FILE_LIMIT=4 20 | # - SLACK_NOTIFICATIONS_INFRA_URL=https://hooks.slack.com/services/???????? # Be sure to fill this in using your URL for the slack webhook integration 21 | volumes: 22 | - "/etc/letsencrypt" 23 | links: 24 | - docker-nginx-letsencrypt-proxy-www 25 | # Be sure to link your container! 26 | 27 | docker-nginx-letsencrypt-proxy-www: 28 | image: nginx 29 | container_name: docker-nginx-letsencrypt-proxy-www 30 | command: bash -c 'echo "It worksDocker Nginx Lets Encrypt Proxy is working." > /usr/share/nginx/html/index.html && nginx -g "daemon off;"' 31 | -------------------------------------------------------------------------------- /docker-compose-production.yml: -------------------------------------------------------------------------------- 1 | docker-nginx-letsencrypt-proxy: 2 | image: annixa/docker-nginx-letsencrypt-proxy 3 | ports: 4 | - 80:80 5 | - 443:443 6 | container_name: docker-nginx-letsencrypt-proxy 7 | log_opt: 8 | max-size: 50k 9 | environment: 10 | # - LE_ENABLED=true 11 | # - LE_TEST=true # LE is rate limited. While doing development, be sure to set testing mode so requests don't count against our quota. 12 | - LE_EMAIL=test@test.com 13 | - LE_DOMAIN=domain.com 14 | - PROXY_DEST=https://www.google.com 15 | # - SLACK_NOTIFICATIONS_INFRA_URL=https://hooks.slack.com/services/???????? # Be sure to fill this in using your URL for the slack webhook integration 16 | volumes: 17 | - "/etc/letsencrypt" 18 | # links: 19 | # - mycontainer 20 | # Be sure to link your container! -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | docker-nginx-letsencrypt-proxy: 4 | build: . 5 | image: annixa/docker-nginx-letsencrypt-proxy:build 6 | ports: 7 | - 80:80 8 | - 443:443 9 | container_name: docker-nginx-letsencrypt-proxy 10 | logging: 11 | options: 12 | max-size: 50k 13 | environment: 14 | - LE_ENABLED=false 15 | # - LE_TEST=true # LE is rate limited. While doing development, be sure to set testing mode so requests don't count against our quota. 16 | - LE_EMAIL=test@test.com 17 | - LE_DOMAIN=domain.com 18 | - PROXY_DEST=https://www.google.com 19 | # - PROXY_PORT=8443 20 | #- SLACK_NOTIFICATIONS_INFRA_URL=https://hooks.slack.com/services/???????? # Be sure to fill this in using your URL for the slack webhook integration 21 | volumes: 22 | - "/etc/letsencrypt" 23 | # links: 24 | # - mycontainer 25 | # Be sure to link your container! 26 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Updated on April 17, 2016 6 | # TLS Protocols and cipher suites recommended by Mozilla 7 | # https://wiki.mozilla.org/Security/Server_Side_TLS 8 | # Be sure to escape !'s 9 | declare -A TLS_SETTING_PROTOS; 10 | TLS_SETTING_PROTOS["MODERN"]="TLSv1.2" 11 | TLS_SETTING_PROTOS["INTERMEDIATE"]="TLSv1 TLSv1.1 TLSv1.2" 12 | TLS_SETTING_PROTOS["OLD"]="SSLv3 TLSv1 TLSv1.1 TLSv1.2" 13 | 14 | declare -A TLS_SETTING_CIPHER; 15 | TLS_SETTING_CIPHER["MODERN"]="ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256" 16 | TLS_SETTING_CIPHER["INTERMEDIATE"]="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:\!DSS" 17 | TLS_SETTING_CIPHER["OLD"]="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:\!aNULL:\!eNULL:\!EXPORT:\!DES:\!RC4:\!MD5:\!PSK:\!RSAPSK:\!aDH:\!aECDH:\!EDH-DSS-DES-CBC3-SHA:\!KRB5-DES-CBC3-SHA:\!SRP" 18 | 19 | 20 | if [ -n "$PROXY_PORT" ]; then 21 | export PROXY_PORT_FWD=":$PROXY_PORT" 22 | fi 23 | 24 | export PROXY_PORT="${PROXY_PORT:-443}" 25 | 26 | # Substitute environment variables for logrotate nginx config 27 | export LOGROTATE_SIZE=${LOGROTATE_SIZE:-10k} 28 | export LOGROTATE_FILE_LIMIT=${LOGROTATE_FILE_LIMIT:-7} 29 | envsubst < /etc/logrotate.d/nginx.tmpl > /etc/logrotate.d/nginx 30 | 31 | # set up cron for logrotate 32 | export CRON_SCHEDULE=${CRON_SCHEDULE:-'0 * * * *'} 33 | echo "$CRON_SCHEDULE /usr/sbin/logrotate /etc/logrotate.d/nginx" | crontab - 34 | 35 | # Check to see if let's encrypt has certificates already issued to a volume. 36 | # If so, immediately overwrite fake certificates. 37 | if [ -z "$LE_DOMAIN" ] && [ -z "$LE_EMAIL" ] && [ -z "$PROXY_DEST" ]; then 38 | echo "DOCKER NGINX LET'S ENCRYPT: The env variables LE_DOMAIN, LE_EMAIL, and PROXY_DEST are required for setup."; 39 | exit 1; 40 | else 41 | # Generate a local fake cert so nginx will start regardless of the success of LE, if it's configured to run. 42 | echo "DOCKER NGINX LET'S ENCRYPT: Generate temporary certificate"; 43 | # Parse domains and Proxy destinations 44 | IFS=',' read -ra DOMAINS <<< "$LE_DOMAIN" 45 | IFS=',' read -ra DESTINATIONS <<< "$PROXY_DEST" 46 | 47 | openssl req -passout pass: -subj "/C=US/ST=CA/L=San Diego/O=$DOMAINS/OU=TS/CN=$DOMAINS/emailAddress=support@$DOMAINS" -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt 48 | 49 | # https://github.com/Annixa/docker-nginx-letsencrypt-proxy/issues/4 50 | # Reintroduce LE_ENABLED mode 51 | if [ "$LE_ENABLED" = false ]; then 52 | echo "DOCKER NGINX LET'S ENCRYPT: Let's Encrypt is disabled according to the environment variable LE_ENABLED. Using self-signed certificates instead."; 53 | else 54 | echo "DOCKER NGINX LET'S ENCRYPT: Checking for previous certificate existence"; 55 | LE_CERT="/etc/letsencrypt/live/$DOMAINS/fullchain.pem"; 56 | LE_KEY="/etc/letsencrypt/live/$DOMAINS/privkey.pem"; 57 | if [ -e $LE_CERT ] && [ -e $LE_KEY ]; then 58 | cp /etc/letsencrypt/live/$DOMAINS/fullchain.pem /etc/nginx/ssl/nginx.crt; 59 | cp /etc/letsencrypt/live/$DOMAINS/privkey.pem /etc/nginx/ssl/nginx.key; 60 | echo "DOCKER NGINX LET'S ENCRYPT: Previous keys found. Moved to nginx ssl directory"; 61 | else 62 | echo "DOCKER NGINX LET'S ENCRYPT: No certificates found."; 63 | fi 64 | fi 65 | 66 | 67 | # Determine TLS_SETTING 68 | # Check to see if env is set and it's one of MODERN, INTERMEDIATE, or OLD. 69 | # If check fails, set to MODERN 70 | if [ ! -z "$TLS_SETTING" ] && ( [ $TLS_SETTING="MODERN" ] || [ $TLS_SETTING="INTERMEDIATE" ] || [ $TLS_SETTING="OLD" ]} ] ); then 71 | echo "DOCKER NGINX LET'S ENCRYPT: TLS_SETTING set to $TLS_SETTING"; 72 | else 73 | 74 | echo "DOCKER NGINX LET'S ENCRYPT: TLS_SETTING not set. Using MODERN"; 75 | TLS_SETTING="MODERN"; 76 | fi 77 | 78 | #GENERATE DHPARAM 79 | echo "DOCKER NGINX LET'S ENCRYPT: Generating DH parameters"; 80 | 81 | # https://github.com/Annixa/docker-nginx-letsencrypt-proxy/issues/3 82 | # Cache generated dhparams.pem 83 | TLS_DHPARAMS="/etc/letsencrypt/dhparam.pem" 84 | if [ -e $TLS_DHPARAMS ]; then 85 | echo "DOCKER NGINX LET'S ENCRYPT: DHPARAMS already exist"; 86 | else 87 | openssl dhparam -out "$TLS_DHPARAMS" 2048 88 | fi 89 | cp -f $TLS_DHPARAMS /etc/nginx/ssl/dhparam.pem 90 | 91 | echo "DOCKER NGINX LET'S ENCRYPT: render nginx configuration with proxy and destination details details"; 92 | # echo "" > /etc/nginx/sites-enabled/webapp.conf 93 | 94 | # Updating to support changes in LE 95 | envsubst '$PROXY_PORT_FWD' < /etc/nginx/sites-available/wellknown.conf > /etc/nginx/sites-enabled/webapp.conf 96 | 97 | CT=0 98 | for i in "${DOMAINS[@]}"; do 99 | # By default, grab the first PROXY_DEST in the array 100 | THIS_DEST="$DESTINATIONS" 101 | if [ $CT -lt ${#DESTINATIONS[@]} ]; then 102 | # Get right VALUE 103 | THIS_DEST="${DESTINATIONS[$CT]}" 104 | fi 105 | envsubst '$PROXY_PORT' < /etc/nginx/sites-available/webapp.1.conf >> /etc/nginx/sites-enabled/webapp.conf 106 | 107 | echo "ssl_protocols ${TLS_SETTING_PROTOS["$TLS_SETTING"]};" >> /etc/nginx/sites-enabled/webapp.conf 108 | echo "ssl_ciphers '${TLS_SETTING_CIPHER["$TLS_SETTING"]}';" >> /etc/nginx/sites-enabled/webapp.conf 109 | 110 | echo "server_name $i;" >> /etc/nginx/sites-enabled/webapp.conf 111 | echo "location / {" >> /etc/nginx/sites-enabled/webapp.conf 112 | 113 | 114 | echo " proxy_pass $THIS_DEST;" >> /etc/nginx/sites-enabled/webapp.conf 115 | envsubst '$PROXY_PORT' < /etc/nginx/sites-available/webapp.2.conf >> /etc/nginx/sites-enabled/webapp.conf 116 | 117 | CT=$(($CT + 1)) 118 | done 119 | 120 | 121 | fi 122 | 123 | # start cron 124 | /etc/init.d/cron start 125 | 126 | # start up supervisor 127 | /usr/bin/supervisord 128 | -------------------------------------------------------------------------------- /letsencrypt-run.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | 3 | # Event Listener Process for supervisor 4 | # Invoked a script designed to periodically request updates 5 | import sys 6 | import subprocess 7 | 8 | def write_stdout(s): 9 | sys.stdout.write(s) 10 | sys.stdout.flush() 11 | 12 | def write_stderr(s): 13 | sys.stderr.write(s) 14 | sys.stderr.flush() 15 | 16 | def main(): 17 | while True: 18 | write_stdout('READY\n') # transition from ACKNOWLEDGED to READY 19 | line = sys.stdin.readline() # read header line from stdin 20 | write_stderr(line) # print it out to stderr 21 | headers = dict([ x.split(':') for x in line.split() ]) 22 | data = sys.stdin.read(int(headers['len'])) # read the event payload 23 | res = subprocess.call("/opt/letsencrypt-run.sh", stdout=sys.stderr) # don't mess with real stdout 24 | write_stderr(data) 25 | write_stdout('RESULT 2\nOK') # transition from READY to ACKNOWLEDGED 26 | 27 | if __name__ == '__main__': 28 | # Do this first 29 | subprocess.call("/opt/letsencrypt-run.sh", stdout=sys.stderr) 30 | main() 31 | import sys -------------------------------------------------------------------------------- /letsencrypt-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TERM="xterm" 4 | export TERM 5 | 6 | # Only run if this variable is set; local stacks should generate a fake certificate. 7 | # https://github.com/Annixa/docker-nginx-letsencrypt-proxy/issues/4 8 | # Reintroduce LE_ENABLED mode 9 | if [ "$LE_ENABLED" = false ]; then 10 | echo "DOCKER NGINX LET'S ENCRYPT: Let's Encrypt is disabled according to the environment variable LE_ENABLED. Using self-signed certificates instead."; 11 | exit 0; 12 | fi 13 | 14 | cd /opt/letsencrypt 15 | # https://github.com/Annixa/docker-nginx-letsencrypt-proxy/issues/6 16 | # Add --expand flag to LE: "When adding domains to the LE_DOMAIN env variable, you may be prompted to expand and replace the existing certificate with a new one." 17 | LE="./letsencrypt-auto --config /opt/letsencrypt.ini certonly -n --keep-until-expiring --expand --agree-tos --email $LE_EMAIL " 18 | 19 | if [ "$LE_TEST" = true ]; then 20 | echo "LET'S ENCRYPT: TESTING MODE"; 21 | LE="$LE --staging " 22 | else 23 | echo "LET'S ENCRYPT: PRODUCTION MODE"; 24 | fi 25 | 26 | # Parse domains 27 | IFS=',' read -ra DOMAINS <<< "$LE_DOMAIN" 28 | for i in "${DOMAINS[@]}"; do 29 | # process "$i" 30 | LE="$LE -d $i " 31 | done 32 | 33 | LE_OUTPUT=$($LE); 34 | LE_EXIT=$?; 35 | 36 | if [ $LE_EXIT -eq 0 ]; then 37 | 38 | # Determine if the files have changed 39 | 40 | ORIG_KEY=`md5sum /etc/nginx/ssl/nginx.key | awk '{print $1 }'` 41 | ORIG_CERT=`md5sum /etc/nginx/ssl/nginx.crt | awk '{print $1 }'` 42 | NEXT_KEY=`md5sum /etc/letsencrypt/live/$DOMAINS/privkey.pem | awk '{print $1 }'` 43 | NEXT_CERT=`md5sum /etc/letsencrypt/live/$DOMAINS/fullchain.pem | awk '{print $1 }'` 44 | 45 | echo $ORIG_KEY 46 | echo $ORIG_CERT 47 | echo $NEXT_KEY 48 | echo $NEXT_CERT 49 | 50 | if [ "$ORIG_KEY" != "$NEXT_KEY" ] || [ "$ORIG_CERT" != "$NEXT_CERT" ]; then 51 | echo "LET'S ENCRYPT: certificates have been updated!" 52 | cp /etc/letsencrypt/live/$DOMAINS/fullchain.pem /etc/nginx/ssl/nginx.crt 53 | cp /etc/letsencrypt/live/$DOMAINS/privkey.pem /etc/nginx/ssl/nginx.key 54 | # Restart nginx? 55 | supervisorctl restart nginx 56 | 57 | if [ -z "$SLACK_NOTIFICATIONS_INFRA_URL" ]; then 58 | echo "LET'S ENCRYPT: set the SLACK_NOTIFICATIONS_INFRA_URL env variable in docker-compose.yml to get automatic notifications." 59 | else 60 | SLACK_PAYLOAD="payload={\"username\": \"letsencrypt\", \"text\": \"A certificate has been updated on *$LE_DOMAIN*. NGINX has been restarted.\", \"icon_emoji\": \":closed_lock_with_key:\"}" 61 | curl -X POST --data-urlencode "$SLACK_PAYLOAD" $SLACK_NOTIFICATIONS_INFRA_URL 62 | fi 63 | else 64 | echo "LET'S ENCRYPT: certificates are unchanged." 65 | fi 66 | 67 | else #If there was an error with the request 68 | echo "LET'S ENCRYPT: There was an error with the request." 69 | echo "$LE_OUTPUT" 70 | if [ -z "$SLACK_NOTIFICATIONS_INFRA_URL" ]; then 71 | echo "LET'S ENCRYPT: set the SLACK_NOTIFICATIONS_INFRA_URL env variable in docker-compose.yml to get automatic notifications." 72 | else 73 | SLACK_PAYLOAD="payload={\"username\": \"letsencrypt\", \"text\": \"An error occured when trying to update the Let's Encrypt certificates.\", \"icon_emoji\": \":closed_lock_with_key:\", \"attachments\": [{ \"fallback\":\"Could not update certificates.\", \"color\":\"#FF0000\", \"pretext\": \"Error Message:\", \"text\": \"$LE_OUTPUT\"}] }" 74 | curl -X POST --data-urlencode "$SLACK_PAYLOAD" $SLACK_NOTIFICATIONS_INFRA_URL 75 | fi 76 | fi 77 | 78 | -------------------------------------------------------------------------------- /letsencrypt.ini: -------------------------------------------------------------------------------- 1 | # Let's Encrypt Configuration with defaults 2 | # All flags used by the client can be configured here. Run Let's Encrypt with 3 | # "--help" to learn more about the available options. 4 | 5 | # Use a 4096 bit RSA key instead of 2048 6 | rsa-key-size = 4096 7 | 8 | # Uncomment and update to register with the specified e-mail address 9 | # email = user@domain.com, set by LE_EMAIL in the env variables provided by Docker 10 | 11 | # Uncomment and update to generate certificates for the specified 12 | # domains. 13 | # domains = example.com, www.example.com 14 | # Always provide domains via env. See letsencrypt-run.sh 15 | 16 | # Uncomment to use a text interface instead of ncurses 17 | text = True 18 | 19 | # Uncomment to use the standalone authenticator on port 443 20 | # authenticator = standalone 21 | # standalone-supported-challenges = tls-sni-01 22 | 23 | # Uncomment to use the webroot authenticator. Replace webroot-path with the 24 | # path to the public_html / webroot folder being served by your web server. 25 | authenticator = webroot 26 | webroot-path = /var/www/challenges --------------------------------------------------------------------------------