TL;DR: On Ubuntu: sudo apt install certbot python3-certbot-nginx, then sudo certbot --nginx -d yourdomain.com. Certbot edits your nginx config and renews automatically every 60 days. Free certs, full TLS, 5 minutes total.
DNS routes traffic; HTTPS encrypts it. Let's Encrypt is a free, automated certificate authority that's been the default answer for "TLS on a Linux server" for nearly a decade. Two commands gets you a real cert; one cron job keeps it valid forever.
HTTPS is the bare minimum table stake for any site that lives on the open web. Browsers warn loudly when it's missing β a giant "Not Secure" badge in the address bar that destroys whatever trust your design earned. Users have learned to read this signal correctly: an HTTP-only site in 2026 looks broken, abandoned, or sketchy. There's no good reason to ship one and many reasons not to.
What makes HTTPS feel like a chore is that historically you bought a TLS certificate from a vendor (Comodo, DigiCert, Let's Encrypt's predecessors) for $50β500/year, validated your ownership through a manual email-and-DNS dance, and installed the cert in your web server by hand. Renewals were a calendar event you sometimes forgot. The whole flow was a small but real ongoing cost.
Let's Encrypt eliminated all of that. It's a free, automated, browser-trusted CA run by the Internet Security Research Group (Mozilla, EFF, Cisco, and others fund it). Their ACME protocol lets a script on your server prove ownership of a domain in seconds and receive a 90-day certificate. The tooling β usually certbot β automates issuance, installation, and renewal. The end result is HTTPS that just works, costs nothing, and renews itself.
HTTPS depends on three things being already true:
If any of these isn't true, fix that first. Let's Encrypt will fail in confusing ways otherwise.
Two reasonable paths:
example.com { ... } in your Caddyfile and Caddy figures out the cert. Right when you're starting fresh.For a brand new server with no Nginx baggage, Caddy is easier. For "I already have Nginx running and just want HTTPS added," certbot is right.
# Install Caddy on Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# Configure: edit /etc/caddy/Caddyfile
sudo nano /etc/caddy/Caddyfile
Replace the file's contents with:
example.com {
reverse_proxy localhost:3000
# Or root * /var/www/html ; file_server for static files
}
Restart Caddy: sudo systemctl restart caddy. On first request, Caddy contacts Let's Encrypt, validates your domain (your DNS must already resolve to this server), gets a cert, and serves HTTPS. It also renews automatically.
Assuming Nginx already runs and you have a server block for example.com:
# Install certbot and the Nginx plugin
sudo apt install -y certbot python3-certbot-nginx
# Issue a cert and edit your Nginx config to use it
sudo certbot --nginx -d example.com -d www.example.com
certbot prompts for your email (used for renewal-failure notifications, won't be otherwise spammed) and agreement to the Let's Encrypt terms. It then talks to Let's Encrypt, validates your domain over HTTP-01 (a temporary file at /.well-known/acme-challenge/... that the LE server fetches), gets the cert, and rewrites your Nginx config to use it.
Test the HTTPS site: curl -I https://example.com should return 200 (or whatever your app returns). The "Not Secure" badge in browsers is gone.
*.example.com requires DNS-01). For DNS-01 with certbot, add --manual --preferred-challenges dns or use a DNS-provider-specific plugin (python3-certbot-dns-cloudflare, etc.).
Both Caddy and certbot ship with automatic renewal. Certs are good for 90 days; renewal runs daily and renews any cert within 30 days of expiring. You don't need to set up a cron job; the package's systemd timer is already installed.
Check it's working:
# For certbot:
sudo systemctl list-timers | grep certbot
sudo certbot renew --dry-run # simulates a renewal without changing anything
# For Caddy: there's nothing to check β Caddy logs renewals to journalctl
journalctl -u caddy | grep -i renew
Set up an email-on-failure notification if you care about being paged: certbot already emails the address you gave it when issuance fails on its own. For Caddy, configure email-on-failure in your monitoring stack.
# From your Mac:
curl -I https://example.com
# Expected: 200 OK (or whatever your app returns), with no SSL errors
# Show the cert details:
echo | openssl s_client -showcerts -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -inform pem -noout -text \
| grep -E "Subject:|Issuer:|Not After"
The Issuer should be "Let's Encrypt" or "ISRG"; the Subject should match your domain; Not After should be 90 days in the future.
For a thorough audit, run your domain through https://www.ssllabs.com/ssltest/. The grade should be A or A+. If it's lower, the report explains what to fix (usually old TLS versions still enabled or weak ciphers).
--staging with certbot) when testing.