This guide walks you through obtaining and installing a free TLS certificate from Let's Encrypt using Certbot on Ubuntu 24.04, with Nginx as the web server. By the end you will have HTTPS enforced on your domain, auto-renewal configured via a systemd timer, and a verified A-grade TLS setup — all at zero certificate cost.
Prerequisites
- Ubuntu 24.04 LTS server (root or sudo access)
- Nginx installed and running (
nginx -vreturns a version) - A registered domain name (e.g.,
example.com) with an A record pointing to your server's public IP - Port 80 and 443 open in your firewall (
ufw allow 'Nginx Full') - Python 3.12 available (default on Ubuntu 24.04)
Step 1 — Update the Package Index
Run the package update so Certbot installs from current metadata.
sudo apt update
Expected output ends with a line similar to:
Reading package lists... Done
Step 2 — Install Certbot and the Nginx Plugin
Install Certbot and its Nginx plugin from the Ubuntu 24.04 repository. The plugin edits your Nginx config automatically.
sudo apt install -y certbot python3-certbot-nginx
Verify the installation:
certbot --version
certbot 2.9.0
(Your version may be newer; any 2.x release is fine.)
Step 3 — Open Firewall Ports
Allow both HTTP and HTTPS through UFW. Certbot's HTTP-01 challenge requires port 80, and your live site needs port 443.
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
Confirm the rules:
sudo ufw status
Status: active
To Action From
-- ------ ----
Nginx Full ALLOW Anywhere
Nginx Full (v6) ALLOW Anywhere (v6)
Step 4 — Configure a Minimal Nginx Server Block
Certbot needs an existing server block for your domain before it can edit it. Create one if you have not already.
sudo nano /etc/nginx/sites-available/example.com
Paste the following, replacing example.com with your actual domain:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Enable the site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Expected output from nginx -t:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Step 5 — Obtain and Install the Free SSL Certificate
Run Certbot with the --nginx plugin. Pass both the bare domain and the www subdomain with -d flags. Replace you@example.com with a real address — Let's Encrypt sends expiry warnings there.
sudo certbot --nginx -d example.com -d www.example.com --email you@example.com --agree-tos --no-eff-email
Certbot will:
- Verify domain ownership via HTTP-01 challenge.
- Download the certificate chain to
/etc/letsencrypt/live/example.com/. - Rewrite your Nginx server block to add TLS directives and an HTTP→HTTPS redirect.
Expected final lines:
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
This certificate expires on 2025-10-01.
Deploying certificate to VirtualHost /etc/nginx/sites-available/example.com
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-available/example.com
Step 6 — Inspect the Updated Nginx Config
Certbot rewrites the server block. Review what it produced to understand the final state.
cat /etc/nginx/sites-available/example.com
You should see two server blocks: one on port 80 that issues a 301 redirect, and one on port 443 with ssl_certificate and ssl_certificate_key directives pointing to /etc/letsencrypt/live/example.com/.
Reload Nginx to apply any remaining changes:
sudo systemctl reload nginx
Step 7 — Verify Auto-Renewal Is Active
Let's Encrypt certificates expire after 90 days. Certbot installs a systemd timer that renews certificates automatically when they are within 30 days of expiry.
Check the timer status:
sudo systemctl status certbot.timer
● certbot.timer - Run certbot twice daily
Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; preset: enabled)
Active: active (waiting) since ...
Trigger: ...
Confirm the timer is enabled and active (waiting). Run a dry-run renewal to verify the full renewal path works without modifying anything:
sudo certbot renew --dry-run
Expected output:
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
Verify It Works
Run three checks to confirm your SSL certificate free setup is fully operational.
1. HTTPS response check:
curl -I https://example.com
HTTP/2 200
server: nginx/1.24.0 (Ubuntu)
content-type: text/html
...
2. HTTP redirect check:
curl -I http://example.com
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
3. Certificate expiry check:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates
notBefore=Jul 1 00:00:00 2025 GMT
notAfter=Sep 29 00:00:00 2025 GMT
The notAfter date should be roughly 90 days from today, confirming a valid Let's Encrypt certificate is in place.
Troubleshooting
Certbot fails with "Connection refused" or "Timeout during connect"
Port 80 is not reachable. Verify UFW rules (sudo ufw status), check your cloud provider's security group, and confirm Nginx is running (sudo systemctl status nginx).
"No virtual host found" error
Certbot cannot locate a server block for your domain. Confirm the symlink exists in /etc/nginx/sites-enabled/ and nginx -t passes before re-running Certbot.
DNS propagation not complete If your A record was created recently, the HTTP-01 challenge may fail. Check propagation with:
dig +short example.com
The output must match your server IP before Certbot can succeed.
certbot renew --dry-run fails after initial setup
Check the Certbot log for details:
sudo cat /var/log/letsencrypt/letsencrypt.log | tail -50
Common causes: Nginx is stopped, port 80 is blocked, or the domain's DNS changed.
Certificate is issued but Nginx returns a 404
Your root path (/var/www/example.com/html) does not exist or is empty. Create it and add an index.html:
sudo mkdir -p /var/www/example.com/html
echo '<h1>OK</h1>' | sudo tee /var/www/example.com/html/index.html
Certbot installed but certbot command not found
The binary is at /usr/bin/certbot. Add it to your PATH or call it with the full path:
/usr/bin/certbot --version
Next Steps
With your free SSL certificate running and auto-renewal confirmed, consider these hardening steps:
- Strengthen TLS protocols. Add
ssl_protocols TLSv1.2 TLSv1.3;andssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;to your server block to drop TLS 1.0/1.1. - Enable HSTS. Add
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;inside the port 443 block to instruct browsers to always use HTTPS. - Test your grade. Run your domain through SSL Labs' server test (
ssllabs.com/ssltest/) to confirm an A or A+ rating. - Wildcard certificates. If you run multiple subdomains, Certbot supports wildcard certs via DNS-01 challenge. That requires DNS API credentials and is covered in a separate guide.
- Monitor expiry. Even with auto-renewal, add an external monitor (UptimeRobot's SSL check is free) so you get alerted if renewal silently fails.
At $0/year for the certificate and roughly 15 minutes of setup time, Let's Encrypt is the only rational choice for indie projects running on a budget.