How to Set Up Free SSL on Nginx With Certbot
In this article12 sections
To set up free SSL on Nginx, install Certbot, make sure your domain points at the server and ports 80 and 443 are open, then run sudo certbot --nginx -d example.com -d www.example.com. Certbot proves you control the domain, gets a certificate from Let’s Encrypt, edits your Nginx configuration to use it, and installs a timer that renews it automatically. The whole job takes about five minutes, and the certificate costs nothing.
This guide is for Ubuntu 22.04 and 24.04 with Nginx already serving your site over plain HTTP. It also covers the three reasons the command fails, how to confirm renewal will work before you need it, and what changes when Cloudflare sits in front of the server.
Before you start: three checks
Nearly every failed attempt comes from one of these, so check them first.
1. The domain points at this server. Compare the server’s address with what DNS answers:
curl -4 -s ifconfig.me; echo
dig +short A example.com
dig +short A www.example.com
dig +short AAAA example.com
The first three lines must show the same IPv4 address. The last one is the check people miss: if the domain has an AAAA (IPv6) record, Let’s Encrypt prefers it. An old IPv6 address left over from a previous host makes validation fail even though the site loads fine for you. Correct it or delete it.
2. Ports 80 and 443 are open.
sudo ufw allow 'Nginx Full'
sudo ufw status
Port 80 must stay open even after the site moves to HTTPS, because renewals use it.
3. Nginx knows the domain. Certbot finds the right configuration by its server_name line:
sudo grep -R "server_name" /etc/nginx/sites-enabled/
You should see server_name example.com www.example.com;. If it still says _ or localhost, edit the file, then run sudo nginx -t && sudo systemctl reload nginx.
Step 1: Install Certbot
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
Step 2: Get and install the certificate
sudo certbot --nginx -d example.com -d www.example.com
List every name the site answers to, each with its own -d. Certbot asks for an email address for your Let’s Encrypt account and for agreement to its terms. When it finishes you will see:
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
Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/example.com
Congratulations! You have successfully enabled HTTPS on https://example.com
Certbot has added the certificate lines to your server block and a redirect from HTTP to HTTPS. Open the site in a browser and check for the padlock.
Step 3: Confirm renewal works
Let’s Encrypt certificates last 90 days. Certbot renews them when 30 days remain, using a timer it installed. Test the whole process now, without changing anything:
sudo certbot renew --dry-run
systemctl list-timers | grep -i certbot
The dry run must end with Congratulations, all simulated renewals succeeded, and the second command must show a timer with a next run time. If both are true, you never have to think about this certificate again.
Tip. Run the dry run again after any change to the firewall, to Nginx, or to DNS. A renewal that silently stopped working is how sites end up with an expired certificate three months later.
Step 4: Check what you have
sudo certbot certificates
This lists each certificate, the names it covers and its expiry date. To see what visitors actually receive:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -ext subjectAltName
When it fails
| Certbot says | Cause | Fix |
|---|---|---|
DNS problem: NXDOMAIN |
One of the names has no DNS record | Add the record, or leave that -d out |
Timeout during connect (likely firewall problem) |
Port 80 is closed, here or at the provider’s firewall | Open port 80 and try again |
Invalid response ... 404 |
The request reached a different server, or a different Nginx block | Check the A and AAAA records and the server_name |
too many failed authorizations recently |
Five failures for the same name within an hour | Fix the cause, wait an hour, then retry |
Could not automatically find a matching server block |
No server_name matches |
Add the name to the Nginx configuration |
While you are still working out a problem, test with Let’s Encrypt’s staging system, which has far higher limits, so repeated attempts cannot lock you out:
sudo certbot certonly --nginx --dry-run -d example.com -d www.example.com
When that passes, run the real command from step 2.
Adding another name later
Run the command again with the full list, old names and new:
sudo certbot --nginx -d example.com -d www.example.com -d shop.example.com
Certbot offers to expand the existing certificate. For a separate website on the same server, run it with that site’s names only, and it gets a certificate of its own.
If Cloudflare is in front of the server
With the orange cloud switched on, visitors connect to Cloudflare, and Cloudflare connects to your server. You still want a real certificate on the server, so that second connection is encrypted and verified:
- Certbot works as described above. Cloudflare passes the validation request through on port 80.
- In Cloudflare, set SSL/TLS mode to Full (strict). “Flexible” sends traffic to your server unencrypted and causes redirect loops once Nginx redirects to HTTPS.
- If validation fails only with the proxy on, check that no Cloudflare rule blocks or redirects paths starting with
/.well-known/acme-challenge/.
Common mistakes to avoid
- Closing port 80 after getting the certificate. Renewal fails 60 days later.
- Forgetting the
wwwname. The certificate then does not cover it and visitors towwwsee a warning. - Leaving a stale AAAA record. Validation goes to the old IPv6 address.
- Retrying a failing command over and over. Five failures an hour is the limit. Use the
certonly --dry-runtest until it passes. - Copying certificate files to another location. Point Nginx at the files in
/etc/letsencrypt/live/. They are links that always lead to the newest certificate; copies go stale at the first renewal. - Never testing renewal. One dry run today saves an outage in three months.
- Waiting for a reminder email. Let’s Encrypt stopped sending them. Monitor the expiry date yourself.
When a free certificate is not enough
A Let’s Encrypt certificate encrypts as well as any paid one. A paid certificate is worth having when you need your organisation’s name verified in it (OV or EV), a warranty, or a year of validity on a system that cannot renew automatically. If you need a wildcard, read what a wildcard certificate covers first: Certbot can issue one, but only with DNS validation, not with the --nginx method used here.
SSL on a SiteHarbour VPS
When we set up a SiteHarbour VPS, our engineers connect your domain and install a free Let’s Encrypt certificate on it as part of the handover, so the first site on the server is already on HTTPS. Use this guide when you add more sites or names yourself. For verified business certificates, see our SSL certificates.
Frequently asked questions
Is a Let’s Encrypt certificate really free?
Yes. There is no charge to issue or renew it, and no limit on how long you can keep renewing.
Do I have to renew it by hand every 90 days?
No. Certbot’s timer checks twice a day and renews any certificate with less than 30 days left. Your only job is to confirm the dry run passes.
Will Certbot break my Nginx configuration?
It edits only the server block that matches your domain and tests the result before reloading. To be safe, keep a copy of the file first, and run sudo nginx -t afterwards.
Can I use one certificate for several domains?
Yes, by listing them all with -d. For unrelated websites it is tidier to give each its own certificate, so one site’s problem cannot block another’s renewal.
What happens if a renewal fails?
Certbot tries again twice a day, so a temporary problem fixes itself. Let’s Encrypt no longer sends expiry reminder emails, so do not rely on being warned: add a certificate expiry check to your monitoring (Uptime Kuma has one built in), or run sudo certbot certificates once a month.
The short version: check DNS (including IPv6), open ports 80 and 443, run one Certbot command, and prove renewal with a dry run. After that the certificate looks after itself.