Akercode.com

Let's Encrypt HTTPS Certificate With Nginx and Docker

Image of a website without HTTPS

This How-To will describe how to set up a secure connection over HTTPS with Let's Encrypt on a Nginx webserver that is running as a Docker container.

We will use Let's Encrypt's installer certbot to fetch the certificate.

Then we will mount the certificate to the docker container running Nginx.

That's it! No setting up of extra containers or complicated scripting.

Prerequisites

Install Certbot and Get the HTTPS Certificate

Ensure that your version of snapd is up to date:

sudo snap install core; sudo snap refresh core

Install Certbot:

sudo snap install --classic certbot

Prepare the Certbot command:

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

If you are runing your application on port 80 you will get this message when you are running the certbot command below, since it uses port 80 to spin up a temporary webserver for fetching the certificate:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Could not bind TCP port 80 because it is already in use by another process on
this system (such as a web server). Please stop the program in question and then
try again.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(R)etry/(C)ancel: 

If so, take down your docker services:

docker-compose down

And run the Certbot command:

sudo certbot certonly --standalone

Output:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Please enter the domain name(s) you would like on your certificate (comma and/or
space separated) (Enter 'c' to cancel): akercode.com
Requesting a certificate for akercode.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/akercode.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/akercode.com/privkey.pem
This certificate expires on 2021-10-17.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

If you see the message above that means you successfully received certificate,

and the certificate should now be stored in path /etc/letsencrypt/live/akercode.com/ (with your own domain instead of akercode.com).

Configure Nginx

This is the file structure for reference. nginx folder in the same directory as Dockerfile and docker-compose.yml.

Directory nginx have two sub directories, conf.d which contains a nginx configuration file named app.conf.

And a ssl directory that will contain the certificate and key after we have fetched it and copied them into the directory.

Image of the application file structure

Create the directory where we will store the certificate (-p flag enables the command to create parent directories as necessary):

mkdir -p nginx/ssl

Open docker-compose.yml:

nano docker-compose.yml

Mount port 443 from the outside to port 443 in the nginx docker container (443:443), and if not set from before, also port 80 (80:80).

Also mount the newly created ssl directory, - ./nginx/ssl/:/etc/nginx/ssl/ so the HTTPS certificate gets loaded by Nginx.

...
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./:/var/www
      - ./nginx/conf.d/:/etc/nginx/conf.d/
      - ./nginx/ssl/:/etc/nginx/ssl/
...

Open the nginx configuration file:

nano nginx/conf.d/app.conf

And paste in the code below, remember to replace akercode.com with you own domain.

Note: Some applications (Laravel & CodeIgniter) has the entrypoint index.php in the public directory under root. root should in that case be /var/www/public in stead of /var/www.

server {
    listen 80;
    server_name akercode.com www.akercode.com;

    return 301 https://akercode.com$request_uri;
}

server {
    listen 443 ssl http2;
    server_name akercode.com www.akercode.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    root /var/www;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }

    # Nginx Pass requests to PHP-FPM
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

Copy your ssl certificate and key to the nginx/ssl directory, and remember to replace with your own domain:

sudo cp /etc/letsencrypt/live/akercode.com/fullchain.pem nginx/ssl/
sudo cp /etc/letsencrypt/live/akercode.com/privkey.pem nginx/ssl/

Set permissions on the ssl directory with its newly copied certificate so that it is owned by your non-root user.

sudo chown -R $USER:$USER ./nginx/ssl

Then we can re-build the nginx service for the ssl certificate to get loaded.

docker-compose up -d --build

When you refresh you should now see a little lock to the side of your domain in your browser, and when clicked on, a message that verifies that the connection is now secure.

Image of a HTTPS secured website