How to Secure Nginx with Let's Encrypt (Certbot Tutorial)

- How Do You Secure Nginx with Let's Encrypt?
- What Let's Encrypt and Certbot Actually Do
- Prerequisites
- Step 1 — Install Certbot
- Step 2 — Obtain and Install the Certificate
- What Certbot Changed in Your Nginx Config
- Step 3 — Verify HTTPS Is Working
- Step 4 — Confirm Automatic Renewal
- Common Errors and Fixes
- Wrapping Up
How Do You Secure Nginx with Let's Encrypt?
To secure Nginx with Let's Encrypt, you install Certbot, run sudo certbot --nginx -d yourdomain.com, and let it obtain a free, browser-trusted TLS certificate and rewrite your Nginx configuration to serve HTTPS. The certificate is valid for 90 days, and Certbot installs a timer that renews it automatically — so after one short session at the terminal, HTTPS becomes something your server maintains by itself.
That is the whole arc of this tutorial. The rest of this guide walks through each step on Ubuntu, explains what Certbot actually changes on disk, and shows you how to verify that auto-renewal will keep working long after you have logged out.
This guide assumes you already have Nginx running and a server block answering for your domain. If you are starting from a bare server, work through installing the LEMP stack on Ubuntu 24.04 first, then set up Nginx server blocks for your domain — Certbot reads your server block to figure out which config to edit, so having it correct beforehand makes everything smoother.
What Let's Encrypt and Certbot Actually Do
Let's Encrypt is a nonprofit certificate authority that issues TLS certificates at no cost. Its certificates are trusted by all mainstream browsers and are functionally equivalent to paid domain-validated (DV) certificates: same encryption, same padlock. What you give up compared to paid certs is organization validation and support contracts — nothing that matters for a typical site.
Certbot is the official client recommended by the EFF for talking to Let's Encrypt. It speaks the ACME protocol: it proves to Let's Encrypt that you control your domain, retrieves the certificate, and — with the Nginx plugin — edits your server block to use it.
The domain-validation step is worth understanding because it explains most failures:
- Certbot asks Let's Encrypt for a challenge.
- Let's Encrypt replies with a token that must be served at a specific URL under
http://yourdomain.com/.well-known/acme-challenge/. - Certbot arranges for Nginx to serve that token, and Let's Encrypt's servers fetch it over the public internet.
- If the token matches, validation passes and the certificate is issued.
This is why your DNS must already point at the server and why port 80 must be reachable. No amount of local configuration can substitute for Let's Encrypt being able to reach your domain from outside.
Prerequisites
Before running Certbot, confirm all four of these:
- A registered domain with an A record (and AAAA record, if you use IPv6) pointing at your server's public IP. DNS changes can take a while to propagate; verify with
dig +short yourdomain.combefore proceeding. - Nginx installed and running, with a server block whose
server_namematches your domain — for exampleserver_name example.com www.example.com;. - Ports 80 and 443 open. If you use ufw, allow the full Nginx profile:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
The first command opens both 80 and 443; the second removes the now-redundant HTTP-only rule if you added it earlier. Do not close port 80 entirely — the HTTP-01 challenge above needs it, both now and at every renewal.
- Root or sudo access on the server.
Step 1 — Install Certbot
On current Ubuntu releases, Certbot's maintainers recommend installing via snap, which stays up to date independently of Ubuntu's release cycle:
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
The symlink just makes certbot available on your normal PATH.
If you prefer apt — for instance, on a system where snap is unavailable — the packaged version works too, though it may lag behind:
sudo apt update
sudo apt install certbot python3-certbot-nginx
Either route gives you the certbot command plus the Nginx plugin. Pick one; do not install both.
Step 2 — Obtain and Install the Certificate
One command does the work:
sudo certbot --nginx -d example.com -d www.example.com
Replace the domains with your own, and list every hostname your server block answers for — each -d flag adds a name to the same certificate. On first run, Certbot will prompt you for an email address (used for expiry and security notices) and ask you to agree to the Let's Encrypt terms of service.
Certbot then performs the challenge, retrieves the certificate, and edits your server block. Depending on your Certbot version, it will either configure an HTTP-to-HTTPS redirect automatically or ask whether you want one — say yes. Redirecting is standard practice: it guarantees visitors and search engines land on the encrypted version of your site.
When it finishes, Certbot reports where the certificate lives. The important paths:
| Path | Contents |
|---|---|
/etc/letsencrypt/live/example.com/fullchain.pem |
Certificate plus intermediate chain |
/etc/letsencrypt/live/example.com/privkey.pem |
Private key |
/etc/letsencrypt/renewal/example.com.conf |
Renewal settings for this certificate |
The files under live/ are symlinks into an archive directory, which is how renewal swaps in new certificates without you touching Nginx config again.
What Certbot Changed in Your Nginx Config
Open your server block afterward and you will find Certbot added lines like these inside the server block:
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
The included options-ssl-nginx.conf carries Certbot's maintained TLS settings — protocol versions and cipher choices — so you inherit sane, current defaults without hand-tuning them. Certbot also adds (or offers to add) a small HTTP server block that returns a 301 redirect to HTTPS.
If you ever edit this file by hand, keep the habit from every good Nginx workflow: run sudo nginx -t to validate syntax, then sudo systemctl reload nginx to apply. A reload picks up config changes without dropping connections.
Step 3 — Verify HTTPS Is Working
Three quick checks:
- Browser: visit
https://example.comand confirm the padlock appears with no warnings. Then visit the plainhttp://URL and confirm it redirects. - Command line:
curl -I http://example.comshould return301 Moved Permanentlywith aLocation:header pointing at the HTTPS URL. - External scan: a TLS testing service such as Qualys SSL Labs will grade your configuration and flag chain or protocol issues. Certbot's defaults typically score well.
If the browser still warns you, the usual culprits are a cached old page, a server_name mismatch, or mixed content — HTTPS pages loading images or scripts over plain HTTP. Mixed content is a page problem, not a certificate problem: update those asset URLs to https:// or protocol-relative paths.
Step 4 — Confirm Automatic Renewal
Let's Encrypt certificates last 90 days by design — short lifetimes limit the damage of a stolen key and force automation. Certbot renews any certificate that is within 30 days of expiry, and the installer sets up scheduled runs for you: the snap install uses a systemd timer (snap.certbot.renew.timer), and the apt package ships certbot.timer. You can see whichever is present with:
systemctl list-timers | grep -i certbot
The check that actually matters is the dry run, which exercises the full renewal path against Let's Encrypt's staging environment without issuing anything:
sudo certbot renew --dry-run
If that succeeds, real renewals will succeed under the same conditions. Run it once after setup, and again after any change to your firewall, DNS, or Nginx config. The renewal process reloads Nginx for you when a certificate is replaced, so new certificates go live without manual steps.
Two things quietly break renewal more than anything else: closing port 80 ("we're HTTPS now, why keep it?" — because the challenge uses it) and deleting or renaming the server block Certbot originally modified.
Common Errors and Fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Challenge fails with a connection or timeout error | Port 80 blocked, or DNS not pointing at this server | Open port 80 in ufw and any cloud firewall; verify dig +short yourdomain.com returns your server IP |
| Certbot says it can't find a matching server block | server_name doesn't include the domain you passed with -d |
Add the domain to server_name, run sudo nginx -t, reload, retry |
| Certificate issued but browser shows the wrong site | Request hit the default server block | Make sure your domain's server block exists and is enabled in sites-enabled |
| "Too many certificates" or rate-limit message | Repeated failed-then-fixed attempts against production | Wait out the limit; use --dry-run (staging) while debugging so failures don't count against you |
Let's Encrypt enforces rate limits on production issuance, which is exactly why the staging dry run exists — debug there, then issue for real once the path works.
Wrapping Up
You installed Certbot, issued a free certificate with certbot --nginx, confirmed the redirect, and verified renewal with a dry run. From here, the certificate maintains itself; your only ongoing job is not to break the two things renewal depends on — an open port 80 and the server block Certbot manages.
HTTPS is the first hardening step for a new server, not the last. For the broader picture — firewalls, SSH keys, and permissions — browse the rest of our Security & TLS guides. And if you are adding more sites to this server, each new server block can get its own certificate with one more certbot --nginx run.