Home

Free SSL with Let's Encrypt and Nginx (with auto-renewal)

Certbot + Let's Encrypt is the easiest way to get a real SSL cert on a public-facing Nginx site. Free, trusted by every browser, auto-renews itself in the background. This walkthrough is on Ubuntu 20.04, but the same flow works on any Debian-based distro.

Prereqs

Install certbot and the Nginx plugin

bash
sudo apt install certbot python3-certbot-nginx

Make sure Nginx is configured for the domain

Certbot needs Nginx to already serve the domain on HTTP — it'll modify the config to add HTTPS. The minimum you need is a server_name line:

nginx
server {    listen 80 default_server;    listen [::]:80 default_server;    root /var/www/html;    index index.html index.htm;    server_name dev.example.com;}

Test the config:

bash
nginx -t
bash
nginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successful

Reload Nginx:

bash
systemctl reload nginx.service

Get the cert

bash
sudo certbot --nginx -d dev.example.com

Certbot will ask for an email (used for renewal warnings) and accept the ToS. The important question is whether to redirect HTTP to HTTPS:

bash
Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.1: No redirect Make no further changes to the webserver configuration.2: Redirect Make all requests redirect to secure HTTPS access.Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/defaultCongratulations! You have successfully enabled https://dev.example.com

Pick 2 (Redirect) unless you have a reason not to. After certbot finishes, your Nginx config will have new SSL blocks added — it looks something like:

nginx
server {    root /var/www/html;    index index.html index.htm index.nginx-debian.html;    server_name dev.example.com; # managed by Certbot    location / {        try_files $uri $uri/ =404;    }    listen [::]:443 ssl ipv6only=on; # managed by Certbot    listen 443 ssl;                  # managed by Certbot    ssl_certificate     /etc/letsencrypt/live/dev.example.com/fullchain.pem; # managed by Certbot    ssl_certificate_key /etc/letsencrypt/live/dev.example.com/privkey.pem;   # managed by Certbot    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;   # managed by Certbot}server {    if ($host = dev.example.com) {        return 301 https://$host$request_uri;    } # managed by Certbot    listen 80 ;    listen [::]:80 ;    server_name dev.example.com;    return 404; # managed by Certbot}

Auto-renewal

Certbot ships with a systemd timer that runs renewals twice a day. Verify it's enabled:

bash
sudo systemctl status certbot.timer
bash
 certbot.timer - Run certbot twice daily     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled)     Active: active (waiting) since Sun 2022-02-13 23:19:34 UTC; 40min ago    Trigger: Mon 2022-02-14 08:30:04 UTC; 8h left   Triggers: certbot.service

Run a dry-run to confirm a real renewal would succeed:

bash
sudo certbot renew --dry-run
bash
Saving debug log to /var/log/letsencrypt/letsencrypt.logProcessing /etc/letsencrypt/renewal/dev.example.com.confCert not due for renewal, but simulating renewal for dry runRenewing an existing certificatePerforming the following challenges:http-01 challenge for dev.example.comWaiting for verification...Cleaning up challengesnew certificate deployed with reload of nginx server** DRY RUN: simulating 'certbot renew' close to cert expiryCongratulations, all renewals succeeded. The following certs have been renewed:  /etc/letsencrypt/live/dev.example.com/fullchain.pem (success)

If that looks clean, you're done — certbot will renew at roughly the 60-day mark and reload Nginx automatically. If a renewal ever fails, Let's Encrypt emails the address you gave at signup before the cert expires.

Verify

Test the cert at SSL Labs: