Back to articles
encryption
8 min readFebruary 25, 2026

Let's Encrypt SSL Setup: Free HTTPS, But Read the Fine Print

Setting up Let's Encrypt takes 10 minutes. Keeping it working takes understanding auto-renewal, Nginx config, and the gotchas that hit you at 2am when your cert silently expires.

Let's Encrypt SSL Setup: Free HTTPS, But Read the Fine Print

Let's Encrypt SSL Setup: Free HTTPS, But Read the Fine Print

I got a panicked message at 2am from a client: "The site is showing a security warning and customers can't check out." The Let's Encrypt certificate had expired. Auto-renewal was "set up." The cron job was there. Certbot was installed. Everything looked correct — except the renewal had been silently failing for weeks because an Nginx config change had broken the ACME challenge path. No alerts, no errors in the obvious places. Just a certificate that quietly expired.

Setting up Let's Encrypt is genuinely easy. Keeping it running reliably is where people get tripped up. This guide covers both.

Why This Matters (Even Though It's "Basic")

Running HTTP in 2026 isn't just a security issue — it's a functionality issue. Modern browsers flag HTTP sites with visible warnings. Chrome DevTools won't let you use certain APIs (geolocation, service workers, camera) without HTTPS. Google's search ranking factors HTTPS. And most importantly, without TLS, every piece of data between your users and your server — passwords, tokens, personal information — travels in plain text across every router between them.

Let's Encrypt removed the last excuse. Free certificates, automated issuance, 90-day validity with automatic renewal. If you're not using HTTPS, it's a choice, not a limitation.

Step 1: Install Certbot

bash
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
certbot --version

Gotcha: On newer Ubuntu versions (22.04+), the

certbot
package from apt can be outdated. The Certbot team recommends using the snap package instead:
sudo snap install --classic certbot
. The snap version auto-updates, which means you get security fixes without manual intervention. Whichever you choose, don't install both — they'll conflict.

Step 2: Get Your Certificate

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

Certbot does a lot in this one command:

  1. Verifies you control the domain (via an HTTP challenge or DNS challenge)
  2. Generates a private key and CSR
  3. Downloads the signed certificate from Let's Encrypt
  4. Configures your Nginx server block to use the certificate
  5. Sets up HTTP-to-HTTPS redirect
  6. Installs a systemd timer (or cron job) for automatic renewal

That's genuinely impressive for a single command. But "automatically configured Nginx" means Certbot modified your Nginx config, and the changes it makes are sometimes not what you'd write by hand. Always review what it changed:

bash
sudo nginx -t  # Test config syntax
sudo diff /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-available/yourdomain.com.bak

Step 3: Harden Your SSL Config

Certbot's default config is functional but not optimized. Here's what a hardened config looks like — and if you've read our TLS 1.3 guide, some of this will look familiar:

nginx
server {
    listen 443 ssl http2;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;

    # HSTS — force HTTPS for 2 years
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # OCSP Stapling — speeds up certificate verification
    ssl_stapling on;
    ssl_stapling_verify on;
}

A few notes on what these do:

HSTS tells browsers to always use HTTPS for your domain, even if the user types

http://
. The
preload
directive submits your domain to browser preload lists, which means browsers will use HTTPS even on the very first visit — before they've seen your HSTS header. This is powerful but also dangerous: if you ever need to go back to HTTP (unlikely but possible), the preload list is slow to update. Start with a short
max-age
(like 300 seconds) to test, then increase once you're confident.

OCSP Stapling speeds up the TLS handshake by having your server fetch and cache the certificate's revocation status, rather than making the client check with the certificate authority on every connection. It's a free performance win.

Step 4: Verify Auto-Renewal Actually Works

This is the step most tutorials include as an afterthought. It's the most important one.

bash
# Test renewal without actually renewing
sudo certbot renew --dry-run
# Output: Congratulations, all renewals succeeded.

If this succeeds, great. But here's what the dry run doesn't test:

  • Nginx reload after renewal: The certificate gets renewed, but Nginx is still serving the old cert from memory until it's reloaded. Certbot usually handles this with a
    --deploy-hook
    , but verify it's configured:
bash
# Check your renewal config
cat /etc/letsencrypt/renewal/yourdomain.com.conf
# Should contain: renew_hook = systemctl reload nginx
  • Permissions: If your renewal cron job runs as a different user than the one that originally set up Certbot, it might not have permission to write the new certificates or reload Nginx. I've seen this happen on servers where someone set up Certbot as root but then changed the cron to run as a less privileged user.

  • Port 80 accessibility: Let's Encrypt's HTTP-01 challenge needs to reach port 80 on your server. If a firewall rule changes, or if you're behind a load balancer that doesn't forward port 80, renewals will fail. DNS-01 challenges avoid this issue but require API access to your DNS provider.

My recommendation: Don't just run the dry run once. Set up actual monitoring for certificate expiry. A simple cron job that checks your certificate's expiry date and alerts you if it's less than 14 days away will save you from the 2am panic:

bash
# Add to crontab — checks daily, alerts if cert expires within 14 days
0 8 * * * echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates | grep notAfter

Testing Your SSL Grade

After everything is configured:

  • SSL Labs: https://ssllabs.com/ssltest/ — aim for A+
  • Security Headers: https://securityheaders.com — tests HTTP security headers beyond just TLS

With the hardened config above, you should get an A+ from SSL Labs. If you don't, the report will tell you exactly what to fix.

Wildcard Certificates

If you run multiple subdomains (

app.example.com
,
api.example.com
,
admin.example.com
), wildcard certificates save you from managing individual certs for each:

bash
sudo certbot certonly --manual --preferred-challenges dns -d "*.yourdomain.com" -d "yourdomain.com"

Wildcard certs require DNS-01 validation, which means you'll need to add a TXT record to your DNS. Certbot has plugins for popular DNS providers (Cloudflare, Route53, DigitalOcean) that automate this.

Gotcha: Wildcard certs cover

*.example.com
but NOT
example.com
itself — that's why you need the
-d "yourdomain.com"
in addition to the wildcard. This trips people up constantly.

The Realistic Summary

Let's Encrypt is one of the best things that's happened to web security. Free, automated, and widely supported. But "automated" doesn't mean "maintenance-free." Set it up, harden the config, verify auto-renewal works end-to-end (not just the dry run), monitor your certificate expiry, and review your Nginx config after each Certbot renewal to make sure nothing got overwritten. That's 30 minutes of setup for years of trouble-free HTTPS. The 2am phone call is entirely preventable.

Discussion

0 comments

Share your thoughts

No comments yet. Be the first to share your thoughts!