Automating Zero-Downtime Multi-Domain SSL on AWS EC2 with Docker and Nginx
Managing SSL/TLS certificates across multiple customer subdomains or microservices is a rite of passage for every cloud engineer. While AWS Application Load Balancer (ALB) paired with AWS Certificate Manager (ACM) is the standard managed solution, smaller projects, staging clusters, and cost-conscious architectures often require running an Nginx reverse proxy directly on an EC2 instance.
In this guide, we will implement an automated, multi-domain SSL architecture on an AWS EC2 instance using Docker, Nginx, and Certbot (Let's Encrypt)βwith zero downtime for existing traffic.
ποΈ The Architecture Overview
Here is how traffic flows through our host:
graph TD
Client[Internet Clients] -->|HTTPS :443| Route53[AWS Route 53]
Route53 -->|A Record| EIP[Elastic IP]
EIP -->|Port 80/443| EC2[Ubuntu 22.04 EC2 Instance]
subgraph "Docker Host"
EC2 --> NginxProxy[Docker: Nginx Reverse Proxy]
NginxProxy -->|upstream app| ServiceA[Application Container :8000]
NginxProxy -->|upstream portal| ServiceB[Portal Container :3000]
Certbot[Certbot Standalone] -.->|expands certs| SSLVolume[(Shared SSL Certs Volume)]
SSLVolume --> NginxProxy
end
π οΈ Step 1: Network & Security Group Baseline
Ensure your EC2 instance is placed in a public subnet with an Elastic IP (EIP). The security group must allow inbound traffic on:
- Port 80 (HTTP): Required for ACME HTTP-01 domain validation challenges and HTTP-to-HTTPS redirection.
- Port 443 (HTTPS): Encrypted client traffic.
- Port 22 (SSH): Strictly restricted to your trusted IP address.
π³ Step 2: Setting Up the Docker Reverse Proxy
Create a directory structure on your EC2 host:
mkdir -p /opt/cloud-proxy/{nginx/conf.d,ssl,certbot}
cd /opt/cloud-proxy
Create a docker-compose.yml file:
version: '3.8'
services:
proxy:
image: nginx:alpine
container_name: cloud_proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
networks:
- web_net
backend_app:
image: traefik/whoami
container_name: app_service
restart: always
networks:
- web_net
networks:
web_net:
driver: bridge
π Step 3: Nginx Multi-Domain Configuration
Create /opt/cloud-proxy/nginx/conf.d/app.conf:
# HTTP - Redirect all traffic to HTTPS
server {
listen 80;
server_name app.example.com portal.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS - Secure Reverse Proxy
server {
listen 443 ssl http2;
server_name app.example.com portal.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Optimized Buffer Configuration for High-Throughput APIs
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
location / {
proxy_pass http://backend_app:80;
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;
}
}
π Step 4: Expanding Multi-Domain SSL Certificates
When onboarding a new subdomain (e.g. adding portal.example.com to an existing certificate for app.example.com), Certbot supports expanding Subject Alternative Names (SAN):
# 1. Briefly pause proxy to release port 80/443 for standalone verification
docker stop cloud_proxy
# 2. Expand certificate across all required subdomains
sudo certbot certonly --standalone --expand \
-d app.example.com \
-d portal.example.com \
--non-interactive \
--agree-tos \
-m admin@example.com
# 3. Copy newly generated certificates to the proxy SSL directory
sudo cp /etc/letsencrypt/live/app.example.com/fullchain.pem /opt/cloud-proxy/ssl/
sudo cp /etc/letsencrypt/live/app.example.com/privkey.pem /opt/cloud-proxy/ssl/
# 4. Resume proxy
docker start cloud_proxy
Note: The proxy pause takes less than 5 seconds. In production environments where even 5 seconds of downtime is unacceptable, use the Webroot ACME challenge method or DNS-01 validation via AWS Route 53.
π Step 5: Automated Verification & Health Checks
Always verify your redirects and TLS handshakes with curl:
# Verify HTTP-to-HTTPS 301 Redirect
curl -sD - http://app.example.com | grep -i "Location:"
# Verify SSL Certificate Validity and Expiry
echo | openssl s_client -servername app.example.com -connect app.example.com:443 2>/dev/null | openssl x509 -noout -dates -subject
β οΈ Common Gotcha: Nginx Upstream Temp Buffering
In high-throughput microservices, you may encounter this warning in your Nginx container logs:
[warn] an upstream response is buffered to a temporary file /var/cache/nginx/proxy_temp/...
This occurs when upstream API responses exceed default 4KB memory buffers, forcing Nginx to write temporary files to disk. On AWS EC2, disk I/O on EBS volumes introduces latency spikes. Tuning proxy_buffer_size 16k; and proxy_buffers 8 32k; resolves this immediately.
π‘ Summary
By combining Docker, Nginx, and Certbot on an AWS EC2 instance, you achieve:
- Low-cost multi-domain routing without multi-ALB overhead.
- Rapid SAN certificate expansion for dynamic tenant subdomains.
- High throughput with tuned proxy buffering and modern TLS protocols.
What strategies do you use for SSL automation on AWS? Let's discuss in the comments below!
Top comments (1)