
If you're running a website on Nginx and still serving traffic over plain HTTP, your visitors are exposed to eavesdropping, and browsers are actively warning them about it. This guide is for sysadmins and developers who want to lock down their server with a free, trusted SSL certificate — without paying for one or manually renewing it every year.
By the end of this guide, your Nginx server will serve traffic over HTTPS, redirect all HTTP requests automatically, and renew its certificate without any manual intervention.
You'll use Let's Encrypt (a free Certificate Authority) and Certbot (the tool that automates the whole process) on Ubuntu 22.04 or later.
Before you begin, make sure you have:
example.com) with two DNS A records:
example.com → your server's public IPwww.example.com → your server's public IPNote: Let's Encrypt cannot issue certificates for bare IP addresses. A registered domain with public DNS is required.
By following these steps, you'll end up with:
Certbot is the official client for Let's Encrypt. The recommended installation method on Ubuntu is via snap, which ensures you always get the latest version.
First, update the snap core:
sudo snap install core; sudo snap refresh core
If you have an older version of Certbot installed from APT, remove it to avoid conflicts:
sudo apt remove certbot
Now install Certbot via snap:
sudo snap install --classic certbot
Finally, symlink the binary so you can run certbot from anywhere:
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Alternative (APT): If your environment doesn't have snapd, install via APT instead:
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
Note: The snap package is the recommended method — it receives updates faster and includes the Nginx plugin out of the box.
Certbot identifies which Nginx configuration to update by looking for the server_name directive that matches your domain. If it doesn't find a match, automatic configuration will fail.
Open your site's configuration file:
sudo nano /etc/nginx/sites-available/example.com
Locate the server_name line. It should look like this:
server_name example.com www.example.com;
If it doesn't match your domain, update it now. After saving, test the Nginx configuration for syntax errors:
sudo nginx -t
If the test passes, reload Nginx to apply the change:
sudo systemctl reload nginx
Tip: If
nginx -treports errors, double-check for missing semicolons or mismatched brackets in your config file.
By default, your firewall may only allow HTTP (port 80). You need to also allow HTTPS (port 443). Nginx registers named profiles with ufw that make this simple.
Check your current firewall status:
sudo ufw status
You'll likely see only Nginx HTTP listed. Allow the full profile (HTTP + HTTPS) and remove the HTTP-only rule:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
Verify the change:
sudo ufw status
Expected output:
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
Now run Certbot with the Nginx plugin. Replace example.com with your actual domain:
sudo certbot --nginx -d example.com -d www.example.com
Certbot will:
On success, you'll see output similar to:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2025-07-01.
Certbot has set up a scheduled task to automatically renew this certificate.
Certbot also updates your Nginx configuration to redirect all HTTP traffic to HTTPS automatically. Visit https://example.com in your browser — you should see a padlock icon in the address bar.
Securing multiple subdomains? Add additional -d flags:
sudo certbot --nginx -d example.com -d www.example.com -d api.example.com
Let's Encrypt certificates are valid for 90 days. Certbot installs a systemd timer that runs twice daily and renews any certificate within 30 days of expiry.
Check that the timer is active:
sudo systemctl status snap.certbot.renew.timer
Run a dry-run to simulate renewal without making actual changes:
sudo certbot renew --dry-run
If no errors appear, automatic renewal is working correctly. You can also add a post-hook to reload Nginx whenever a renewal occurs:
sudo certbot renew --dry-run --post-hook "systemctl reload nginx"
Getting a certificate is just the start. Here's how to make your HTTPS setup production-grade.
Enable HSTS to force browsers to always use HTTPS for your domain:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Restrict to modern TLS protocols and cipher suites:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
Enable OCSP Stapling for faster and more reliable certificate validation:
ssl_stapling on;
ssl_stapling_verify on;
Enable TLS session caching to speed up repeat connections:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
Place these directives inside your server block (port 443). After editing, test and reload:
sudo nginx -t && sudo systemctl reload nginx
Caution: Don't submit your domain to the HSTS preload list until you're fully confident HTTPS works correctly for all subdomains. HSTS preload is very difficult to undo.
Validate your final setup with the SSL Labs Server Test. A properly configured server should score an A or A+.
After completing all steps, run these checks to confirm everything is working:
Check certificate details:
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -enddate
Verify HTTP redirects to HTTPS:
curl -I http://example.com
You should see 301 Moved Permanently with a Location: https://example.com/ header.
Check your Nginx configuration is valid:
sudo nginx -t
Visit your site at https://example.com — the browser padlock should be green/closed with no warnings.
Error: The client lacks sufficient authorization or challenge fails
Cause: Port 80 is blocked by your firewall or upstream provider
Fix: Ensure ufw allows Nginx Full and that no external firewall (e.g., cloud security group) is blocking port 80
Error: No match for domain in Nginx config
Cause: Certbot can't find a server_name block matching your domain
Fix: Open /etc/nginx/sites-available/example.com and confirm server_name exactly matches the domain you're requesting a certificate for
Error: DNS validation fails or times out
Cause: Your A record doesn't point to the correct server IP, or DNS hasn't propagated yet
Fix: Verify DNS with dig example.com and confirm the IP matches your server. Wait a few minutes for propagation if you recently updated DNS records.
Error: certbot: command not found after snap install
Cause: The symlink to /usr/bin/certbot wasn't created
Fix: Run sudo ln -s /snap/bin/certbot /usr/bin/certbot
Error: Too many requests / rate limit hit
Cause: You've made too many certificate requests to Let's Encrypt in a short period
Fix: Use --dry-run during testing. Rate limits reset weekly — check status at crt.sh
You've now secured your Nginx server with a free, trusted SSL certificate from Let's Encrypt. Your site serves traffic over HTTPS, automatically redirects HTTP visitors, and will renew its certificate without any manual action on your part.
From here, consider exploring:
sudo certbot --manual --preferred-challenges dns -d *.example.com)http2 to your listen 443 ssl directivehttps://ssl-config.mozilla.org for a hardened Nginx template tailored to your requirements