Free SSL certificates for Docker Compose + Nginx. No Certbot required.
Certificate Architecture
Edge Certificate (Cloudflare ↔ Visitors): 3-month validity, auto-renewed
Origin Certificate (Cloudflare ↔ Server): 15-year validity, manual setup
Quick Setup
1. Generate Certificate
Cloudflare Dashboard → SSL/TLS → Origin Server → Create Certificate
- Hostnames:
*.yourdomain.com, yourdomain.com - Validity: 15 years
- Format: PEM
⚠️ Save private key immediately – shown only once.
2. Save to Server
mkdir -p nginx/ssl
nano nginx/ssl/nginx.crt # Paste origin certificate
nano nginx/ssl/nginx.key # Paste private key
chmod 644 nginx/ssl/nginx.crt
chmod 600 nginx/ssl/nginx.key
3. Docker Compose
nginx:
volumes:
- ./nginx/ssl/nginx.crt:/etc/nginx/ssl/nginx.crt:ro
- ./nginx/ssl/nginx.key:/etc/nginx/ssl/nginx.key:ro
4. Nginx Config
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://app:port;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
5. Cloudflare Settings
SSL/TLS → Overview → Set to Full (strict)
6. Deploy
docker compose up -d
docker compose logs nginx # Verify
Troubleshooting
Cannot load certificate
ls -la nginx/ssl/ # Files exist?
cat nginx/ssl/nginx.crt # Not empty?
chmod 644 nginx/ssl/nginx.crt
chmod 600 nginx/ssl/nginx.key
docker compose restart nginx
Lost private key: Delete old cert in Cloudflare, create new one
Test SSL
curl -I https://yourdomain.com
openssl s_client -connect yourdomain.com:443
Certificate Renewal
Origin Certificate (15 years)
- Validity: 15 years from creation
- Action required: Regenerate before expiry
- Reminder: Set calendar reminder for year 2040 (or your expiry date)
Edge Certificate (3 months)
- Validity: 90 days
- Action required: None (auto-renewed by Cloudflare)
- Note: This is the certificate browsers see
Security Best Practices
File Permissions
# Certificate (public) - readable by all
chmod 644 nginx/ssl/nginx.crt
# Private key (secret) - readable only by owner
chmod 600 nginx/ssl/nginx.key
Directory Structure
Recommended structure:
project/
├── docker-compose.yml
├── nginx/
│ ├── nginx.conf
│ └── ssl/
│ ├── nginx.crt (644 permissions)
│ └── nginx.key (600 permissions)
Version Control
NEVER commit private keys to Git:
# Add to .gitignore
echo "nginx/ssl/*.key" >> .gitignore
Consider committing certificates (public):
# Certificates are public, safe to commit
git add nginx/ssl/nginx.crt
Backup
Backup your private key securely:
# Encrypt and backup
gpg -c nginx/ssl/nginx.key
# Store nginx.key.gpg in secure location
Alternative: Using Let’s Encrypt Paths
If you prefer traditional Let’s Encrypt directory structure:
Directory Structure
mkdir -p /etc/letsencrypt/live/yourdomain.com
Save Certificates
# Origin Certificate → fullchain.pem
nano /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# Private Key → privkey.pem
nano /etc/letsencrypt/live/yourdomain.com/privkey.pem
# Set permissions
chmod 644 /etc/letsencrypt/live/yourdomain.com/fullchain.pem
chmod 600 /etc/letsencrypt/live/yourdomain.com/privkey.pem
Docker Compose Configuration
volumes:
- /etc/letsencrypt/live/yourdomain.com/fullchain.pem:/etc/letsencrypt/live/yourdomain.com/fullchain.pem:ro
- /etc/letsencrypt/live/yourdomain.com/privkey.pem:/etc/letsencrypt/live/yourdomain.com/privkey.pem:ro
Nginx Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
Notes
- Free: Included in all Cloudflare plans
- Validity: Origin cert = 15 years, Edge cert = 90 days (auto-renewed)
- Wildcard:
*.domain.comcovers all subdomains at no extra cost - No Certbot: Origin certificates are simpler than Let’s Encrypt
- Browser view: Users see the 3-month Edge certificate (auto-renewed by Cloudflare)
- Renewal: Set reminder for 2040 (or your expiry date) to regenerate Origin cert

