Setup SSL with Docker, NGINX and Lets Encrypt

20 Comments
Modified: 15.07.2023

Did you ever want to secure your application with the HTTPS protocol? This guide will show you how to run your applications behind a reverse proxy and secure the communication with HTTPS by using Docker, NGINX, and Lets Encrypt.

Don’t want to read? Watch the video instead!

Used Technologies

With these three technologies, you can create a secure environment to publish your applications to the web. NGINX will be the entry point for users from the web to access the different applications. The SSL certificates are needed to use HTTPS as a communication protocol between your server and the clients.
Docker itself will host NGINX, your applications, and a service to generate new Lets Encrypt certificates automatically.

Server icon

VPS Hosting Course

Learn everything you need to know about servers and hosting your own applications!

There is a updated version of this guide in wich I will teach you how to setup an simpler and automated process. You can find it here.

To follow this guide, you need a domain, and you need to install docker and docker-compose for your system!

Steps

You can receive SSL certificates for any application you want with the following steps.

  1. Create your application with Docker
  2. Create a reverse proxy with NGINX
  3. Automate SSL certificates with Certbot

Create your application with Docker

The first step is to use docker compose to create a container for your application. I will use the simple helloworld image found here.

Need help or want to share feedback? Join my discord community!

services:
    helloworld:
        container_name: helloworld
        image: crccheck/hello-world
        ports:
          - 80:8000

The tasks of the different attributes are:

Now you can create the container by running:

KOFI Logo

If this guide is helpful to you and you like what I do, please support me with a coffee!

docker compose up -d helloworld

After this, you can reach your application on the host machine by entering the IP of your server. Don’t make your port available if you run this on a machine on the Internet because it is unsecured. To make this secure will create a reverse proxy with NGINX and put it in front of our applications.

[convertkit form=2649016]

Create a reverse proxy with NGINX

First, we will create a configuration to pass the requests to our node application via NGINX. Therefore we will make the directory nginx containing the file nginx.conf. With this file, we configure the NGINX instance.

events {
    worker_connections  1024;
}

http {
    server_tokens off;
    charset utf-8;

    server {
        listen 80 default_server;

        server_name _;

        location / {
            proxy_pass http://helloworld:8000/;
        }
    }
}

The event section is needed to run NGINX. The http section is the interesting part for us. First, the server is defined to listen to all requests on port 80 and is set as a default_server for all requests to this host. After that, we say each server name is valid for requests. In a later part, you can define different domains that are used for a server.
In the location / we define that every request should be passed to our helloworld container with port 8000. It is important to say that the specified port is not the public but the private port. Therefore we will remove the port mapping in the docker-compose.yml file.
Additionally, we will create an NGINX container in the same file.

services:
    helloworld:
        container_name: helloworld
        image: crccheck/hello-world
        expose:
            - 8000

    nginx:
        container_name: nginx
        restart: unless-stopped
        image: nginx
        ports:
            - 80:80
            - 443:443
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf

We will make ports 80 and 443 public because these are used for HTTP and HTTPS.
The volume is used to make the configuration available inside the container and to persist any changes.

You can create and update the container by running:

docker compose up -d

After this, you should be able to open the application by entering the URL to the host. Now we have setup a reverse proxy for our application. The next step will be to get SSL certificates and redirect all HTTP requests to HTTPS.

Automate SSL certificates with Certbot

Let’s Encrypt works with challenges to check if the domain and the host are eligible. For the challenges, we have to create a route called /.well-known/acme-challenge/ in the NGINX configuration. This will be part of the HTTP section, but we will move it there after switching to HTTPS, so the automated renewal works as expected.

    server {
        listen 80 default_server;

        .
        .
        .

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

As you can see, it points to the directory /var/www/certbot. Therefore, we need to add two volumes to make the challenges and the resulting certificates available in the NGINX container.

- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot

With this done, we have to recreate the container:

docker compose up -d nginx

After configuring NGINX, we can retrieve the certificates. To do this, we will use the certbot container and run it with a set of parameters.

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes: 
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    command: certonly --webroot -w /var/www/certbot --force-renewal --email {email} -d {domain} --agree-tos

The parameters are used to automate the process without you needing to manually enter the data on every container run.

Before requesting the certificate, we need to create an A record at our hosting provider pointing to our server. I am using Hostinger as my domain provider (find a domain here – affiliate link):

  1. Manage Domain
  2. DNS / Nameserver
  3. Manage DNS Records > Create A record:
    • Type = A
    • Name = @ – (This means it is for the main domain)
    • Points to = IP of your server
    • TTL = 14400

Afterward, we can request the certificates by creating the container with:

docker compose up -d certbot

By running the command docker logs certbot you can see if everything worked out and if you received your certificate. After you receive it, you have to include the certificate in nginx.conf.

Configure HTTPS in NGINX

In the first step, we redirect all HTTP requests to HTTPS, and in the second step, we create the HTTPS section for our application:

http {
    server_tokens off;
    charset utf-8;

    # always redirect to https
    server {
        listen 80 default_server;

        server_name _;

        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl http2;
        # use the certificates
        ssl_certificate     /etc/letsencrypt/live/{domain}/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;
        server_name {domain};
        root /var/www/html;
        index index.php index.html index.htm;


        location / {
            proxy_pass http://helloworld:8000/;
        }

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

Another thing you can see is that we moved the challenge route to the HTTPS section. We need to do this because all new requests of Lets Encrypt will also be redirected to HTTPS.

Automated renewal with crontab

The last step is to automatically renew the certificates before they run out. A certificate has a lifetime of 90 days, and it is recommended to update them after a timespan of 60 days. Therefore, we need to rerun our certbot container every 60 days to renew the certificates. I will accomplish this by using crontab.
A crontab can be created on linux systems by running:

crontab -e

And adding a line with the following structure:

0 5 1 */2 *  /usr/bin/docker compose -f /var/docker/docker-compose.yml up certbot

The command means: Run docker-compose up -d at 5 am on the first day every 2nd month.

After implementing this guide, you should have an application behind a reverse proxy accessible via the HTTPS protocol!
Thank you for reading my guide. If you have any questions feel free to shoot me an email at mail@programonaut.com, and I will try to help you!

In case you liked this post consider subscribing to my newsletter. You will also receive a free docker-compose cheat sheet!

[convertkit form=2649016]

Discussion (20)