How to Get Free SSL Certificates with Docker & LetsEncrypt

Acel - Jul 23 - - Dev Community

"An SSL certificate is a digital certificate that authenticates a website's identity and enables an encrypted connection. SSL stands for Secure Sockets Layer, a security protocol that creates an encrypted link between a web server and a web browser." - kaspersky

Majority of websites built today use SSL certificates, and if you are building a website in 2024, you will need to learn how to get one too!

There are several approaches to getting an SSL certificate for your domain. But in this article, we will take a look at generating SSL certificates with Let's Encrypt - a nonprofit certificate authority (CA) that provides free SSL/TLS certificates for enabling HTTPS (secure HTTP) on websites. In addition to this, automating the renewal process with Certbot and using a Docker image that was built on top of the official Nginx Docker images.

Prerequisites

To successfully complete this guide, you should be familiar with the following:

  • Docker and Docker Compose
  • Virtual machines from cloud providers, e.g. Azure VMs, AWS EC2 etc.
  • Linux

It is a good idea to containerize your app with Docker, but if you don't want to, you can still follow along just fine!

Configuring Certbot and Nginx

In the root directory of your project, please create a docker-compose file.

touch docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Then paste the following code in the docker-compose.yml file.

version: '3'

services:
  nginx:
    image: jonasal/nginx-certbot:latest
    restart: unless-stopped
    environment:
      - CERTBOT_EMAIL
    env_file:
      - ./nginx-certbot.env
    ports:
      - 80:80
      - 443:443
    volumes:
      - nginx_secrets:/etc/letsencrypt
      - ./user_conf.d:/etc/nginx/user_conf.d

volumes:
  nginx_secrets:
Enter fullscreen mode Exit fullscreen mode

The code we just pasted above is a YAML configuration for our Nginx docker image. It makes use of the jonasal/nginx-certbot:latest image that has certbot built on top of Nginx.

Now, let us create a nginx-certbot.env file to store the variables needed for our Nginx image.

touch nginx-certbot.env
Enter fullscreen mode Exit fullscreen mode

Then paste this into your nginx-certbot.env file

# Required
CERTBOT_EMAIL=your@email.com

# Optional (Defaults)
DHPARAM_SIZE=2048
RSA_KEY_SIZE=2048
ELLIPTIC_CURVE=secp256r1
RENEWAL_INTERVAL=8d
USE_ECDSA=1
STAGING=1
Enter fullscreen mode Exit fullscreen mode

Next, we create a server configuration file to configure our Nginx server.

touch server.conf
Enter fullscreen mode Exit fullscreen mode
server {
    # Listen to port 443 on both IPv4 and IPv6.
    listen 443 ssl default_server reuseport;
    listen [::]:443 ssl default_server reuseport;

    # Domain names this server should respond to.
    server_name yourdomain.com www.yourdomain.com;

    # Load the certificate files.
    ssl_certificate         /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;

    # Load the Diffie-Hellman parameter.
    ssl_dhparam /etc/letsencrypt/dhparams/dhparam.pem;

    # Redirect non-https traffic to https
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }

    return 200 'Let\'s Encrypt certificate successfully installed!';
    add_header Content-Type text/plain;
}
Enter fullscreen mode Exit fullscreen mode

Now, all we need to do is build the docker image and run it on our server.

docker compose -f docker-compose.yml build
Enter fullscreen mode Exit fullscreen mode

Next, move the images to the server whose IP address is mapped to a domain name you own and run this command:

docker compose -f docker-compose.yml up
Enter fullscreen mode Exit fullscreen mode

The command starts the server and Certbot automatically creates new SSL certificates for us. Try navigating to the domain name mapped to the server, you should see a "not trusted" page.

This is because we used "test" certificates to make sure that our configuration works fine. Issuing of live certificates is rate limited, so using test/staging certificates first is a better approach to ensure that it runs fine.

Now, we will update our nginx-certbot.env file to enable us generate live SSL certificates. Change the staging value from 1 to 0.

...
STAGING=0
Enter fullscreen mode Exit fullscreen mode

Stop the server with ctrl+c and run the command below to regenerate SSL certificates.

docker exec -it <container_name> /scripts/run_certbot.sh force
Enter fullscreen mode Exit fullscreen mode

Start up the server again

docker compose -f docker-compose.yml up
Enter fullscreen mode Exit fullscreen mode

Now, navigate to your domain name. Your site should open up just fine!

Certbot and Nginx configurations for Dockerized apps

Assuming you dockerized your app, you will need to make a few more changes to what we have done so far.
First, we update the docker-compose.yml file so that the Nginx service depends on your app.

    ...
    volumes:
      - nginx_secrets:/etc/letsencrypt
      - ./user_conf.d:/etc/nginx/user_conf.d
    depends_on:
      - web

  web:
    container_name: core_app
    build: .
    restart: always
    ...

volumes:
  nginx_secrets:
Enter fullscreen mode Exit fullscreen mode

Where web is the name of your app service.

Then, we update the server.conf file to point to our web server.

upstream webapp {
    server core_app:8000;
    # core_app is the container name of the web server
    # 8000 is the port for the web container
}
server {
    # Listen to port 443 on both IPv4 and IPv6.
    listen 443 ssl default_server reuseport;
    listen [::]:443 ssl default_server reuseport;

    # Domain names this server should respond to.
    server_name yourdomain.com www.yourdomain.com;

    server_tokens off;
    client_max_body_size 20M;

    # Load the certificate files.
    ssl_certificate         /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;

    # Load the Diffie-Hellman parameter.
    ssl_dhparam /etc/letsencrypt/dhparams/dhparam.pem;

    # Redirect non-https traffic to https
    if ($scheme != "https") {
        return 301 https://$host$request_uri;
    }

    location / {
        proxy_pass http://webapp;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect off;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, Nginx should be routing traffic directly to your web server (docker container).

Conclusion

We have successfully created free SSL certificates for our domain name that renew automatically, so we never have to worry about it unless our server goes down.

In addition to this article, you can learn more about this approach here: https://github.com/JonasAlfredsson/docker-nginx-certbot/tree/master

Thanks for reading.

¡Hasta luego!

. . . . .