Step-by-step
-
1
Verify DNS is propagated first
Certbot will fail if your domain does not resolve to this server. Check before you run anything — it saves debugging time.
bash# Should return your server's public IP dig +short example.com dig +short www.example.com # Or use a web tool curl https://dns.google/resolve?name=example.com&type=A | python3 -m json.tool -
2
Install Certbot and the Nginx plugin
The
python3-certbot-nginxplugin is what lets Certbot read and rewrite your Nginx config automatically. Install both in one shot.bashsudo apt update sudo apt install -y certbot python3-certbot-nginx -
3
Run Certbot for your domain
This single command does everything: obtains the certificate, edits your Nginx config to use it, redirects HTTP to HTTPS, and tests the reload. Answer the interactive prompts (email for renewal alerts, ToS agreement).
bashsudo certbot --nginx -d example.com -d www.example.com -
4
Understand what Certbot changed
Certbot rewrites your server block. Inspect what it added — knowing the file layout helps when you need to tweak things later.
bashcat /etc/nginx/sites-available/myapp.conf # You'll see Certbot added: # - listen 443 ssl; # - ssl_certificate / ssl_certificate_key paths under /etc/letsencrypt/live/ # - A new server block on port 80 that redirects to https # - include /etc/letsencrypt/options-ssl-nginx.conf (modern TLS settings) # - ssl_dhparam path -
5
Test the auto-renewal timer
Certbot installs a systemd timer (or cron job on older systems) that runs twice a day and renews any certificate expiring within 30 days. Test the dry run to confirm it will work.
bash# Simulate renewal without actually renewing sudo certbot renew --dry-run # Check the timer status sudo systemctl status certbot.timer # List active timers sudo systemctl list-timers | grep certbot -
6
Verify the certificate in a browser and CLI
Hit your site over HTTPS. The padlock should show. Use OpenSSL from the command line to check the certificate details and expiry date.
bash# Should show the full certificate chain and "Verify return code: 0 (ok)" echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -subject -issuer -dates # Quick check: just expiry echo | openssl s_client -connect example.com:443 2>/dev/null \ | openssl x509 -noout -enddate -
7
Troubleshoot common failures
If Certbot fails, the error message is usually clear. The two most common causes:
- Port 80 blocked — check your firewall:
sudo ufw allow 80and check your cloud provider's security group/firewall rules - DNS not propagated — use
dig +short example.com @8.8.8.8to query Google DNS directly; if it returns the wrong IP, wait and retry - Nginx not reloading cleanly — run
sudo nginx -tfirst; Certbot can't reload a broken config
bash# Open port 80 if blocked sudo ufw allow 80 sudo ufw allow 443 sudo ufw reload # Check Certbot logs for the exact error sudo cat /var/log/letsencrypt/letsencrypt.log | tail -50 - Port 80 blocked — check your firewall:
-
8
Force HTTPS and check redirects
Certbot usually adds the HTTP→HTTPS redirect automatically. Confirm it with curl — you want a 301 redirect from port 80 to 443, not a 200 on both.
bash# Should return 301 Moved Permanently with Location: https:// curl -I http://example.com # Should return 200 OK curl -I https://example.com # Follow redirects automatically curl -IL http://example.com
Tips & gotchas
- Add the <code>--staging</code> flag while testing to avoid Let's Encrypt rate limits (5 failed cert requests per domain per hour).
- Let's Encrypt certificates are valid for 90 days. The auto-renewal timer runs twice daily and renews at ~60 days — you should never need to renew manually.
- Wildcard certificates (<code>*.example.com</code>) require DNS-01 challenge (a TXT record), not HTTP-01. Use: <code>sudo certbot certonly --manual --preferred-challenges dns -d '*.example.com'</code>
- If your server is behind a load balancer or CDN, the HTTP-01 challenge won't work (the request won't reach your server). Use DNS-01 instead or certbot's webroot plugin.
- Run <code>sudo certbot certificates</code> to list all certificates on the machine and their expiry dates.
Wrapping up
Your site is now on HTTPS with a certificate that renews itself. Between this guide and the Nginx reverse proxy guide before it, you have a production-grade setup: Nginx terminating TLS, proxying to your app, auto-renewing certificates every 90 days. The next step is locking down your security headers — HSTS, CSP, and X-Frame-Options.