Introduction
There was a time when SSL/TLS certificates cost money and felt like luxury items for homelab servers. Those days are over. Let’s Encrypt changed the game — free, automated, trusted certificates for any domain you own. Combined with Certbot, the EFF’s official client, you can go from HTTP to HTTPS in about five minutes.
In this tutorial, I’ll walk you through obtaining and automatically renewing Let’s Encrypt certificates using Certbot, covering both the standalone method (when you don’t have a web server yet) and the Nginx plugin method (when you do). By the end, every service you run — your dashboard, your wiki, your media server — will have that satisfying padlock in the browser bar.
Prerequisites
- A Linux server (Debian/Ubuntu/RHEL — I’ll note differences where needed)
- A domain name you control (subdomains like
wiki.example.comanddash.example.comwork perfectly) - DNS A/AAAA records pointing your domain(s) to your server’s public IP
- Ports 80 and 443 open in your firewall (port 80 is used for HTTP-01 challenges)
- Root or sudo access
Important note: Let’s Encrypt certificates are only issued for domain names, not bare IP addresses. If you’re running a pure-home-lab with no external domain, consider using a dynamic DNS service or a custom domain with Cloudflare — but that’s a topic for another post.
Installing Certbot
Certbot is packaged for most distributions, but I recommend using snap to get the latest version. Distribution packages can lag behind security updates.
Method 1: Snap (Recommended)
# Install snapd if you don't have it
sudo apt update && sudo apt install snapd -y
sudo snap install core && sudo snap refresh core
# Install Certbot via snap
sudo snap install --classic certbot
# Make sure the certbot command is in your PATH
ln -s /snap/bin/certbot /usr/bin/certbot
Method 2: Apt (Quick and simple)
sudo apt update
sudo apt install certbot -y
If you’ll be using the Nginx plugin:
sudo apt install certbot python3-certbot-nginx -y
Or with snap, the plugin is included in the main certbot snap — no extra install needed.
Getting Your First Certificate
Scenario A: You already have Nginx running
The Nginx plugin automates everything — it reads your server blocks, identifies domain names, obtains certificates, and configures Nginx to use them.
sudo certbot --nginx -d example.com -d www.example.com
Certbot will ask for:
- Your email (for expiry notifications — important!)
- Whether you accept the Terms of Service
- Whether you’d like to share your email with the EFF
- Whether to redirect HTTP to HTTPS (say yes — always yes)
After it runs, your Nginx config will have been modified to include the SSL certificate paths and a redirect from HTTP to HTTPS.
Scenario B: No web server yet (Standalone mode)
If you’re setting up a new server and just need certificates provisionally, use standalone mode. Certbot spins up a temporary web server to handle the ACME challenge.
# Make sure nothing is running on port 80 first
sudo certbot certonly --standalone -d example.com
The certificates are saved to /etc/letsencrypt/live/example.com/. The key files:
fullchain.pem— the certificate chain (use this for most servers)privkey.pem— the private key (keep this safe!)
Using Certificates with Non-Nginx Services
Once you have certificates on disk, you can point any service to them. Here are common patterns:
Docker containers
Mount the certificate directory as a read-only volume:
docker run -d \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
-p 8443:8443 \
your-app \
--cert=/etc/letsencrypt/live/example.com/fullchain.pem \
--key=/etc/letsencrypt/live/example.com/privkey.pem
Node.js / Python apps
// Node.js
const https = require('https');
const fs = require('fs');
https.createServer({
cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem')
}, app).listen(443);
One catch: your app process needs read access to the certificate files (they’re readable only by root by default). Either run your app as root (not ideal) or add a ssl-cert group permission strategy, or copy the files (with correct permissions) into a location your app can read after renewal.
Automatic Renewal
Let’s Encrypt certificates are valid for 90 days — intentionally short so compromised keys don’t cause long-term damage. This means you must automate renewal.
If you installed via snap or apt, a systemd timer or cron job is set up automatically. Verify it:
# Check the timer exists
sudo systemctl list-timers | grep certbot
# Dry run — test renewal without actually renewing
sudo certbot renew --dry-run
The dry run is your best friend. If it succeeds silently, you’re golden. If it fails, it’ll tell you exactly why.
Custom renewal hooks
What happens after a certificate renews? Your services might need to be restarted to pick up the new files. Use a deploy hook:
sudo nano /etc/letsencrypt/renewal-hooks/post/reload-services.sh
#!/bin/bash
# Reload Nginx to pick up new certs
systemctl reload nginx
# Restart Docker container if you're using bind mounts
docker restart my-app
# Or copy certs to another location with proper permissions
cp /etc/letsencrypt/live/example.com/fullchain.pem /opt/my-app/certs/
cp /etc/letsencrypt/live/example.com/privkey.pem /opt/my-app/certs/
chown myuser:myuser /opt/my-app/certs/*.pem
chmod +x /etc/letsencrypt/renewal-hooks/post/reload-services.sh
Common Pitfalls and Troubleshooting
Rate limits
Let’s Encrypt has rate limits. The most relevant ones:
- 5 duplicate certificates per week per set of domains
- 50 certificates per domain per week
- 5 failed validations per hour (this one gets people)
When developing or debugging, always use Let’s Encrypt’s staging environment to avoid hitting rate limits:
sudo certbot --staging --nginx -d example.com
DNS issues
If validation fails with “DNS problem: NXDOMAIN” or similar:
- Verify your DNS records:
dig +short example.com A - Propagation takes time — wait 5-30 minutes after creating new DNS records
- Check that your server’s firewall allows inbound on port 443 (and 80 for challenges)
- Make sure your Nginx
server_namematches exactly what you passed to Certbot
Certificate renewal failures
The most common renewal failure is a changed server configuration. If you moved Nginx off port 80 or deleted a server block, Certbot can’t complete the HTTP-01 challenge. Solutions:
- Use DNS-01 challenges instead (requires a DNS provider API plugin)
- Use webroot mode:
certbot renew --webroot -w /var/www/html - Ensure something is listening on port 80 to handle
.well-known/acme-challenge/
Going Further
Once you have the basics working, consider exploring:
- Wildcard certificates — covers all subdomains with one certificate. Requires DNS-01 validation.
- Alternative clients — acme.sh (pure shell, no Python dependencies), Caddy and Traefik (reverse proxies with automatic HTTPS built in)
- IPv6 considerations — Certbot will try to validate over IPv6 if you have it. Make sure your firewall allows it on both ports.
Conclusion
For years, HTTPS was something you had to pay for and manually manage. That era is over. Let’s Encrypt has encrypted the internet — and your homelab deserves encryption too. A five-minute setup gets you free certificates with automatic renewal, and from that point on your browser just… trusts you. There’s something quietly satisfying about that green padlock on your home-grown wiki.
Run the certbot --nginx command today, do the dry run, and sleep easier knowing your traffic is encrypted and your certs will renew themselves. The padlock awaits.
Leave a Reply