Install Let's Encrypt on Nginx Ubuntu: free HTTPS in 10 minutes

Learn how to install Let's Encrypt on Nginx Ubuntu 24.04 with Certbot, automatic renewal and A+ rated HTTPS validated on SSL Labs.

SSL/TLS certificates stopped being a luxury a long time ago — Chrome flags any HTTP site as “Not secure” since 2018, mobile browsers won’t even let you POST without TLS, and Google penalizes HTTP in search ranking. Let’s Encrypt solved the cost problem (free certificates) and the operational one (automatic renewal), but correct configuration on Nginx still trips many people up because of DNS details, command ordering and outdated cipher suites.

This tutorial shows how to install and configure Let’s Encrypt on Nginx running Ubuntu 24.04 LTS, from scratch to an A+ grade on SSL Labs. We cover the parts usually skipped: pre-validating DNS, HTTP→HTTPS redirect without loops, renewal tested with --dry-run, and a reload hook so Nginx picks up the new cert with no manual intervention.

Estimated time: 10 to 15 minutes if DNS is already propagated. If you just created the A record, wait for propagation to complete before starting — otherwise the HTTP-01 challenge fails and you burn rate-limit quota for nothing.

Prerequisites

What you need before starting

Ubuntu 24.04 LTS server with sudo access, Nginx installed and running, a registered domain pointing to the server’s public IP (A record in DNS), ports 80 and 443 open on the firewall and DNS propagation complete. Without this, Certbot fails the HTTP-01 challenge.

System Ubuntu 24.04 LTS
Web server Nginx 1.24+
Required ports 80 and 443 (TCP)
Disk space ~50 MB for certbot

Before continuing, validate that your domain resolves to the correct IP. Run dig +short yourdomain.com on your local machine — the result must be the server’s public IP. If it comes back empty or with a different IP, the problem is in DNS and no step in this tutorial will work until you fix it there.

Installing Certbot

Certbot is the official Let’s Encrypt client and the recommended way to issue and renew certificates. Ubuntu 24.04 ships a version in apt, but the Electronic Frontier Foundation recommends installing via snap to ensure automatic updates — the client evolves quickly and old versions can break with changes in the ACME protocol.

01

Update the package index and install snapd if you don’t have it yet:

sudo apt update
sudo apt install -y snapd

On Ubuntu 24.04 LTS, snapd ships pre-installed on most server images, but on minimal VPS it may be missing. The command is idempotent — there’s no harm in running it again.

02

Remove any old Certbot version installed via apt to avoid conflicts:

sudo apt remove -y certbot python3-certbot-nginx

If apt replies “Package ‘certbot’ is not installed”, ignore it — it just means there was no old version to remove.

03

Install Certbot via snap and create the symlink so the command is available in PATH:

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

The --classic flag is required because Certbot needs to access directories outside snap’s default confinement (specifically /etc/letsencrypt and /etc/nginx).

04

Confirm the installed version:

certbot --version

You should see something like certbot 2.x.x. Versions 1.x still work but are deprecated — if 1.x shows up, redo the previous steps.

Configuring Nginx for the HTTP-01 challenge

Let’s Encrypt validates domain ownership through a file that must be served at http://yourdomain.com/.well-known/acme-challenge/. Certbot’s --nginx plugin edits your vhost automatically, but to do so it needs a valid server block listening on port 80 with the correct server_name.

05

Create a basic vhost at /etc/nginx/sites-available/yourdomain.com:

server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com www.yourdomain.com;

    root /var/www/yourdomain.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Replace yourdomain.com with your real domain in every occurrence. If you are hosting a dynamic application (Node, PHP-FPM, Python), the location / changes — but keep the server_name exact.

06

Enable the vhost by creating the symlink in sites-enabled and test the syntax:

sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

nginx -t reports configuration errors without applying them — always run it before reloading. If you get an error that the symlink already exists (when repeating the tutorial), remove it with sudo rm /etc/nginx/sites-enabled/yourdomain.com before recreating.

Watch out for default_server

If you have a default vhost on port 80 (usually /etc/nginx/sites-enabled/default), make sure it does NOT capture requests for your domain. Nginx picks vhost by server_name, but if none matches it falls back to default_server. Remove the default or set server_name _ to a value that never matches.

Issuing the certificate

With Nginx serving the domain on port 80, Certbot can complete the challenge. The command below issues the certificate, installs the SSL directives in the vhost and automatically sets up the HTTP→HTTPS redirect.

07

Run Certbot with the nginx plugin:

sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Certbot will ask:

  • Email for expiration notifications (use an email you actually read — it’s the alert channel if renewal fails)
  • Acceptance of Let’s Encrypt terms (answer Y)
  • Whether to share email with the EFF (optional, answer N if you prefer privacy)
  • Whether to redirect HTTP traffic to HTTPS (answer 2 to enable — recommended)

After a few seconds, the cert is issued and Nginx is already serving HTTPS.

08

Verify the certificate was installed by opening https://yourdomain.com in the browser. You should see the green padlock with no warnings. If the browser complains about an invalid cert, check:

  • The server_name in the vhost matches the domain accessed exactly
  • You included www. in Certbot if accessing via www
  • DNS has already propagated (check with dig)
Staging mode for testing

Before issuing a real cert, run with --staging to validate the config without burning rate limit. The staging cert isn’t trusted by the browser (you’ll get a warning) but confirms the challenge works. Then remove --staging and run again to issue the production cert.

Configuring automatic renewal

Let’s Encrypt certificates expire in 90 days. The Certbot snap already creates a systemd timer that tries to renew twice a day — but it’s prudent to validate it’s working and add the Nginx reload hook.

09

Confirm Certbot’s systemd timer is active:

sudo systemctl list-timers | grep certbot

You should see snap.certbot.renew.timer listed with the next run scheduled. If it doesn’t show up, enable it manually:

sudo systemctl enable --now snap.certbot.renew.timer
10

Configure the deploy hook to reload Nginx after every successful renewal:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
echo '#!/bin/bash
systemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Without this hook, the renewed cert sits on disk but Nginx keeps serving the old one until the next manual restart. With the hook, the operation is completely hands-off.

11

Test renewal in dry-run mode to validate everything works without touching the real cert:

sudo certbot renew --dry-run

Expected output: Congratulations, all simulated renewals succeeded. If it errors out, read the message carefully — it’s usually a firewall closed on port 80 or broken DNS.

Final verification

With everything configured, validate your HTTPS security level on SSL Labs. Visit https://www.ssllabs.com/ssltest/analyze.html?d=yourdomain.com and wait 1-2 minutes for the analysis to complete. The expected grade is A. To reach A+, edit your vhost and add the security headers to the server block (already there, created by Certbot):

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;

Reload Nginx with sudo systemctl reload nginx and rerun the SSL Labs test.

HSTS with preload is a permanent decision

The preload directive in HSTS adds your domain to the Chrome list embedded in the browser binary. Reverting takes months. Only enable if you are sure that EVERY subdomain of your site will serve HTTPS forever. In testing environments, omit preload and set max-age to a smaller value (e.g. 300 seconds).

Troubleshooting

Error “Connection refused” on the HTTP-01 challenge

This means Certbot couldn’t reach port 80 of your server. Check with sudo ufw status whether the firewall allows the port, and check whether another service (Apache, another Nginx) is conflicting. Kill the conflict with sudo ss -tlnp | grep :80.

Error “DNS problem: NXDOMAIN looking up A”

DNS hasn’t propagated yet or the A record doesn’t exist. Confirm locally with dig +short yourdomain.com @8.8.8.8 (uses Google’s public DNS). If it returns empty, go to your registrar’s panel and create the A record pointing to the server’s IP.

Cert renewed but browser still shows the old one

Confirm the reload hook is executable and working. Force renewal ignoring the cooldown with sudo certbot renew --force-renewal and check the output. If the hook doesn’t run, the problem is in /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh — check permissions with ls -la.

Next steps

With HTTPS running, consider the next hardening steps: configure OCSP Stapling to reduce revocation check latency, add a Content Security Policy to mitigate XSS, and enable HTTP/2 or HTTP/3 on Nginx (already enabled by default on the 1.25+ line). For serious production, monitor certificate expiration with Uptime Robot or similar — an alert independent of the renewal cron.

If you are putting a critical application into production, a Hostini VPS delivers automatic snapshots, native IPv6 and a network firewall already configured to shorten the path between apt install nginx and the site live with an A+ grade.

Frequently asked questions

Can I issue a Let's Encrypt certificate without a domain?

No. Let's Encrypt validates ownership through HTTP-01 or DNS-01 challenges, and both require a registered name pointing to the server. For local development, use mkcert or an internal CA; for public IPs without a domain, no free public authority will issue a certificate.

What's the difference between Certbot --nginx and --webroot?

The --nginx plugin automatically edits your nginx.conf to insert the ssl_certificate directives and perform the challenge. The --webroot just drops the validation file into an existing directory and leaves the vhost configuration to you. Use --nginx on simple hosts and --webroot when your Nginx is already in production with a custom config you don't want Certbot to touch.

How many certificates can I issue per domain?

Let's Encrypt enforces a rate limit of 50 certificates per registered domain per week and 5 duplicate certificates per week. If you are testing, use the staging environment (--staging), which has much higher limits and issues certs not trusted by browsers — perfect for validating the config without burning quota.

Why did my certificate renew but Nginx is still serving the old one?

Certbot updates the files under /etc/letsencrypt/live/ but Nginx keeps the old cert in memory until it receives a reload signal. The deploy hook --deploy-hook 'systemctl reload nginx' solves this. Without it, you only see the new cert after a manual reload or service restart.

Is it safe to open port 80 just for Certbot and close it afterwards?

Not recommended. Automatic renewal every 60 days needs port 80 to complete the HTTP-01 challenge. Keep port 80 open but configure a permanent 301 redirect to 443 — this serves Certbot and forces all traffic to HTTPS without exposing content in clear text.

Can I use Let's Encrypt in critical production environments?

Yes. Let's Encrypt issues more than 350 million active certificates for sites like Reddit, Shopify and part of Cloudflare's CDN. The catch is operational: you must ensure automatic renewal works (test with --dry-run) and monitor expiration with alerts, because an expired certificate silently breaks the site from the visitor's point of view.

Topics:
Next steps Ryzen cloud with NVMe storage and always-on DDoS protection.Go live on a Hostini VPS →
Was this tutorial helpful?
Chat on WhatsApp