How to Secure Nginx with Let's Encrypt SSL (Certbot) and Auto-Renewal
To secure Nginx with a free Let's Encrypt certificate, install Certbot from snap and run sudo certbot --nginx -d example.com -d www.example.com. Certbot proves you control the domain, downloads the certificate, adds HTTPS and an HTTP-to-HTTPS redirect to your Nginx server block, and installs a systemd timer that renews the certificate automatically. You can confirm that renewal works with sudo certbot renew --dry-run.
This guide applies to any Ubuntu 24.04 or 22.04 server running Nginx, such as a Cloudesta Cloud VPS or dedicated server. If you use shared cPanel hosting, you do not need Certbot, because cPanel's AutoSSL issues and renews certificates for you.
Prerequisites
- Root or sudo SSH access to the server.
- Nginx installed and running (
sudo apt install nginx). - DNS A records for
example.comandwww.example.compointing to your server's IPv4 address, for example203.0.113.10. - Ports 80 and 443 open to the internet.
- An Nginx server block whose
server_namelists the domains you want on the certificate.
1. How do I prepare DNS, the firewall and the Nginx server block?
First, check that both names resolve to your server. Each command should print your server's IP:
dig +short example.com A
dig +short www.example.com A
dig +short example.com AAAA
If UFW is enabled, allow web traffic. The Nginx Full profile opens both 80 and 443. Make sure SSH stays allowed:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw status
Next, create or edit the server block at /etc/nginx/sites-available/example.com. A minimal HTTP-only block looks like this:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
}
Enable the site, test the configuration and reload Nginx:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
2. How do I install Certbot on Ubuntu?
The Certbot project recommends the snap package because it is always current and ships its own renewal timer. If you previously installed Certbot from apt, remove that version first so the two do not conflict:
sudo apt remove certbot python3-certbot-nginx
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
certbot --version
3. How do I get the certificate and enable HTTPS?
Run Certbot with the Nginx plugin and list every name you want covered:
sudo certbot --nginx -d example.com -d www.example.com
On the first run, Certbot asks for an email address for expiry notices and asks you to accept the terms of service. To run it without prompts, for example from a script, use:
sudo certbot --nginx -d example.com -d www.example.com \
--agree-tos -m admin@example.com --redirect --no-eff-email
Current Certbot versions add the HTTP-to-HTTPS redirect by default. Passing --redirect makes this explicit on any version.
What does Certbot change in the Nginx config?
Certbot edits your server block in place and marks every line it adds with # managed by Certbot. The result looks roughly like this:
server {
server_name example.com www.example.com;
root /var/www/example.com;
...
listen [::]:443 ssl ipv6only=on; # managed by Certbot
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
}
server {
if ($host = www.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 404; # managed by Certbot
}
Your certificate files are stored in /etc/letsencrypt/live/example.com/. The renewal settings are saved in /etc/letsencrypt/renewal/example.com.conf. Do not move or rename these files, because renewal depends on them.
4. How does automatic renewal work?
Let's Encrypt certificates are valid for 90 days. The snap package installs a systemd timer that runs twice a day. It renews any certificate that has 30 days or less remaining, then reloads Nginx. Confirm the timer is active:
systemctl list-timers | grep certbot
systemctl status snap.certbot.renew.timer
Then simulate a renewal against the Let's Encrypt staging server. This does not count against rate limits:
sudo certbot renew --dry-run
If the output ends with "Congratulations, all simulated renewals succeeded", automatic renewal is working.
5. Can I get a wildcard certificate with Certbot?
Yes, but a wildcard such as *.example.com can only be validated through DNS, not over port 80. You need a Certbot DNS plugin for your DNS provider so that renewal can create the required TXT record automatically. For example, with Cloudflare DNS:
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'
The credentials file must contain a Cloudflare API token, and you should restrict its permissions with chmod 600. The certonly command does not edit Nginx, so you add the ssl_certificate lines yourself. Avoid --manual for wildcards, because manually issued certificates cannot renew automatically.
6. Should I add HSTS and modern TLS settings?
The included options-ssl-nginx.conf file already limits connections to TLS 1.2 and 1.3 and uses strong ciphers. Do not repeat ssl_protocols or ssl_ciphers in the same server block, because Nginx will reject the duplicate directives.
Once HTTPS works for every subdomain you serve, you can add HSTS inside the 443 server block:
add_header Strict-Transport-Security "max-age=31536000" always;
max-age=300, and only add includeSubDomains once every subdomain has a valid certificate.Verify it works
curl -I http://example.com
curl -I https://example.com
sudo certbot certificates
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -dates -ext subjectAltName
Check each result against the list below, then open https://example.com in a private browser window and confirm the padlock shows a certificate issued by Let's Encrypt.
- The HTTP request returns
301 Moved Permanentlypointing tohttps://. - The HTTPS request returns
200, or your application's normal status code. - The subjectAltName list includes both
example.comandwww.example.com.
Common problems
- "Timeout during connect" or "Connection refused" during the challenge. Port 80 is blocked or DNS does not point here yet. Check
sudo ufw statusanddig +short example.com, and make sure no stale AAAA record exists. If DNS was changed recently, wait for propagation and retry. - "Could not automatically find a matching server block". No enabled server block has a matching
server_name. Add the domain toserver_name, runsudo nginx -t, reload Nginx and run Certbot again. - "Too many certificates already issued" or "too many failed authorizations". You hit a Let's Encrypt rate limit. The limits are 5 duplicate certificates per week, 50 certificates per registered domain per week, and 5 failed validations per hostname per hour. Wait for the limit to reset, and test fixes with
--dry-runor--stagingfirst. - The browser shows a certificate for the wrong domain. The request is landing on another server block, often the default one. Check
server_nameandsudo certbot certificates. To add names to an existing certificate, runsudo certbot --nginx --cert-name example.com -d example.com -d www.example.com -d shop.example.com. - Renewal fails after you edit the config. Common causes are a broken Nginx config, a removed port 80 block, a changed
server_name, or a CDN proxy blocking the challenge. Runsudo nginx -tandsudo certbot renew --dry-run, then read the details in/var/log/letsencrypt/letsencrypt.log.
Frequently asked questions
How long is a Let's Encrypt certificate valid?
Each certificate is valid for 90 days. Certbot's timer renews it automatically when 30 days or less remain, so you never need to renew by hand.
Do I need to restart Nginx after a renewal?
No. When a certificate was obtained with --nginx, Certbot reloads Nginx itself after each successful renewal.
Can I use Certbot on shared cPanel hosting?
No, and you do not need to. On shared hosting, AutoSSL issues free certificates automatically, and you can check them in cPanel under Security » SSL/TLS Status.
Need help? If validation keeps failing or you are unsure about your configuration, open a support ticket at https://cloudesta.com/submitticket.php with the full Certbot error output. You can also browse more guides in our knowledgebase.