Paying for an SSL certificate in 2024 is like paying for email. There's no reason to do it unless you enjoy burning money.
Let's Encrypt has been issuing free, trusted SSL certificates since 2016. Certbot — the official client — automates the whole process. Yet I still see indie hackers buying Comodo or Sectigo certs from their registrar for $80–120/year. The upsell works because nobody told them they didn't have to pay.
This guide covers SSL certificate setup free with Let's Encrypt from scratch: installing Certbot, issuing your first cert, configuring Nginx or Apache, and automating renewal so you never think about it again. I'll also cover DNS validation for wildcard certs, because sooner or later you'll need one.
Why Let's Encrypt Is Good Enough for Production
Let's Encrypt issues Domain Validated (DV) certificates. That's the same trust level as most paid certs — your browser shows the padlock, TLS is encrypted, done. The only thing you don't get is Extended Validation (EV), which used to show a green company name in the address bar. Modern browsers dropped that UI in 2019. EV certs are now a $300/year placebo.
Let's Encrypt certs expire every 90 days. That sounds annoying until you realize renewal is fully automated. I've had certs auto-renewing on Hetzner servers for four years without a single manual intervention.
One real limitation: rate limits. You can issue 50 certs per registered domain per week. For a single app or a handful of subdomains, that's irrelevant. For a SaaS platform issuing certs per-customer, you'll need a different approach — but that's a separate article.
Installing Certbot
Certbot is the official ACME client from the Electronic Frontier Foundation. Install it from snap — it's the method the EFF now recommends because it stays current regardless of your distro's package lag.
# Remove any old certbot installed via apt
sudo apt remove certbot
# Install via snap
sudo snap install --classic certbot
# Link the binary
sudo ln -s /snap/bin/certbot /usr/bin/certbot
This works on Ubuntu 20.04, 22.04, and Debian 11/12. If you're on a different distro, check certbot.eff.org — they have instructions for everything down to FreeBSD.
Verify the install:
certbot --version
# certbot 2.10.0
As of early 2024, 2.10.x is current. If you see something older, your snap isn't refreshing — run sudo snap refresh certbot.
Getting Your First Certificate with Nginx
Assume your domain is example.com, DNS is pointed at your server, and Nginx is running. The Nginx plugin handles everything — it edits your config, reloads Nginx, and sets up the HTTPS block automatically.
sudo certbot --nginx -d example.com -d www.example.com
Certbot will ask for your email (used for expiry warnings), ask you to agree to the ToS, and optionally subscribe you to EFF updates. Then it does the HTTP-01 challenge: it drops a file in /.well-known/acme-challenge/ on your server, Let's Encrypt's servers fetch it to prove you control the domain, and the cert is issued.
The whole thing takes about 30 seconds.
After it runs, your Nginx config will have something like this appended:
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
That options-ssl-nginx.conf file sets sane TLS defaults — TLS 1.2/1.3 only, reasonable cipher suites. It's fine for most apps. If you need a specific security posture (PCI-DSS, etc.), you'll want to customize it, but for a typical SaaS or portfolio site, leave it alone.
Getting Your First Certificate with Apache
Same idea, different plugin:
sudo certbot --apache -d example.com -d www.example.com
Certbot will find your VirtualHost, configure SSL, and set up a redirect from HTTP to HTTPS. Check the result:
apache2ctl -t && sudo systemctl reload apache2
If Apache throws a syntax error after Certbot runs, it's almost always a duplicate ServerName directive. Check /etc/apache2/sites-enabled/ and clean it up.
Wildcard Certificates via DNS Challenge
HTTP-01 validation only works for specific subdomains. If you want *.example.com — covering app.example.com, api.example.com, staging.example.com, and anything else — you need a wildcard cert, and that requires DNS-01 validation.
DNS-01 means Certbot adds a TXT record to your DNS to prove domain ownership. The catch: it needs API access to your DNS provider to do this automatically.
Most major providers are supported: Cloudflare, Route53, DigitalOcean DNS, Hetzner DNS, Namecheap (via a plugin), and more. I'll show Cloudflare since that's what I use.
# Install the Cloudflare DNS plugin
sudo snap install certbot-dns-cloudflare
Create a credentials file:
mkdir -p ~/.secrets/certbot
nano ~/.secrets/certbot/cloudflare.ini
Paste in your Cloudflare API token (not the global API key — create a token scoped to DNS edit on the specific zone):
dns_cloudflare_api_token = your_cloudflare_api_token_here
Lock down the permissions:
chmod 600 ~/.secrets/certbot/cloudflare.ini
Now issue the wildcard cert:
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
-d example.com \
-d "*.example.com"
Certbot will create the TXT record, wait for propagation (default 10 seconds — sometimes you need to bump this with --dns-cloudflare-propagation-seconds 30 if you hit validation errors), verify, and issue the cert.
Your wildcard cert lands at /etc/letsencrypt/live/example.com/. Point Nginx or Apache at it the same way as a regular cert.
Automating Renewal
This is where people mess up. They get the cert, forget about it, and wake up 90 days later to a broken site.
Certbot installs a systemd timer automatically. Check it:
sudo systemctl status snap.certbot.renew.timer
You should see something like:
● snap.certbot.renew.timer - Timer for snap application certbot.renew
Loaded: loaded
Active: active (waiting)
Trigger: Thu 2024-03-21 06:32:00 UTC
The timer runs twice daily. Certbot only actually renews when a cert is within 30 days of expiry — so it's a no-op most of the time.
Test that renewal works without actually renewing:
sudo certbot renew --dry-run
If that passes, you're set. If it fails, fix it now — not when the cert is actually expiring.
One thing to check: if you're using the DNS challenge for wildcards, make sure the credentials file is still at the path Certbot expects. I've seen people rotate API tokens and forget to update the file, then wonder why renewal failed three months later.
Renewal Hooks
If your web server needs a reload after renewal (Nginx usually handles this automatically via the plugin, but not always), add a deploy hook:
nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Anything in /etc/letsencrypt/renewal-hooks/deploy/ runs after a successful renewal. Use it for reloading services, copying certs to other locations, or sending yourself a Slack notification.
Checking Your SSL Configuration
Once the cert is live, run it through SSL Labs (ssllabs.com/ssltest). You're looking for an A or A+ rating. The default Certbot/Nginx config usually lands at A. To get A+, you need HSTS with a long max-age:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Add that to your Nginx server block. Just be sure your subdomains all serve HTTPS before you do — HSTS with includeSubDomains will break any subdomain still on HTTP.
Also verify the cert from the command line:
curl -vI https://example.com 2>&1 | grep -E "SSL|expire|issuer"
Or use openssl:
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
That prints notBefore and notAfter — useful to confirm you're looking at a fresh cert and not a cached one.
Common Errors and Fixes
| Error | Likely cause | Fix |
|---|---|---|
Connection refused during challenge |
Port 80 blocked | Open port 80 in firewall: ufw allow 80 |
DNS problem: NXDOMAIN |
DNS not propagated yet | Wait 5–10 min, retry |
Too many certificates already issued |
Hit rate limit | Use staging flag to test: --staging |
Permission denied on credentials file |
Wrong file permissions | chmod 600 cloudflare.ini |
| Renewal fails silently | Nginx config changed | Run certbot renew --dry-run and read the output |
The rate limit one bites people who are testing. Use --staging during development — it issues certs from Let's Encrypt's staging CA (not trusted by browsers, but functionally identical for testing). Once your setup works, drop the flag and issue the real cert.
SSL Certificate Setup Free with Let's Encrypt: What to Do Tomorrow
If you have any server running HTTP-only, or paying for a cert you don't need to pay for, here's your action plan:
- SSH into the server.
- Install Certbot via snap.
- Run
certbot --nginx(or--apache) with your domain. - Run
certbot renew --dry-runto confirm auto-renewal works. - Check SSL Labs. Fix anything below A.
That's it. The whole SSL certificate setup free with Let's Encrypt process takes under 10 minutes for a straightforward single-domain setup. If you need a wildcard cert, add another 15 minutes for the DNS plugin setup.
Stop paying for certificates. Put that $100/year toward something that actually improves your product.
If you're setting up a new VPS from scratch, check out our guide on hardening a fresh Ubuntu server before you expose it to the internet. And if you're running multiple apps on one box, Nginx reverse proxy setup covers how to route traffic cleanly without port conflicts.