Get free SSL certificates for your VPS using Let's Encrypt. This guide covers multiple methods: Certbot with Nginx/Apache, Traefik, and Caddy.
Overview
Let's Encrypt provides free, automated SSL certificates. You have several options:
| Method | Best For | Difficulty |
|---|---|---|
| Certbot + Nginx | Traditional setups | Easy |
| Certbot + Apache | PHP/WordPress | Easy |
| Traefik | Docker containers | Medium |
| Caddy | Automatic everything | Easiest |
| Certbot standalone | No web server | Easy |
Before you start
Verify your domain points to your VPS:
dig +short yourdomain.com
This should return your VPS IP address.
Ensure ports 80 and 443 are open:
sudo ufw allow 80
sudo ufw allow 443
sudo ufw status
Method 1: Certbot with Nginx
Install Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
Get the certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will:
- Verify you control the domain
- Obtain the certificate
- Configure Nginx to use HTTPS
- Set up auto-renewal
Follow the prompts:
- Enter your email for renewal notices
- Agree to terms of service
- Choose whether to redirect HTTP to HTTPS (recommended: yes)
Verify the configuration
sudo nginx -t
sudo systemctl reload nginx
Visit https://yourdomain.com - you should see a padlock.
Verify auto-renewal
sudo certbot renew --dry-run
Certbot installs a systemd timer that runs twice daily:
sudo systemctl status certbot.timer
Method 2: Certbot with Apache
Install Certbot
sudo apt update
sudo apt install certbot python3-certbot-apache -y
Get the certificate
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
Verify
sudo apachectl configtest
sudo systemctl reload apache2
Method 3: Certbot standalone (no web server)
Use this when you do not have a web server running or want to manage certificates separately.
Stop any service on port 80
sudo systemctl stop nginx # or apache2
Get the certificate
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
Certificates are saved to:
- Certificate:
/etc/letsencrypt/live/yourdomain.com/fullchain.pem - Private key:
/etc/letsencrypt/live/yourdomain.com/privkey.pem
Restart your service
sudo systemctl start nginx
Configure renewal hooks
Since standalone needs port 80 free, add hooks:
sudo nano /etc/letsencrypt/renewal/yourdomain.com.conf
Add at the end:
[renewalparams]
pre_hook = systemctl stop nginx
post_hook = systemctl start nginx
Method 4: Traefik (Docker)
Traefik handles SSL automatically for Docker containers.
Create docker-compose.yml
services:
traefik:
image: traefik:v2.11
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
- [email protected]
- --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
restart: unless-stopped
app:
image: your-app:latest
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`yourdomain.com`)
- traefik.http.routers.app.tls=true
- traefik.http.routers.app.tls.certresolver=letsencrypt
- traefik.http.services.app.loadbalancer.server.port=3000
volumes:
letsencrypt:
Start the stack
docker compose up -d
Traefik automatically obtains and renews certificates for any container with the appropriate labels.
Method 5: Caddy (automatic SSL)
Caddy handles SSL with zero configuration.
Install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
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 -y
Configure Caddy
sudo nano /etc/caddy/Caddyfile
For a reverse proxy:
yourdomain.com {
reverse_proxy localhost:3000
}
anotherdomain.com {
reverse_proxy localhost:3001
}
For a static site:
yourdomain.com {
root * /var/www/html
file_server
}
Start Caddy
sudo systemctl restart caddy
That is it. Caddy automatically obtains and renews certificates.
Wildcard certificates
Wildcard certificates cover all subdomains: *.yourdomain.com
Requirements
Wildcard certificates require DNS-01 challenge (not HTTP). You need:
- DNS provider API access
- Certbot DNS plugin for your provider
Example with Cloudflare
Install the plugin:
sudo apt install python3-certbot-dns-cloudflare -y
Create credentials file:
sudo mkdir -p /etc/letsencrypt
sudo nano /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = your-api-token
Secure the file:
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
Get the wildcard certificate:
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d yourdomain.com \
-d "*.yourdomain.com"
DNS plugins for other providers
| Provider | Plugin |
|---|---|
| Cloudflare | python3-certbot-dns-cloudflare |
| DigitalOcean | python3-certbot-dns-digitalocean |
| Route53 | python3-certbot-dns-route53 |
| Google Cloud | python3-certbot-dns-google |
Certificate locations
Certbot stores certificates in:
/etc/letsencrypt/live/yourdomain.com/
├── cert.pem # Your certificate
├── chain.pem # Intermediate certificates
├── fullchain.pem # cert.pem + chain.pem (use this)
├── privkey.pem # Private key
Always use fullchain.pem for the certificate and privkey.pem for the key.
Manual renewal
Force renewal of all certificates:
sudo certbot renew --force-renewal
Renew a specific certificate:
sudo certbot certonly --force-renewal -d yourdomain.com
Check certificate expiration
sudo certbot certificates
Or check a specific domain:
echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates
Rate limits
Let's Encrypt has rate limits:
| Limit | Value |
|---|---|
| Certificates per domain | 50 per week |
| Duplicate certificates | 5 per week |
| Failed validations | 5 per hour |
For testing, use the staging environment:
sudo certbot --nginx --staging -d yourdomain.com
Remove --staging when ready for production.
Troubleshooting
Challenge failed - connection refused
Port 80 must be open and accessible:
sudo ufw allow 80
sudo ss -tlnp | grep :80
Test from outside:
curl -I http://yourdomain.com
DNS problem - NXDOMAIN
Your DNS is not pointing to your VPS:
dig +short yourdomain.com
Wait for DNS propagation (up to 48 hours, usually minutes).
Too many certificates already issued
You hit rate limits. Wait a week or use a different subdomain for testing.
Certificate not trusted
You are using the staging certificate. Re-run Certbot without --staging.
Auto-renewal not working
Check the timer:
sudo systemctl status certbot.timer
journalctl -u certbot
Test renewal:
sudo certbot renew --dry-run
Permission denied reading certificates
Ensure proper permissions:
sudo chmod 755 /etc/letsencrypt/live
sudo chmod 755 /etc/letsencrypt/archive
For non-root services, add them to the ssl-cert group:
sudo usermod -aG ssl-cert www-data
sudo chgrp -R ssl-cert /etc/letsencrypt/live
sudo chgrp -R ssl-cert /etc/letsencrypt/archive
Nginx SSL best practices
After getting your certificate, optimize your Nginx SSL config:
sudo nano /etc/nginx/sites-available/yourdomain.com
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# ... rest of your config
}
Test your SSL configuration at SSL Labs.
Where to go next
Tutorials:
Comparisons:
ServerCompass:
From across the StoicSoft network
Hand-curated reads on the same topic from sister sites in the StoicSoft family.

