Securing Nginx with Certbot
When you visit a site over HTTPS (the secure version of HTTP, the protocol browsers use to load web pages), the little padlock in the address bar means the connection is encrypted. That encryption needs an SSL/TLS certificate (a small file that proves your domain is who it says it is and lets browsers set up an encrypted connection). Certbot is a free command-line tool that gets one of these certificates from Let’s Encrypt (a non-profit that issues them for free) and wires it into your web server automatically. This page walks through using Certbot to add HTTPS to an Nginx site (Nginx is a popular, fast web server) on Ubuntu, step by step.
When to use Certbot with Nginx
Use Certbot’s Nginx plugin when Nginx is the server that terminates traffic for your domain (the public-facing server users connect to) and you want it to handle HTTPS directly. This is the most common setup for a website, a single-page app, or a reverse proxy (a server that sits in front of your app and forwards requests to it).
Do not use the Nginx plugin if HTTPS is already handled somewhere in front of Nginx — for example a cloud load balancer, Cloudflare in “Full” proxy mode, or another gateway that already presents the certificate. In those cases the edge handles the certificate and your Nginx box may not even be reachable on port 443 from the public internet. If you run Apache instead of Nginx, see the Certbot + Apache page.
| Situation | Use Certbot —nginx? |
|---|---|
| Nginx is your public web server / reverse proxy | Yes |
| A load balancer or Cloudflare terminates TLS | No — certificate lives at the edge |
| You only want the certificate file, not config edits | Use certbot certonly instead |
You need a wildcard cert (*.example.com) | Not with this plugin — needs DNS validation |
Before you start
Two things must already be true, or Certbot will fail:
- Your domain’s DNS A record (the record that maps a name like
example.comto your server’s IP address) points at this server. Check it before anything else. - Nginx already serves the site on port 80 (plain HTTP) and your firewall allows web traffic.
# Confirm DNS points here (compare the IP it prints to your server's IP)
dig +short example.com
# Confirm Nginx is running
sudo systemctl status nginx
Output:
203.0.113.42
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-06-15 09:12:04 UTC; 2h ago
Make sure your UFW firewall (Uncomplicated Firewall, Ubuntu’s simple firewall front-end) lets in HTTP and HTTPS. The Nginx Full profile opens both ports 80 and 443:
sudo ufw allow 'Nginx Full'
sudo ufw status
Output:
Status: active
To Action From
-- ------ ----
Nginx Full ALLOW Anywhere
Install Certbot
The Certbot project recommends installing via snap (Ubuntu’s universal package format) because it always ships the latest version. The older apt install certbot packages can lag behind.
# Make sure snapd is present and up to date
sudo apt update
sudo snap install core
sudo snap refresh core
# Install certbot and link it so you can run `certbot` directly
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Output:
certbot 3.1.0 from Certbot Effort (certbot-eff✓) installed
If you previously installed Certbot from apt, remove it first with
sudo apt remove certbotso the two versions don’t clash on your PATH.
The snap of Certbot bundles the Nginx plugin, so there is nothing extra to install. (If you instead use the apt route, you would also need sudo apt install python3-certbot-nginx to get the plugin.)
Run Certbot for Nginx
Now the main event. The --nginx flag tells Certbot to read your Nginx config, find the domains, obtain a certificate, and edit the config to use it.
sudo certbot --nginx
Certbot is interactive the first time. It asks for an email (used for expiry warnings), asks you to agree to the terms, then lists the domains it found in your Nginx server blocks. Pick the one(s) you want.
Output:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: example.com
2: www.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1,2
Requesting a certificate for example.com and www.example.com
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 2026-09-13.
Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/example.com
Successfully deployed certificate for www.example.com to /etc/nginx/sites-enabled/example.com
Congratulations! You have successfully enabled HTTPS on https://example.com and
https://www.example.com
You can skip the prompts by passing everything on the command line, which is handy for scripts:
sudo certbot --nginx -d example.com -d www.example.com \
--redirect --agree-tos -m [email protected] --no-eff-email
The --redirect flag tells Certbot to add a rule that automatically sends all plain HTTP visitors to HTTPS. If you leave it off, Certbot asks interactively whether to redirect — always say yes for a public site.
What Certbot changed in your config
Certbot edits your server block in place. After it runs, /etc/nginx/sites-available/example.com gains the certificate lines and a redirect block. The Certbot-added lines are marked with # managed by Certbot comments — leave those alone so future renewals can update them.
server {
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/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 = www.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name example.com www.example.com;
return 404; # managed by Certbot
}
The first block now listens on port 443 with TLS. The second block listens on port 80 and 301-redirects (a permanent “moved” redirect that browsers and search engines remember) every plain-HTTP request to the HTTPS version.
Verify HTTPS works
Reload Nginx and test the live site. Certbot already reloaded it, but it’s good practice to confirm the config is valid yourself.
sudo nginx -t
sudo systemctl reload nginx
# Follow redirects and inspect the TLS certificate
curl -sI http://example.com | head -n 1
curl -svo /dev/null https://example.com 2>&1 | grep -E 'subject:|expire'
Output:
nginx: configuration file /etc/nginx/nginx.conf test is successful
HTTP/1.1 301 Moved Permanently
* subject: CN=example.com
* expire date: Sep 13 06:00:00 2026 GMT
The 301 Moved Permanently confirms the HTTP-to-HTTPS redirect, and the certificate subject confirms the TLS handshake works. Open the site in a browser and you should see the padlock.
Certificates from Let’s Encrypt last only 90 days on purpose. Certbot installs a systemd timer that renews them automatically — verify it with
sudo systemctl list-timers | grep certbot. Never plan to renew by hand. See the auto-renew page for details.
Best practices
- Always confirm DNS points at the server before running Certbot — a wrong A record is the number-one cause of failed validation.
- Use the snap install so you stay on a current, supported Certbot release with the latest TLS defaults.
- Always enable the
--redirectoption so no traffic is ever served unencrypted. - Never hand-edit the
# managed by Certbotlines; let Certbot own them so renewals apply cleanly. - Test renewals safely with
sudo certbot renew --dry-runafter setup to prove the automatic renewal will succeed. - Keep both the bare domain and the
wwwsubdomain on the same certificate so either name works with HTTPS.