How to Set Up Automated SSL Certificates with Let’s Encrypt and Certbot: Never See Your Padlock Turn Red Again

Introduction

You’ve just spun up a beautiful self-hosted app — maybe a MediaWiki, a Home Assistant instance, or a personal dashboard. You open it in your browser and there it is: that angry red “Not Secure” warning staring back at you. It’s like buying a gorgeous new front door and leaving it wide open.

The good news? Making that padlock turn green is genuinely easy. Let’s Encrypt provides free SSL/TLS certificates, and Certbot automates the entire process — from issuance to renewal. In about 10 minutes, you’ll have HTTPS running on all your self-hosted services, with zero effort going forward.

In this tutorial, I’ll walk you through setting up Certbot on an Ubuntu/Debian server, obtaining certificates for multiple subdomains, configuring automatic renewal, and integrating it with Nginx so your sites are secure out of the box.

Prerequisites

  • A Linux server (Ubuntu 20.04+ or Debian 11+ recommended) with sudo access
  • Port 80 and 443 open in your firewall (Let’s Encrypt needs these for validation)
  • A registered domain name pointing to your server via A/AAAA records
  • Nginx or Apache installed (we’ll cover both briefly)
  • Basic comfort with the terminal

Why Let’s Encrypt and not a self-signed certificate?

Self-signed certs work for personal testing, but browsers scream at you when users visit them. Let’s Encrypt certificates are free, trusted by every modern browser, and — here’s the kicker — they auto-renew. Once set up, you literally never think about them again. That’s the dream, right?

Step 1: Install Certbot

Certbot is packaged in most modern distributions. Let’s keep it simple:

# Ubuntu / Debian
sudo apt update
sudo apt install certbot

# If using Nginx
sudo apt install python3-certbot-nginx

# If using Apache
sudo apt install python3-certbot-apache

For Nginx users, the python3-certbot-nginx plugin is wonderful — it not only obtains certificates but automatically rewrites your Nginx config to use HTTPS. Same story for Apache users.

Step 2: Verify Your DNS Is Ready

Before we go any further, let’s make sure Certbot can actually reach your domain:

# Replace with your domain
dig +short admin.example.com
# Should show your server's public IP

# Test that port 80 is reachable
curl -I http://example.com | head -5

If your domain doesn’t resolve to your server yet, go check your DNS records. This is the number one cause of “Certbot worked yesterday but fails today” — DNS records that were never actually propagated.

Step 3: Obtain a Certificate

Here’s where the magic happens. Run Certbot with the appropriate plugin:

For Nginx Users

sudo certbot --nginx -d example.com -d admin.example.com -d api.example.com

For Apache Users

sudo certbot --apache -d example.com -d admin.example.com -d api.example.com

For Standalone (no web server running)

sudo certbot certonly --standalone -d example.com -d www.example.com

Certbot will ask for your email (for renewal notifications) and prompt you to agree to the terms. It also asks if you want to redirect HTTP to HTTPS — say yes. Always. Your future self will thank you.

Step 4: Understand What Certbot Just Did

After success, you’ll see output telling you where your certificates live. All Let’s Encrypt certificates are stored in:

/etc/letsencrypt/live/example.com/
├── cert.pem        # Your certificate
├── chain.pem       # Intermediate certificate
├── fullchain.pem   # cert + chain (what Nginx uses)
└── privkey.pem     # Your private key (keep this secret!)

The Nginx/Apache plugin automatically updates your web server config to point to these files. If you’re using standalone mode, you’ll need to configure this manually — but don’t worry, the Certbot docs make it clear.

Step 5: Set Up Automatic Renewal

Let’s Encrypt certificates are valid for only 90 days (by design — shorter lifetimes mean less damage from compromised certs). But don’t panic — Certbot installs a systemd timer that handles renewal automatically.

Test the renewal process:

sudo certbot renew --dry-run

If you see “Congratulations, all renewal attempts succeeded,” you’re golden. The dry run simulates renewal without actually replacing anything.

Verify the timer is active:

sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot

You should see the timer running and scheduled for twice-daily checks. Certbot only renews certificates that are within 30 days of expiration, so most checks are no-ops.

Set up a post-renewal hook (optional but recommended)

If you have services that need to reload after certificate renewal (like Nginx or a Docker container), create a hook:

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

Step 6: Configure Nginx for Optimal HTTPS

If you manually configure certificates (without the Nginx plugin), here’s a solid HTTPS server block:

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Strong SSL settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # HSTS (uncomment once you're confident HTTPS works)
    # add_header Strict-Transport-Security "max-age=63072000" always;

    # Your app configuration...
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

Pro tip: uncomment the HSTS header after you’ve confirmed everything works. Once browsers see that header, they’ll refuse to connect over HTTP for the specified duration — which is great for security but a nightmare if you break your HTTPS config.

Common Pitfalls and Troubleshooting

❌ “Connection refused” or “Timeout” during challenge

Your firewall is blocking port 80. Let’s Encrypt must verify your domain over plain HTTP. Even if you want HTTPS only, you need port 80 open for certificate issuance and renewal:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload

❌ Rate limits

Let’s Encrypt has strict rate limits: 50 certificates per domain per week for production certs. During testing, you might hit this. Use staging mode to avoid limits:

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

Staging certs won’t be trusted by browsers (they’ll still show “Not Secure”), but they let you test the workflow without burning through your production quota.

❌ “No viable Virtual Host” errors

Your Nginx config probably doesn’t have a server_name matching your domain. Either fix the config temporarily, or use standalone mode with --standalone:

sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com
sudo systemctl start nginx

❌ Timer not running after installation

On some minimal distributions, the systemd timer isn’t automatically enabled:

sudo systemctl enable --now certbot.timer
sudo systemctl status certbot.timer

Bonus: Wildcard Certificates

If you have lots of subservices on different subdomains, wildcard certificates save management overhead. These require DNS-01 validation (proving you control DNS, not just the server):

# If you use Cloudflare DNS (via certbot-dns-cloudflare plugin)
sudo apt install python3-certbot-dns-cloudflare
sudo mkdir -p /etc/letsencrypt

# Create Cloudflare API credentials file (protect this!)
cat | sudo tee /etc/letsencrypt/cloudflare.ini <<'CREDEOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
CREDEOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

# Request wildcard certificate
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d example.com -d *.example.com

Wildcard certs cover anything.example.com — new subdomains automatically get HTTPS protection without any certificate management.

Conclusion

There’s no good reason anymore to run a web service without HTTPS. Not when it’s free, automated, and takes about 10 minutes to set up. The combination of Let’s Encrypt and Certbot has genuinely democratized encryption — from an expensive, arcane process to something any hobbyist can do over coffee.

The next time you spin up a new service, don’t even dare to open that padlock-less browser tab. Run Certbot first. Your users (and your security team, if you have one) will thank you.

And if you’re reading this and still running HTTP-only services from 2016 — I see you. No judgment. But also, please fix it. It really does only take 10 minutes.

Quick Reference

Command What it does
certbot --nginx -d domain.com Obtain and install certificate for Nginx
certbot renew --dry-run Test renewal without applying changes
certbot certificates List all certificates on the server
certbot delete --cert-name domain.com Remove a certificate
certbot renew --force-renewal Renew immediately (bypasses 30-day check)

Have questions or run into issues? Drop a comment below — I read every single one.

Leave a Reply

Your email address will not be published. Required fields are marked *