Generate SSL/TLS certificates for free with Nginx/Certbot

Posted by : on

Category : Infrastructure


Why you may want to read this article

Today it is a continuation of my short Infrastructure-related articles.

This article will be about creating and configuring SSL/TLS certificates (https) for your domain and IP.

Presetup

I have my Ubuntu server, domain that points to this server, Nginx reverse proxy in the docker container, and React application in the container as well. Each request that goes to my application is not protected, because it uses http protocol (not https).

I would like to make my application more secure by adding https functionality for free, without any cost.

To do that, we will

  • Certbot tool inside the docker container that will generate certificates for domain
  • Configure Nginx to use those SSL/TLS certificates

Configuring Nginx

Nginx and certbot docker-compose

docker-compose.yml

version: '3'

services:
  nginx:
    container_name: nginx
    image: nginx:latest
    restart: always
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
      - ./nginx-logs:/var/log/nginx
    ports:
      - 80:80
      - 443:443
    networks:
      - nginx-network
      - default

  certbot:
    image: certbot/certbot:latest
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    networks:
      - nginx-network

networks:
  nginx-network:
    external: true

This docker-compose file contains nginx service specification. We created a volume for the nginx configuration to pass local configurations to the container and a volume for certificates ./certbot/….

We have added nginx-network, this is for encapsulation and accessibility. With this setup only containers that are inside nginx-network network can be accessed from nginx.

On top of that, there is certbot specification with the certificate volumes.

Nginx.conf file

nginx.conf

user  nginx;

worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    client_max_body_size 10M;

    # **disables nginx version**
    server_tokens off;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    keepalive_timeout  65;

    include /etc/nginx/conf.d/*.conf;
}

It is pretty simple nginx.conf file, just regular configurations, and in the last line we are including all the subconfigurations for nginx.

Application nginx subconfiguration

conf.d/symptoms-dev.conf

#frontend dev env
server {
    listen 80;

    server_name symptom-diary.com;

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

    location / {
        proxy_set_header Host $host;
        return 301 https://symptom-diary.com$request_uri;
    }
}


#server {
#    listen 443 ssl;

#    server_name symptom-diary.com;

#    ssl_certificate /etc/letsencrypt/live/symptom-diary.com/fullchain.pem;
#    ssl_certificate_key /etc/letsencrypt/live/symptom-diary.com/privkey.pem;

#    location / {
        #this needed to resolve host by docker dns, othervise 'set $upstream will' not work
#        resolver 127.0.0.11 valid=10s;

        #this variable needed to not fail nginx if any of the containers is down
#        set $upstream http://symptom-tracker-dev:3000;

#        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#        proxy_set_header Host $host;
#        proxy_pass $upstream;
#    }
#}

This is subconfiguration for my react application for demo purpose.

It has 2 parts:

  • The first part is for unsecured http connection that listens to 80th port of our domain symptom-diary.com. The main purpose for this block is to allow certbot authenticate throught the challenge request. Other than that It just redirects the request to https connection on 443d port.
  • The second part is specifying secured https connection with SSL/TLS protocols, it directs request to our application docker container symptom-tracker-dev on 3000 internal docker port. Until we have SSL certificates created by certbot it is unusable.

The SSL certificates are ensured by

ssl_certificate /etc/letsencrypt/live/symptom-diary.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/symptom-diary.com/privkey.pem;

But now there is no fullchain.pem and privkey.pem files, let’s generate them. That’s why the second part is commented. Now we need to generate certificates and uncomment the SSL part (second one) of configuration

Generate certificates

Go to our docker-compose.yml location.

Note, that I’m generating the certificates also for 2 subdomains: dev and blog. If you would like to have only one domain - specify only one.

Run

docker-compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d symptom-diary.com -d dev.symptom-diary.com -d blog.symptom-diary.com

You will see this output:

alt_text

As you can see our certificates were successfully generated by letsencrypt organization, and since we have our docker volumes they are already in our server and inside the nginx container.

Take a look into certificate expiration, by that time - you need to renew the certificate with the same command.

Demo

Now let’s go and try to access our application by domain name symptom-diary.com

alt_text

alt_text

As you can see it shows TLS expiration details.


Follow up

Please subscribe to my social media to not miss updates.: Instagram, Telegram

I’m talking about life as a Software Engineer at Microsoft.


Besides that, my projects:

Symptoms Diary: https://symptom-diary.com

Pet4Pet: https://pet-4-pet.com


About Andrii Bui

Hi, my name is Andrii. I'm Software Engineer at Microsoft with 5 years of experience.