Free SSL Certificate Setup Guide with Let's Encrypt

by David Park
Free SSL Certificate Setup Guide with Let's Encrypt

What You'll End Up With

This free SSL certificate setup guide walks you through issuing a trusted TLS certificate from Let's Encrypt, installing it on Nginx running on Ubuntu 24.04, and configuring automatic renewal. When you're done, your domain will serve HTTPS with a valid certificate, HTTP will redirect to HTTPS, and a systemd timer will renew the cert before it expires — no manual intervention required.

Prerequisites:

  • Ubuntu 24.04 server (root or sudo user)
  • Nginx installed and running (nginx -v returns a version)
  • A 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
  • No existing certificate on the domain

Step 1 — Update Packages and Install Certbot

Certbot is the official Let's Encrypt client. Install it and the Nginx plugin from the Ubuntu package repository.

1.1 Refresh the package index.

sudo apt update

1.2 Install Certbot and the Nginx plugin.

sudo apt install -y certbot python3-certbot-nginx

Expected output ends with:

Setting up python3-certbot-nginx (2.x.x) ...

1.3 Confirm the installed version.

certbot --version
certbot 2.x.x

Step 2 — Configure a Minimal Nginx Server Block

Certbot needs a valid server_name directive to locate and modify the correct Nginx configuration. If you don't already have a server block for your domain, create one now.

2.1 Create a new site configuration file.

sudo nano /etc/nginx/sites-available/example.com

2.2 Paste the following minimal block. Replace example.com with your actual domain.

server {
    listen 80;
    server_name example.com www.example.com;

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

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

2.3 Enable the site by creating a symlink.

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

2.4 Test the Nginx configuration for syntax errors.

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

2.5 Reload Nginx to apply the new server block.

sudo systemctl reload nginx

Step 3 — Open Firewall Ports

Let's Encrypt's HTTP-01 challenge requires port 80. HTTPS traffic uses port 443. Both must be reachable from the internet.

3.1 Allow HTTP and HTTPS through UFW.

sudo ufw allow 'Nginx Full'
Rule added
Rule added (v6)

3.2 If you previously had only port 80 open, remove the redundant rule.

sudo ufw delete allow 'Nginx HTTP'

3.3 Verify the firewall status.

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)

Step 4 — Issue the Free SSL Certificate

Run Certbot with the --nginx plugin. It will authenticate your domain via the HTTP-01 challenge, obtain the certificate from Let's Encrypt, and automatically modify your Nginx configuration to enable HTTPS.

4.1 Run Certbot for your domain. Replace example.com with your domain.

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

Certbot will prompt you for:

  1. An email address for renewal and security notices — enter a real address you monitor.
  2. Agreement to the Let's Encrypt Terms of Service — type Y.
  3. Whether to share your email with EFF — optional, type N to decline.

Expected output on success:

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-XX-XX.
These files will be updated when the certificate renews.

Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/sites-available/example.com
Successfully deployed certificate for www.example.com to /etc/nginx/sites-available/example.com
Congratulations! You have successfully enabled HTTPS on https://example.com and https://www.example.com

4.2 Inspect the Nginx config Certbot modified to confirm the SSL directives were added.

sudo cat /etc/nginx/sites-available/example.com

You should see listen 443 ssl;, ssl_certificate, and ssl_certificate_key lines, plus an HTTP-to-HTTPS redirect block.


Step 5 — Harden the SSL Configuration

Certbot's default SSL parameters are acceptable, but adding strong cipher settings and HTTP Strict Transport Security (HSTS) raises your security posture and improves SSL Labs grades.

5.1 Open the Nginx SSL parameters snippet Certbot created.

sudo nano /etc/letsencrypt/options-ssl-nginx.conf

Verify it contains at least:

ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

If TLSv1 or TLSv1.1 appear, remove them.

5.2 Add an HSTS header inside the server block that listens on port 443 in /etc/nginx/sites-available/example.com.

sudo nano /etc/nginx/sites-available/example.com

Add this line inside the server { listen 443 ssl; ... } block:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

5.3 Test and reload Nginx.

sudo nginx -t && sudo systemctl reload nginx

Step 6 — Verify Automatic Renewal

Let's Encrypt certificates expire after 90 days. Certbot installs a systemd timer that runs renewal checks twice daily. Confirm it is active and perform a dry run.

6.1 Check the Certbot 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: ...

6.2 Run a renewal dry run to confirm the process works end-to-end without issuing a new certificate.

sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

6.3 Check when the certificate actually expires.

sudo certbot certificates
Found the following certs:
  Certificate Name: example.com
    Domains: example.com www.example.com
    Expiry Date: 2025-XX-XX (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem

Verify It Works

Run these checks after completing all steps.

Browser check: Navigate to https://example.com. The padlock icon should appear with no certificate warnings.

HTTP redirect check: Confirm HTTP redirects to HTTPS.

curl -I http://example.com
HTTP/1.1 301 Moved Permanently
Location: https://example.com/

Certificate details via OpenSSL:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -dates
issuer=C=US, O=Let's Encrypt, CN=R11
notBefore=2025-XX-XX ...
notAfter=2025-XX-XX ...

SSL Labs grade (optional): Submit your domain at https://www.ssllabs.com/ssltest/. Expect an A rating with the hardening from Step 5.


Troubleshooting

Certbot fails with "Connection refused" or "Timeout during connect" Port 80 is blocked. Verify UFW allows Nginx Full (sudo ufw status) and that your hosting provider's external firewall or security group also allows port 80.

"No names were found in your configuration files" error Your Nginx server block is missing a server_name directive or the file is not symlinked into sites-enabled. Re-check Step 2.

"Too many certificates already issued for exact set of domains" Let's Encrypt enforces a limit of 5 duplicate certificates per week. Wait until the rate limit resets, or add --staging to test with a non-trusted certificate.

Nginx fails to reload after Certbot modifies config Run sudo nginx -t to identify the syntax error. Certbot sometimes adds duplicate ssl_certificate lines if the block was already partially configured. Remove duplicates manually.

Dry run succeeds but live renewal fails Check the Certbot log at /var/log/letsencrypt/letsencrypt.log. Common causes: DNS propagation lag after an IP change, or port 80 blocked between renewal attempts.

Certificate shows as expired in browser despite renewal Nginx may be serving a cached configuration. Run sudo systemctl reload nginx after any renewal. Certbot's deploy hook handles this automatically, but verify the hook exists:

cat /etc/letsencrypt/renewal-hooks/deploy/nginx

If missing, create it:

echo -e '#!/bin/bash\nsystemctl reload nginx' | sudo tee /etc/letsencrypt/renewal-hooks/deploy/nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx

Next Steps

With your free SSL certificate setup complete, consider these follow-on tasks:

  • Enable HTTP/2 — Add http2 to the listen 443 directive (listen 443 ssl http2;) for faster asset loading.
  • Set up multiple domains — Re-run certbot --nginx -d domain2.com for each additional domain; each gets its own certificate.
  • Monitor expiry — Add a cron job or use a monitoring tool to alert you if a certificate is fewer than 14 days from expiry, catching any renewal failures early.
  • Wildcard certificates — If you need *.example.com, use the DNS-01 challenge (certbot certonly --manual --preferred-challenges dns -d '*.example.com') and update DNS TXT records as prompted.