Managing Let’s Encrypt SSL Certificates for Nginx Running in Docker (nginx_prod)
This guide covers how to properly manage Let’s Encrypt SSL certificates when running Nginx inside Docker containers, addressing common challenges and providing automated solutions.
Prerequisites
Before starting, ensure you have:
- Docker and Docker Compose installed
- A domain pointing to your server
- Root or sudo access on your server
Installation
1. Install Certbot
On Ubuntu/Debian:
sudo apt update sudo apt install certbot
On CentOS/RHEL/Fedora:
sudo dnf install certbot # Fedora sudo yum install certbot # CentOS/RHEL
Using Snap (universal):
sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot
2. Verify Installation
certbot --version
Understanding the Challenge
When Nginx runs in Docker, several issues arise:
- Port Conflict: Certbot’s standalone mode needs port 80, but Dockerized Nginx typically uses it
- File Access: Certificates are stored in
/etc/letsencrypt/live/
but need to be accessible to the container - Automation: Renewals must account for Docker container lifecycle
Solution: Standalone Mode with Docker Integration
Step 1: Prepare Directory Structure
Create directories for SSL certificates that will be mounted in your Docker container:
sudo mkdir -p /etc/nginx/ssl sudo chmod 755 /etc/nginx/ssl
Step 2: Stop Nginx Container
Temporarily stop your Nginx container to free port 80:
docker stop nginx_prod # Or if using docker-compose: docker-compose down
Step 3: Obtain SSL Certificate
Run Certbot in standalone mode:
sudo certbot certonly \ --standalone \ -d your.domain.com \ -d www.your.domain.com \ --email your.email@example.com \ --agree-tos \ --non-interactive \ --preferred-challenges http
For multiple domains:
sudo certbot certonly \ --standalone \ -d domain1.com \ -d www.domain1.com \ -d domain2.com \ -d www.domain2.com \ --email your.email@example.com \ --agree-tos \ --non-interactive
Step 4: Copy Certificates to Docker Mount Point
Copy the certificates to your Docker-accessible directory:
sudo cp /etc/letsencrypt/live/your.domain.com/fullchain.pem /etc/nginx/ssl/your.domain.com.crt sudo cp /etc/letsencrypt/live/your.domain.com/privkey.pem /etc/nginx/ssl/your.domain.com.key # Set proper permissions sudo chmod 644 /etc/nginx/ssl/your.domain.com.crt sudo chmod 600 /etc/nginx/ssl/your.domain.com.key
Step 5: Update Docker Configuration
Ensure your Docker Compose file mounts the SSL directory:
version: '3.8' services: nginx: image: nginx:alpine container_name: nginx_prod ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - /etc/nginx/ssl:/etc/nginx/ssl:ro # Mount SSL certificates restart: unless-stopped
Step 6: Configure Nginx for SSL
Update your Nginx configuration to use the certificates:
server { listen 80; server_name your.domain.com www.your.domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your.domain.com www.your.domain.com; ssl_certificate /etc/nginx/ssl/your.domain.com.crt; ssl_certificate_key /etc/nginx/ssl/your.domain.com.key; # Modern SSL configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # Your application configuration location / { # Your proxy_pass or static file serving configuration } }
Step 7: Start Nginx Container
docker start nginx_prod # Or with docker-compose: docker-compose up -d
Automation: Automated Certificate Renewal
Create Renewal Script
Create a script to handle the entire renewal process:
sudo nano /usr/local/bin/renew-ssl.sh
#!/bin/bash DOMAIN="your.domain.com" CONTAINER_NAME="nginx_prod" SSL_DIR="/etc/nginx/ssl" # Stop nginx container echo "Stopping Nginx container..." docker stop $CONTAINER_NAME # Renew certificate echo "Renewing SSL certificate..." certbot renew --quiet --standalone # Copy renewed certificates echo "Copying certificates..." cp /etc/letsencrypt/live/$DOMAIN/fullchain.pem $SSL_DIR/$DOMAIN.crt cp /etc/letsencrypt/live/$DOMAIN/privkey.pem $SSL_DIR/$DOMAIN.key # Set permissions chmod 644 $SSL_DIR/$DOMAIN.crt chmod 600 $SSL_DIR/$DOMAIN.key # Start nginx container echo "Starting Nginx container..." docker start $CONTAINER_NAME echo "SSL renewal completed successfully!"
Make it executable:
sudo chmod +x /usr/local/bin/renew-ssl.sh
Set Up Cron Job
Add to root’s crontab for automatic renewal:
sudo crontab -e
Add this line (runs every day at 3 AM, but only renews if needed):
0 3 * * * /usr/local/bin/renew-ssl.sh >> /var/log/ssl-renewal.log 2>&1
Test the Renewal Process
Test renewal without waiting for expiration:
sudo /usr/local/bin/renew-ssl.sh
Alternative: Using Docker Volumes
For a more integrated approach, you can mount the Let’s Encrypt directory directly:
version: '3.8' services: nginx: image: nginx:alpine container_name: nginx_prod ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - /etc/letsencrypt:/etc/letsencrypt:ro restart: unless-stopped
Then reference certificates directly in Nginx config:
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
Troubleshooting
Common Issues
Port 80 already in use:
# Check what's using port 80 sudo netstat -tlnp | grep :80 # Or with ss sudo ss -tlnp | grep :80
Permission denied errors:
# Fix Let's Encrypt permissions sudo chmod -R 755 /etc/letsencrypt sudo chmod -R 644 /etc/letsencrypt/archive sudo chmod -R 600 /etc/letsencrypt/archive/*/privkey*.pem
Certificate not found:
# List available certificates sudo certbot certificates
Verification
Check if SSL is working properly:
# Test SSL configuration openssl s_client -connect your.domain.com:443 -servername your.domain.com # Check certificate expiration openssl x509 -in /etc/nginx/ssl/your.domain.com.crt -text -noout | grep "Not After"
Best Practices
- Regular Monitoring: Set up monitoring to alert you if certificate renewal fails
- Backup Certificates: Include
/etc/letsencrypt
in your backup strategy - Test Renewals: Regularly test the renewal process in staging
- Multiple Domains: Use SAN certificates for multiple subdomains
- Security Headers: Add security headers to your Nginx configuration
Security Considerations
Always use strong SSL configurations:
# Add security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options DENY always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/nginx/ssl/your.domain.com.crt;
This approach provides a robust, automated solution for managing SSL certificates with Dockerized Nginx while maintaining security and ease of maintenance.