Knowledgebase

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.com and www.example.com pointing to your server's IPv4 address, for example 203.0.113.10.
  • Ports 80 and 443 open to the internet.
  • An Nginx server block whose server_name lists 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 an AAAA (IPv6) record exists, Let's Encrypt will try IPv6 first. That record must point to this server, and Nginx must listen on IPv6. Otherwise, delete the AAAA record before you continue.

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;
Browsers cache HSTS for the full max-age period, so a later HTTPS mistake will lock visitors out. Start with a short value such as 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 Permanently pointing to https://.
  • The HTTPS request returns 200, or your application's normal status code.
  • The subjectAltName list includes both example.com and www.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 status and dig +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 to server_name, run sudo 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-run or --staging first.
  • The browser shows a certificate for the wrong domain. The request is landing on another server block, often the default one. Check server_name and sudo certbot certificates. To add names to an existing certificate, run sudo 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. Run sudo nginx -t and sudo 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.

Was this answer helpful?

0 Users Found This Useful

Powered by WHMCompleteSolution