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
- A domain whose DNS A/AAAA records point at your server
- Nginx installed and serving the domain on port 80
- Port 80 reachable from the internet (Let's Encrypt uses an HTTP-01 challenge by default)
Install certbot and the Nginx plugin
sudo apt install certbot python3-certbot-nginxMake 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:
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:
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successfulReload Nginx:
systemctl reload nginx.serviceGet the cert
sudo certbot --nginx -d dev.example.comCertbot will ask for an email (used for renewal warnings) and accept the ToS. The important question is whether to redirect HTTP to HTTPS:
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.comPick 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:
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:
sudo systemctl status certbot.timer● 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.serviceRun a dry-run to confirm a real renewal would succeed:
sudo certbot renew --dry-runSaving 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: