Skip to main content

NGINX Docker with Certbot

Victoria Harbour, Hong Kong

This is a continuation of the last 2 tutorials to set up an NGINX web proxy in Docker. This time I am going to replace the self-signed TLS certificate with a "real" certificate from Let's Encrypt using Certbot. Though I had some issue with the directory structure and had to move a few things around to make this work. The final structure now looks like this:

/opt/docker-ingress
├── configuration
│   ├── conf.d
│   │   ├── buffers.conf
│   │   ├── cache.conf
│   │   ├── default.bak
│   │   ├── default.conf
│   │   ├── gzip.conf
│   │   ├── header.conf
│   │   ├── self-signed.conf
│   │   ├── ssl-params.conf
│   │   ├── test-instar-wiki.conf
│   │   └── timeouts.conf
│   ├── fastcgi.conf
│   ├── fastcgi_params
│   ├── koi-utf
│   ├── koi-win
│   ├── mime.types
│   ├── nginx.conf
│   ├── scgi_params
│   ├── ssl
│   │   ├── dhparam
│   │   │   └── dhparam.pem
│   │   ├── selfsigned
│   │   │   ├── nginx-selfsigned.crt
│   │   │   └── nginx-selfsigned.key
│   │   └── test-instar-wiki
│   ├── uwsgi_params
│   └── win-utf
└── docker-compose.yml

6 directories, 23 files

Install snapd

On Debian 9 (Stretch) and Debian 10 (Buster), snap can be installed directly from the command line:

apt update
apt install snapd

After this, install the core snap in order to get the latest snapd:

snap install core
  core 16-2.47 from Canonical✓ installed

If you already have everything installed update it with snap refresh core.

Certbot SnapApp

Remove any Certbot OS packages

If you have any Certbot packages installed using an OS package manager, you should remove them before installing the Certbot snap to ensure that when you run the command certbot the snap is used rather than the installation from your OS package manager:

apt-get remove certbot

Install Certbot

Run this command on the command line on the machine to install Certbot.

snap install --classic certbot

Warning: /snap/bin was not found in your $PATH. If you've not restarted your session since you installed snapd, try doing that. Please see https://forum.snapcraft.io/t/9469 for more details.

Execute the following instruction on the command line on the machine to ensure that the certbot command can be run.

ln -s /snap/bin/certbot /usr/bin/certbot

Run Certbot

Standalone or Webroot

My web server is not currently running on this machine.

Stop your webserver, then run this command to get a certificate. Certbot will temporarily spin up a webserver on your machine.

certbot certonly --standalone

No, I need to keep my web server running.

If you have a webserver that's already using port 80 and don't want to stop it while Certbot runs, run this command and follow the instructions in the terminal.

certbot certonly --webroot

Important Note: To use the webroot plugin, your server must be configured to serve files from hidden directories. If /.well-known is treated specially by your webserver configuration, you might need to modify the configuration to ensure that files inside /.well-known/acme-challenge are served by the webserver.

Install your Certificate

certbot certonly --standalone

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None

...

Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel): test.instar.com
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for test.instar.com
Waiting for verification...
Cleaning up challenges

Your certificate and chain have been saved at:

/etc/letsencrypt/live/test.instar.com/fullchain.pem
/etc/letsencrypt/live/test.instar.com/privkey.pem

To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew all of your certificates, run:

certbot renew

Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal.

Test Automatic Renewal

The Certbot packages on your system come with a cron job or systemd timer that will renew your certificates automatically before they expire. You will not need to run Certbot again, unless you change your configuration. You can test automatic renewal for your certificates by running this command:

sudo certbot renew --dry-run
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/test.instar.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

The command to renew certbot is installed in one of the following locations:

/etc/crontab/
/etc/cron.*/*
systemctl list-timers

If you needed to stop your webserver to run Certbot, you'll want to add hook scripts to stop and start your webserver automatically:

sudo sh -c 'printf "#!/bin/sh\ndocker stop ingress\n" > /etc/letsencrypt/renewal-hooks/pre/docker-ingress.sh'
sudo sh -c 'printf "#!/bin/sh\ndocker start ingress\n" > /etc/letsencrypt/renewal-hooks/post/docker-ingress.sh'
sudo chmod 755 /etc/letsencrypt/renewal-hooks/pre/docker-ingress.sh
sudo chmod 755 /etc/letsencrypt/renewal-hooks/post/docker-ingress.sh

Configuring the NGINX Container

Docker Compose

/opt/docker-ingress/docker-compose.yml

version: "3.8"
services:

    ingress:
        image: nginx:stable-alpine
        container_name: ingress
        networks:
        - gateway
        ports:
        - "80:80"
        - "443:443"
        restart: unless-stopped
        volumes:
        - /opt/docker-ingress/configuration/conf.d:/etc/nginx/conf.d
        - /opt/docker-ingress/configuration/ssl/dhparam:/etc/nginx/ssl/dhparam
        - /opt/docker-ingress/configuration/ssl/selfsigned:/etc/nginx/ssl/selfsigned
        # - /etc/letsencrypt/live/test.instar.com:/etc/nginx/ssl/test.instar.com
        - /etc/letsencrypt/archive/test.instar.com:/etc/nginx/ssl/test.instar.com
        - /opt/docker-ingress/configuration/nginx.conf:/etc/nginx/nginx.conf
        - /opt/test-static/public:/opt/test-static/public

networks:
  gateway: {}

Virtual Server Configuration

/opt/docker-ingress/configuration/conf.d/default.conf

server {
    listen      443 ssl;
    listen      [::]:443 ssl;

    include     conf.d/test-instar-wiki.conf;
    include     conf.d/ssl-params.conf;

    server_name test.instar.com;

    location / {
        rewrite   ^/(.*)$  https://wiki.instar.com/Quick_Installation/ONVIF/$1  permanent;
    }
    
    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

SSL Configuration

/opt/docker-ingress/configuration/conf.d/test-instar-com.conf

ssl_certificate /etc/nginx/ssl/test.instar.com/fullchain1.pem;
ssl_certificate_key /etc/nginx/ssl/test.instar.com/privkey1.pem;

/opt/docker-ingress/configuration/conf.d/ssl-params.conf

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/ssl/dhparam/dhparam.pem;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout  10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Disable strict transport security for now. You can uncomment the following
# line if you understand the implications.
# add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";