Jon Combe logo

Add a Let's Encrypt SSL certificate to an Nginx Docker setup in a few seconds (no, really!)

April 20222 years, 5 months ago

Many people have tackled the problem of issuing Let's Encrypt SSL certificates for an Nginx webserver in a Docker environment. Many of these solutions are great but can take a fair amount of time to complete.

Instead of writing lengthy instructions, I created a script that automates most of the process for you. Your mother warned you against downloading scripts from strange men on the internet and so I have made all the source code and documentation available on GitHub.

Of all the guides and tutorials on the web, I'm going to be outrageously bold and say that mine is the quickest and easiest.

Here's what to do:

  • Ensure your server has docker and docker compose installed.
  • Ensure the DNS for your (sub)domain name is configured to point to your server.
  • Copy two tiny text files, certbot.json and certbot.py, from the repo to your server. You can copy the command below to do this automatically:
    TERMINAL
    curl -OO https://raw.githubusercontent.com/joncombe/docker-nginx-letsencrypt-setup/main/{(certbot.json, certbot.py)}
  • Edit the certbot.json file. You likely only need to change the domain and email fields, but do check the documentation. The email is passed to Let's Encrypt as part of the certificate creation process, I never see it.
  • Run the script:
    TERMINAL
    python3 certbot.py

That's great! Now what?

The script has fetched and installed an SSL certificate for you from Let's Encrypt. Go to your (sub)domain in your browser to see it working. Awesome!

If you now list the contents of your folder you will see you have one new folder and two new files. Your certificates are stored in the data folder.

TERMINAL
> ls
data   docker-compose.yml   nginx.conf

Post-install step #1: docker-compose.yml

Think of the docker-compose.yml as a starting template for your new project. This is your file to add to and edit as you please, but just be careful with the volumes mappings within the webserver and certbot services (shown here in blue). You can add other volumes of course, but be sure to keep these ones.

version: '3'

services:
  webserver:
    image: nginx:1.23.4-alpine
    ports:
      - 80:80
      - 443:443
    restart: always
    volumes:
      - ./data/certbot/conf/:/etc/nginx/ssl/:ro
      - ./data/certbot/www:/var/www/certbot/:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

    certbot:
      image: certbot/certbot:latest
      volumes:
        - ./data/certbot/conf/:/etc/letsencrypt/:rw
        - ./data/certbot/www/:/var/www/certbot/:rw

Post-install step #2: nginx.conf

Your nginx.conf file will look like the one below exceptexample.com will be replaced with your (sub)domain.

The first server block redirects HTTP traffic to HTTPS. You probably don't need to touch this.

The second server block is just a starting point: you will need to add your own rules here. Nginx configurations vary by use case and can get very complex, so it is not my place to dictate what is in your config file, however, be sure to keep the ssl_certificate and ssl_certificate_key lines exactly as they are (highlighted here in blue).

server {
  listen 80;
  listen [::]:80;

  server_name _;
  server_tokens off;

  location /.well-known/acme-challenge/ {
    root /var/www/certbot;
  }

  location / {
    return 301 https://example.com$request_uri;
  }
}

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

  server_name _;
  server_tokens off;

  ssl_certificate /etc/nginx/ssl/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/live/example.com/privkey.pem;

  location / {
    if ( $host !~* ^(example.com)$ ) {
      return 444;
    }

    root /usr/share/nginx/html;
  }
}

Post-install step #3: auto-renew your certificates

You need to add two new cronjobs on the server. The first line instructs the Certbot docker container to request a new certificate before the old one expires. The second line reloads nginx (with zero downtime) so that it discovers the new certificate.

Important: which cronjobs you use depends on which version of docker compose you have installed.

If you use the docker compose command - without a hyphen - add these lines to your crontab:

0 0,12 * * *   docker compose run --rm certbot renew
5 0 * * *      docker exec webserver nginx -s reload

If you use the legacy docker-compose command - with a hyphen - add these ones instead:

0 0,12 * * *   /usr/local/bin/docker-compose run --rm certbot renew
5 0 * * *      docker exec webserver nginx -s reload

Is that it?

Yup, that's it. If you have any problems please double check the instructions here and in the GitHub repo. If that doesn't fix it, please raise an issue or drop me a line. If this saved you a chunk of time and hair-pulling, I'd like to think this repo is worthy of a GitHub star.

This method has been used and tested on Debian/Ubuntu-based servers. Feedback from users of other Linux distros would be gratefully received.

Happy secure browsing!