Web servers
How to set up HTTPS on Nginx with Let's Encrypt and Certbot
Install Certbot from snap and run certbot --nginx. Certbot obtains a free Let's Encrypt certificate for example.com and www.example.com, configures the Nginx server block and renews the certificate automatically, and Nginx redirects plain HTTP to HTTPS.
The steps apply to Ubuntu 22.04/24.04 and Debian 12/13 with any supported Nginx release and Certbot 4.1 or later (Certbot 5.x is current as of November 2026).
Prerequisites
You need a server that Let's Encrypt can reach over HTTP on port 80.
- A Linux server with systemd and sudo access. Snap is preinstalled on Ubuntu; on Debian install it with
sudo apt install snapd. - Nginx installed and running, with a server block for your domain.
- DNS
A(andAAAA, if you use IPv6) records forexample.comandwww.example.compointing at the server, for example203.0.113.10. - Inbound TCP ports 80 and 443 open in the host firewall and in any cloud security group. Let's Encrypt validates from several network locations, so do not restrict port 80 to specific source IPs.
Check DNS and the firewall before you start:
dig +short example.com A
dig +short www.example.com A
sudo ufw allow 'Nginx Full' # only if you use ufw
Step 1: Make sure Nginx has a server block for the domain
Certbot's Nginx plugin finds the server block by its server_name, so the names must match the domains you request. A minimal HTTP-only block looks like this:
# /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
}
Enable it and test the configuration:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 2: Install Certbot from snap
The snap package is Certbot's recommended installation method; it bundles the Nginx plugin and a renewal timer. Remove any distribution package first so two copies do not fight over the same files.
sudo apt-get remove certbot # ignore "not installed" errors
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
certbot --version
If you cannot use snap, Certbot also documents a pip install in a virtual environment (supported on a best-effort basis):
sudo apt update
sudo apt install python3 python3-dev python3-venv libaugeas-dev gcc
sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-nginx
sudo ln -s /opt/certbot/bin/certbot /usr/local/bin/certbot
Step 3: Get the certificate and let Certbot configure Nginx
One command obtains the certificate with the HTTP-01 challenge, adds the TLS settings to your server block and reloads Nginx.
sudo certbot --nginx -d example.com -d www.example.com
Certbot asks you to accept the Let's Encrypt subscriber agreement and offers to register an email address. The email is optional: Let's Encrypt stopped sending expiry reminder emails on June 4, 2025. For scripts, run it non-interactively:
sudo certbot --nginx --non-interactive --agree-tos \
-d example.com -d www.example.com
If you prefer to edit Nginx yourself, use sudo certbot certonly --nginx -d example.com -d www.example.com to obtain the certificate only. The files land in /etc/letsencrypt/live/example.com/.
Step 4: Review the HTTPS server block
After Certbot finishes, the server block should reference fullchain.pem and privkey.pem and redirect HTTP to HTTPS. If you write the configuration by hand, this is a sound baseline:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on; # Nginx 1.25.1+; on older versions write "listen 443 ssl http2;" instead
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
root /var/www/example.com;
index index.html;
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Leave out ssl_stapling and ssl_stapling_verify: Let's Encrypt certificates no longer contain an OCSP URL. To generate cipher settings for your client base, use the TLS config generator. Apply the changes:
sudo nginx -t && sudo systemctl reload nginx
Verify
The live site should present the Let's Encrypt certificate for example.com and www.example.com, and curl -I http://example.com should return 301 with a Location: https://... header. Check it from outside the server with the SSL checker, which shows the chain, protocols and expiry date, or from any terminal:
curl ssl.sh/example.com
On the server, confirm what Certbot manages and what Nginx serves:
sudo certbot certificates
curl -sI https://example.com | head -n 1
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Automatic renewal
Renewal is already scheduled: the snap installs the systemd timer snap.certbot.renew.timer, which runs certbot renew twice a day. Certbot 4.1 and later check ACME Renewal Information (ARI) and renew when Let's Encrypt suggests; otherwise they renew when one third of the lifetime remains. Confirm the timer and run a dry run:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
Certificates obtained with --nginx reload Nginx automatically after renewal. If you used certonly with --webroot or --standalone, add a deploy hook so Nginx picks up the new files:
sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload nginx"
The pip install has no timer; add the cron line from the Certbot instructions. See Certbot renewal hooks for pre, post and deploy hooks. Nothing else needs to change when Let's Encrypt shortens its default lifetime to 64 days on February 10, 2027 and 45 days on February 16, 2028, as long as the timer keeps running. Because Let's Encrypt no longer emails you before expiry, add the domain to ssl.sh monitoring (in development) as a safety net.
Troubleshooting
Most failures come from DNS, the firewall or a mismatched server block.
- Timeout during connect or Connection refused: port 80 is blocked or DNS points elsewhere. See certbot timeout during connect.
- Could not automatically find a matching server block: add the exact names to
server_nameand run Certbot again. - too many certificates already issued: you hit a rate limit while testing. Use
--dry-runor--test-cert(staging), and read Let's Encrypt rate limits. - CAA record prevents issuance: your DNS has a CAA record that does not list
letsencrypt.org. See CAA record prevents issuance. - key values mismatch when reloading Nginx: the certificate and key files do not belong together. See nginx key values mismatch.
- NET::ERR_CERT_COMMON_NAME_INVALID for
www: the certificate does not cover that name. Run Certbot again with every-dname and answer "Expand". See NET::ERR_CERT_COMMON_NAME_INVALID. - Works in the browser but curl or apps fail: Nginx is serving
cert.pem. See missing intermediate certificate.
FAQ
Should Nginx use cert.pem or fullchain.pem?
Use fullchain.pem for ssl_certificate. It contains your certificate followed by the Let's Encrypt intermediate. cert.pem contains only your certificate, which works in some browsers that cache intermediates but fails in curl, Java, Android apps and many API clients.
Do I still need ssl_stapling for Let's Encrypt certificates?
No. Let's Encrypt stopped including OCSP URLs in certificates in May 2025 and shut down its OCSP responders in August 2025. With ssl_stapling on; Nginx only logs a warning that the certificate has no OCSP responder URL. Remove the stapling lines.
Can certbot --nginx issue a wildcard certificate?
No. Wildcard certificates such as *.example.com require the DNS-01 challenge, which the Nginx plugin does not perform. Use a DNS plugin, for example Certbot with the Cloudflare DNS plugin, then point Nginx at the resulting files.
How do I get the 45-day tlsserver profile instead of the default?
Certbot 4.0 and later accept --preferred-profile tlsserver (or --required-profile), and Certbot 4.1 and later save the choice for renewals. See Let's Encrypt certificate profiles before switching, because renewal must run at least daily.
Is Nginx's own ACME module an alternative to Certbot?
Yes. nginx.org publishes ngx_http_acme_module (package nginx-module-acme), which obtains and renews certificates inside Nginx with the acme_issuer and acme_certificate directives. It supports HTTP-01 and TLS-ALPN-01 but not DNS-01, so wildcards still need another client. Check the module documentation for the version you install.