LEMP Field Notes
Security & TLS

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

How to Secure Nginx with Let's Encrypt (Certbot Tutorial)
In briefTo secure Nginx with Let's Encrypt, install Certbot (sudo snap install --classic certbot on Ubuntu), then run sudo certbot --nginx -d yourdomain.com. Certbot proves you control the domain over port 80, obtains a free browser-trusted TLS certificate, edits your Nginx server block to serve HTTPS, and sets up an HTTP-to-HTTPS redirect. Certificates last 90 days, and a bundled systemd timer renews them automatically — verify it with sudo certbot renew --dry-run.

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:

  1. Certbot asks Let's Encrypt for a challenge.
  2. Let's Encrypt replies with a token that must be served at a specific URL under http://yourdomain.com/.well-known/acme-challenge/.
  3. Certbot arranges for Nginx to serve that token, and Let's Encrypt's servers fetch it over the public internet.
  4. 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:

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.

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:

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.

FAQ

Is a Let's Encrypt certificate really free?

Yes. Let's Encrypt is a nonprofit certificate authority that issues domain-validated TLS certificates at no cost, and they are trusted by all mainstream browsers. The encryption is identical to what paid DV certificates provide. What you don't get is organization or extended validation and a paid support contract — neither of which affects security for a typical website.

How do I install Certbot on Ubuntu?

Certbot's maintainers recommend the snap package: run sudo snap install core, sudo snap refresh core, then sudo snap install --classic certbot, and symlink it with sudo ln -s /snap/bin/certbot /usr/bin/certbot. Alternatively, install from apt with sudo apt install certbot python3-certbot-nginx, though the apt version can lag behind the snap. Use one method, not both.

How does Let's Encrypt auto renewal work with Nginx?

Let's Encrypt certificates are valid for 90 days, and Certbot renews any certificate within 30 days of expiry. The installer sets up a scheduled task automatically — a systemd timer for both the snap and apt installs — so no cron job is needed. When a certificate renews, Certbot reloads Nginx to pick it up. Test the whole path safely with sudo certbot renew --dry-run.

Do I still need port 80 open after enabling HTTPS?

Yes, keep port 80 open. Let's Encrypt's HTTP-01 challenge validates your domain by fetching a token over plain HTTP, and that validation repeats at every renewal — closing port 80 is one of the most common causes of silent renewal failure. Port 80 should serve only a 301 redirect to HTTPS, which Certbot configures for you.

Can one certificate cover multiple domains or subdomains?

Yes. Pass each hostname with its own -d flag — for example sudo certbot --nginx -d example.com -d www.example.com — and Certbot puts all the names on a single certificate. Every name must resolve to your server and appear in a server_name directive so validation can succeed. Wildcard certificates (*.example.com) are also available but require the DNS-01 challenge instead of the Nginx plugin's HTTP challenge.

Why did my Certbot challenge fail?

The usual causes: DNS doesn't point at the server yet (check with dig +short yourdomain.com), port 80 is blocked by ufw or a cloud firewall, or your server block's server_name doesn't include the domain you requested. Fix the issue, then retry with sudo certbot renew --dry-run or a fresh issuance. Debug against the staging environment via --dry-run so failed attempts don't count toward Let's Encrypt's production rate limits.