How to Install a Let's Encrypt SSL Certificate on Nginx: A Step-by-Step Guide
Let's Encrypt is a free certificate authority trusted by every modern browser. With its certificate, your site opens over HTTPS, browsers don't show a "Not secure" warning, and search engines don't penalize the site.
This guide gets a certificate with Certbot, a tool that configures Nginx for you, sets up the HTTP-to-HTTPS redirect and turns on automatic renewal.
Why auto-renewal is critical. Let's Encrypt certificates are currently valid for 90 days, and that's getting shorter: 64 days from February 2027 and 45 days from February 2028. Let's Encrypt no longer sends expiration reminder emails. If renewal breaks, you'll hear about it from your customers. That's why this guide covers how to check that auto-renewal works.
Requirements:
- A server running Ubuntu 22.04, 24.04 or 26.04 with Nginx installed.
- A domain whose A record points to the server's IP. Check with
dig +short example.com. - Ports 80 and 443 open. Port 80 is needed even for HTTPS: Let's Encrypt uses it to verify that you own the domain.
Step 1. Install Certbot
Install Certbot and its Nginx plugin from the Ubuntu repository:
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
Check the install: certbot --version.
The Certbot team also ships it as a snap, which always has the latest version. If you prefer that, install it instead of the apt package:
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Don't install both: two Certbot versions will conflict during renewal.
If the firewall is on, open the web ports:
sudo ufw allow 'Nginx Full'
Step 2. Prepare Your Site's Nginx Config
Certbot looks for a server block with your domain in the server_name line and adds the HTTPS settings to it. If your site already has a config, check that the domain is listed in server_name and skip to step 3.
If there's no config yet, create a minimal one. Replace example.com with your domain:
sudo nano /etc/nginx/sites-available/example.com
Paste the contents:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
}
Create the site folder with a test page and enable the config:
sudo mkdir -p /var/www/example.com
echo '<h1>It works</h1>' | sudo tee /var/www/example.com/index.html
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Open http://example.com in your browser — you should see "It works". If you see the default Nginx page instead, check server_name.
Domain with and without www. Include both in server_name and create an A record for www. Otherwise the certificate covers only one address, and the browser shows an error on the other.
Step 3. Get the Certificate
One command gets the certificate and configures HTTPS in Nginx:
sudo certbot --nginx -d example.com -d www.example.com
On the first run, Certbot asks for:
- Email. Important account notices go there. There will be no more expiration reminders.
- Agreement to the Let's Encrypt terms of service — answer
Y. - The EFF newsletter (the organization behind Certbot) — your choice.
Certbot checks that the domain points to your server, gets the certificate and adds HTTPS settings plus an HTTP-to-HTTPS redirect for all requests to your Nginx config. At the end you'll see Successfully deployed certificate.
The certificate files are in /etc/letsencrypt/live/example.com/:
fullchain.pem— the certificate with intermediates, used inssl_certificate.privkey.pem— the certificate's private key, used inssl_certificate_key. Never share it.
To get a certificate without letting Certbot touch your Nginx config, use certonly: sudo certbot certonly --nginx -d example.com. Then add the ssl_certificate and ssl_certificate_key lines to your config by hand.
Step 4. Verify the Certificate and Auto-Renewal
Open https://example.com — you should see a padlock in the address bar. The http:// address should redirect to HTTPS on its own.
List the certificates on the server and their expiry dates:
sudo certbot certificates
Auto-renewal. Certbot creates a systemd timer that checks certificates twice a day and renews those close to expiring. Make sure the timer is active:
systemctl list-timers | grep certbot
The output should include certbot.timer (for the snap version, snap.certbot.renew.timer) with the next run time.
Check that renewal will work with a test run (it doesn't touch your real certificates):
sudo certbot renew --dry-run
If it ends with Congratulations, all simulated renewals succeeded, auto-renewal works. When renewing through the --nginx plugin, Certbot reloads Nginx itself so it picks up the new certificate.
Checking from outside. To see when the certificate your site actually serves expires, run this from any computer:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
Since Let's Encrypt no longer sends expiration emails, set up external monitoring with an SSL check: it will alert you via Telegram or email if less than a week is left before expiry — a sign that auto-renewal has broken.
Wildcard Certificate for All Subdomains
A wildcard certificate *.example.com covers any subdomain: app.example.com, n8n.example.com, shop.example.com. It's handy when you have many subdomains or create them on the fly.
For wildcards, Let's Encrypt verifies the domain through DNS rather than the website: Certbot has to create a special TXT record. For renewal to stay automatic, you need a plugin for your DNS provider. Here's an example with Cloudflare, the most popular option.
Install the plugin:
sudo apt install -y python3-certbot-dns-cloudflare
In Cloudflare, create an API token from the Edit zone DNS template for your domain. Save it to a file only root can read:
sudo mkdir -p /root/.secrets
echo 'dns_cloudflare_api_token = YOUR_TOKEN' | sudo tee /root/.secrets/cloudflare.ini
sudo chmod 600 /root/.secrets/cloudflare.ini
Get a certificate for the main domain and all subdomains. The --deploy-hook option is remembered and reloads Nginx after every renewal:
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d example.com -d "*.example.com" --deploy-hook "systemctl reload nginx"
With certonly, Certbot doesn't change your Nginx config, so add these lines to each subdomain's server block:
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Other DNS providers have their own plugins (for example, python3-certbot-dns-digitalocean). Without a plugin, you can get a wildcard manually with --manual, but then renewal is manual too — impractical with today's certificate lifetimes.
Let's Encrypt and Cloudflare: What to Keep in Mind
If your domain goes through the Cloudflare proxy (orange cloud), visitors see Cloudflare's certificate, while the certificate on your server secures the connection between Cloudflare and your VPS. In that case:
- Set Cloudflare's SSL/TLS mode to Full (strict). Flexible mode leaves traffic to your server unencrypted and, combined with an HTTPS redirect on the server, causes an endless redirect loop.
- First certificate issuance. The simplest approach is to switch the record to grey cloud (DNS only) while issuing, then turn the orange cloud back on.
- DNS validation is the most reliable. The
python3-certbot-dns-cloudflareplugin from the wildcard section works regardless of the proxy and firewall: your server doesn't even need port 80 open to the internet. This matters especially if you only allow Cloudflare IPs to reach your server, as in how to protect a VPS from DDoS attacks.
Alternative: Cloudflare Origin Certificate. A free Cloudflare certificate valid for up to 15 years, with no renewals. But only Cloudflare trusts it: if you turn the proxy off, browsers will show an error. Let's Encrypt is more versatile: your site stays available over HTTPS even without Cloudflare.
Common Errors
Timeout during connectorConnection refusedduring domain validation. Let's Encrypt can't reach your server on port 80. Check the firewall (sudo ufw status), that Nginx is running, and that the domain points to the right IP:dig +short example.com.Could not automatically find a matching server block. No Nginx config has your domain inserver_name. Go back to step 2.too many certificates already issued. You've hit Let's Encrypt's limit on reissuing identical certificates (no more than 5 per week). Wait, and use the test server for experiments: add--dry-runor--stagingto your command.- The browser shows
NET::ERR_CERT_COMMON_NAME_INVALIDon the www address.wwwisn't in the certificate. Add it:sudo certbot --nginx --expand -d example.com -dwww.example.com. ERR_TOO_MANY_REDIRECTS. Almost always Cloudflare in Flexible mode. Switch SSL/TLS to Full (strict).- The certificate renewed, but the site serves the old one. Nginx wasn't reloaded after renewal. Run
sudo systemctl reload nginx, and for certificates obtained withcertonly, add--deploy-hook "systemctl reload nginx".
Checklist
FAQ
How much does a Let's Encrypt certificate cost? Nothing, it's free. Browsers trust it just as much as paid DV certificates.
How is it different from a paid SSL certificate? The encryption is the same. Paid certificates make sense if you need organization validation (OV/EV), a warranty or support from the certificate authority.
How long is the certificate valid? Currently 90 days. From February 2027, 64 days; from February 2028, 45 days. With auto-renewal set up, this changes nothing for you.
Can I get one certificate for several domains? Yes, list them with multiple -d flags. One certificate can cover up to 100 names.
How do I delete a certificate I no longer need? sudo certbot delete --cert-name example.com. First remove references to it from your Nginx config, or Nginx won't start.