SSL with Nginx & Certbot
How HTTPS Works (in 30 seconds)
When a browser connects to your site over HTTPS:
- Your server presents an SSL certificate — a digitally signed document proving you own the domain
- The browser verifies the certificate was signed by a trusted Certificate Authority (CA)
- Both sides agree on an encryption key and the connection is encrypted
Let's Encrypt is a free, trusted CA. Certbot is the tool that requests certificates from Let's Encrypt, proves you control the domain (by temporarily serving a file on port 80), and configures Nginx to use the cert automatically.
Prerequisites Checklist
Before running Certbot, confirm:
- Your domain's DNS A record points to your server's IP (e.g.,
example.com → 1.2.3.4) - Port 80 is open in your security group / firewall (Certbot needs it to verify ownership)
- Port 443 is open (for HTTPS traffic)
- Nginx is installed and running (
sudo systemctl status nginx) - Your Nginx server block has
server_name example.com www.example.com;
Step 1 — Install Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
The python3-certbot-nginx plugin lets Certbot read and modify your Nginx config automatically.
Step 2 — Obtain the Certificate
sudo certbot --nginx -d example.com -d www.example.com
Certbot will:
- Ask for your email (for renewal reminders)
- Ask you to agree to Let's Encrypt's Terms of Service
- Perform the HTTP-01 challenge to verify domain ownership
- Obtain the certificate
- Automatically modify your Nginx config to enable HTTPS
If you want to see what it would do without actually changing anything:
sudo certbot --nginx -d example.com --dry-run
Step 3 — What Certbot Added to Nginx
After running, your Nginx server block will look something like:
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri; # redirect HTTP → HTTPS
}
The second server block (port 80) redirects all HTTP traffic to HTTPS permanently (301).
Step 4 — Test the Configuration
sudo nginx -t # check for syntax errors
sudo systemctl reload nginx
Visit https://example.com in your browser. You should see the padlock icon with no warnings.
Also verify the redirect works:
curl -I http://example.com
# HTTP/1.1 301 Moved Permanently
# Location: https://example.com/
Step 5 — Automatic Renewal with Cron
Let's Encrypt certificates expire every 90 days. Certbot includes a renewal command (certbot renew) that checks all installed certs and renews any expiring within 30 days. You need to run this automatically.
Option A — Cron Job (classic, explicit)
Open the root crontab:
sudo crontab -e
Add this line:
0 3 * * * certbot renew --quiet && systemctl reload nginx
Breaking it down:
0 3 * * *— runs every day at 3:00 AMcertbot renew— checks and renews expiring certificates--quiet— suppresses output unless there's an error&& systemctl reload nginx— reloads Nginx to pick up the new cert after renewal
Option B — Systemd Timer (modern, preferred on Ubuntu 20.04+)
Ubuntu installs a systemd timer automatically when you install Certbot via snap (the newer install method). Check if it's already running:
sudo systemctl status snap.certbot.renew.timer
# or
sudo systemctl status certbot.timer
If the timer is active, you're done — no cron job needed. If you used apt to install Certbot (as in this guide), add the cron job from Option A.
Verify the Renewal Works
Test the renewal process without actually renewing (dry run):
sudo certbot renew --dry-run
You should see:
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
If this passes, your renewal is set up correctly.
Certificate Files Reference
Certbot stores certificates in /etc/letsencrypt/live/<domain>/:
| File | Purpose |
|---|---|
fullchain.pem | Your cert + the intermediate CA chain (use this in Nginx) |
privkey.pem | Your private key (never share this) |
cert.pem | Your cert only (without chain — rarely needed) |
chain.pem | The intermediate chain only |
Common Issues
IMPORTANT NOTES: Failed to renew certificate — usually means port 80 is blocked or Nginx isn't running. The HTTP-01 challenge requires port 80 to be reachable.
certificate not yet due for renewal — Certbot only renews if expiry is within 30 days. Run certbot certificates to see the expiry date.
Certificate works but old cert still showing — reload Nginx after renewal: sudo systemctl reload nginx.