SSL Certificates for Self-Hosted Services: Complete Guide to HTTPS
Running self-hosted services over plain HTTP is risky. Passwords, session cookies, and personal data travel in cleartext, vulnerable to interception.
HTTPS encrypts everything. With free SSL certificates from Let’s Encrypt, there’s no excuse to skip it.
This guide covers three ways to get SSL certificates for your home server:
- Nginx Proxy Manager - Easiest, GUI-based
- Caddy - Automatic HTTPS, zero config
- Certbot - Manual, maximum control
Why SSL Certificates Matter
Without HTTPS
- Passwords visible - Anyone on your network sees login credentials
- Session hijacking - Attackers steal cookies, impersonate you
- MITM attacks - Traffic can be intercepted and modified
- Browser warnings - “Not Secure” labels scare users
- Modern features broken - Service workers, webcam access, etc. require HTTPS
With HTTPS
- End-to-end encryption - Data scrambled between browser and server
- Authentication - Certificate proves you’re talking to the right server
- Trust indicators - Green padlock in browser
- Full feature support - PWAs, geolocation, notifications work
- Compliance - Required for many services (OAuth, payment processing)
Prerequisites
1. Domain Name
You need a domain pointing to your server. Let’s Encrypt validates domain ownership.
Options:
- Buy domain: Namecheap, Porkbun, Cloudflare ($8-15/year)
- Free subdomain: DuckDNS, FreeDNS (limited features)
2. Ports Open
Let’s Encrypt validates via HTTP (port 80) or HTTPS (port 443). Open these on your router:
3. DNS Pointing to Your Server
Set an A record at your domain registrar:
Find your public IP:
curl ifconfig.me
Test DNS propagation:
dig yourdomain.com
# Should show your public IP
Method 1: Nginx Proxy Manager (Easiest)
Best for: Beginners, multiple services, GUI preference
Install
mkdir -p ~/nginx-proxy-manager
cd ~/nginx-proxy-manager
nano docker-compose.yml
Paste:
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '443:443'
- '81:81'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
Start:
docker compose up -d
Configure
- Access admin panel:
http://your-server-ip:81 - Default login:
- Email:
[email protected] - Password:
changeme
- Email:
- Change credentials immediately
Add SSL Certificate
Step 1: Add Proxy Host
- Click “Hosts” → “Proxy Hosts” → “Add Proxy Host”
- Domain Names:
yourdomain.com - Scheme:
http - Forward Hostname:
localhost(or container name) - Forward Port: Service port (e.g.,
8080for Nextcloud) - ✅ Block Common Exploits
Step 2: Enable SSL
- Click “SSL” tab
- SSL Certificate: “Request a new SSL Certificate”
- ✅ Force SSL
- ✅ HTTP/2 Support
- Email: Your email for renewal notices
- ✅ Agree to Let’s Encrypt Terms
- Click Save
Done! NPM requests certificate, installs it, and auto-renews every 90 days.
Add Multiple Subdomains
Repeat for each service:
nextcloud.yourdomain.com→localhost:8080jellyfin.yourdomain.com→localhost:8096vaultwarden.yourdomain.com→localhost:8000
Each gets its own free certificate.
Wildcards (Advanced)
For *.yourdomain.com certificates, use DNS challenge:
- Get Cloudflare API token
- In NPM, select “DNS Challenge”
- Choose “Cloudflare”
- Paste API token
- Request wildcard:
*.yourdomain.com
Now all subdomains share one certificate.
Method 2: Caddy (Automatic HTTPS)
Best for: Zero-config lovers, simple setups, single service
Caddy gets SSL certificates automatically 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
Configure Caddyfile
Edit /etc/caddy/Caddyfile:
That’s it. Caddy automatically:
- Requests Let’s Encrypt certificate
- Redirects HTTP → HTTPS
- Renews certificates
- Enables HTTP/2 and HTTP/3
Reload Caddy
sudo systemctl reload caddy
Visit https://yourdomain.com - SSL works immediately.
Multiple Services
Each subdomain gets automatic HTTPS.
Custom Options
Method 3: Certbot (Manual)
Best for: Nginx/Apache users, maximum control, custom setups
Install Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
Or for Apache:
sudo apt install certbot python3-certbot-apache -y
Get Certificate (Nginx)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Certbot will:
- Validate domain ownership
- Generate certificate
- Update Nginx config
- Set up auto-renewal
Get Certificate (Apache)
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
Standalone (No Web Server)
If you want certificates without a web server:
sudo certbot certonly --standalone -d yourdomain.com
Certificates go to:
Manual Nginx Config
If Certbot doesn’t auto-configure, edit /etc/nginx/sites-available/default:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
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 config
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Test and reload:
sudo nginx -t
sudo systemctl reload nginx
Auto-Renewal
Certbot sets up auto-renewal via systemd timer:
sudo systemctl status certbot.timer
Test renewal:
sudo certbot renew --dry-run
If successful, certificates auto-renew before expiration.
DNS Challenge (For Local Servers)
If you can’t open ports 80/443 (CGNAT, corporate network), use DNS challenge.
With Certbot + Cloudflare
Get Cloudflare API token:
- Cloudflare dashboard → My Profile → API Tokens
- Create token with
Zone:DNS:Editpermissions
Install plugin:
sudo apt install python3-certbot-dns-cloudflareCreate credentials file:
sudo nano /root/.secrets/cloudflare.iniPaste:
dns_cloudflare_api_token = YOUR_TOKEN_HERESecure it:
sudo chmod 600 /root/.secrets/cloudflare.iniRequest certificate:
sudo certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \ -d yourdomain.com \ -d '*.yourdomain.com'
Now you have SSL without exposing your server publicly!
With Caddy + Cloudflare
Install Caddy with DNS plugin:
sudo caddy add-package github.com/caddy-dns/cloudflare
Caddyfile:
Set environment variable:
export CLOUDFLARE_API_TOKEN="your_token"
Troubleshooting
“Unable to validate domain”
Causes:
- DNS not propagated yet (wait 5-10 minutes)
- Firewall blocking port 80/443
- Wrong IP in DNS A record
Check:
# Test port 80 is reachable
curl -I http://yourdomain.com
# Verify DNS
dig yourdomain.com
# Check firewall
sudo ufw status
“Certificate already exists”
If certificate exists but not installed:
sudo certbot certificates
Shows certificate location. Update your web server config to use it.
“Too many requests”
Let’s Encrypt rate limit: 5 failed attempts per hour, 50 certificates per domain per week.
Fix: Wait 1 hour and try again.
“Connection refused”
Your service isn’t running or is on wrong port.
Check:
sudo netstat -tulpn | grep :8080
Ensure service is running and listening on expected port.
Browser shows “Not Secure”
Mixed content - Page loads HTTPS but includes HTTP resources (images, scripts).
Fix: Update URLs to use https:// or protocol-relative //domain.com/....
Best Practices
1. Use HSTS
Force browsers to always use HTTPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
2. Redirect HTTP → HTTPS
Never serve content over HTTP:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
3. Monitor Expiration
Certificates expire in 90 days. Most tools auto-renew at 30 days.
Check status:
sudo certbot certificates
Or use monitoring:
echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates
4. Test SSL Configuration
Check your SSL setup:
- SSL Labs Test
- Aim for A+ rating
5. Backup Certificates
Certificates are in /etc/letsencrypt/. Back up this directory:
sudo tar czf letsencrypt-backup.tar.gz /etc/letsencrypt
Store backup offsite.
Comparison Table
| Method | Difficulty | Auto-Renewal | GUI | Best For |
|---|---|---|---|---|
| Nginx Proxy Manager | Easy | ✅ Yes | ✅ Yes | Beginners, multiple services |
| Caddy | Easy | ✅ Yes | ❌ No | Simple setups, auto config |
| Certbot | Medium | ✅ Yes | ❌ No | Nginx/Apache, advanced users |
Security Checklist
- SSL certificate installed and valid
- HTTP redirects to HTTPS
- HSTS header enabled
- No mixed content warnings
- Auto-renewal configured
- Firewall allows ports 80/443
- DNS points to correct IP
- Tested with SSL Labs (A+ rating)
- Certificates backed up
- Monitoring for expiration set up
What About Internal-Only Services?
If you only access services from your local network, you have options:
1. Self-Signed Certificates
Free but browsers show security warnings.
Generate:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/selfsigned.key \
-out /etc/ssl/certs/selfsigned.crt
Use in Nginx:
ssl_certificate /etc/ssl/certs/selfsigned.crt;
ssl_certificate_key /etc/ssl/private/selfsigned.key;
You’ll need to add the certificate to your browser’s trusted store.
2. mkcert (Local CA)
Create locally-trusted certificates:
sudo apt install libnss3-tools
wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
sudo mv mkcert-v1.4.4-linux-amd64 /usr/local/bin/mkcert
sudo chmod +x /usr/local/bin/mkcert
mkcert -install
Generate certificate:
mkcert yourdomain.local localhost 192.168.1.100
Creates yourdomain.local+2.pem and yourdomain.local+2-key.pem.
Use in your web server - no browser warnings!
3. Tailscale HTTPS
If using Tailscale VPN:
tailscale cert yourhostname.your-tailnet.ts.net
Gets valid HTTPS certificate for your Tailscale hostname.
Final Thoughts
There’s no reason to skip HTTPS in 2025. Free certificates, automatic renewal, and easy tools make it trivial.
Start with Nginx Proxy Manager if you’re unsure. It’s the easiest path to HTTPS for multiple services.
Next Steps:
- Securing Your Home Server: Essential Steps
- How to Set Up Fail2ban
- VPN Options for Remote Access (coming soon)
Questions? Join our Discord community or drop a comment below.