1. The Architectural Blueprint
Before we type a single command, understand the "Flow of Traffic":
-
The User types
yourdomain.com. - GoDaddy DNS points that name to your AWS Elastic IP.
- AWS Security Group acts as a firewall, allowing traffic on Port 80 (HTTP) and 443 (HTTPS).
- Nginx (inside Docker) receives the request, handles the SSL "handshake," and passes the traffic to Daphne (the Django ASGI server).
2. Infrastructure Setup (GoDaddy & AWS)
Step 1: Lock your IP in AWS
By default, EC2 IPs change on reboot.
- Go to EC2 Dashboard > Elastic IPs.
- Click Allocate Elastic IP and then Associate it with your instance.
- Your instance now has a permanent "Home Address."
Step 2: Update GoDaddy DNS
You need two records so both yourdomain.com and www.yourdomain.com work.
-
A Record: Host:
@, Value:Your_Elastic_IP -
CNAME Record: Host:
www, Value:@(This points www to your main domain).
3. Obtaining SSL with Certbot (Let's Encrypt)
Certbot is the tool that talks to Let's Encrypt to prove you own the domain and issue a certificate. We use the Standalone method because our Nginx is inside Docker, and we don't want to mess with host-level Nginx.
Installation & Linking
# 1. Update system
sudo apt update
# 2. Install Snap (the recommended way for Certbot)
sudo snap install core; sudo snap refresh core
# 3. Install Certbot
sudo snap install --classic certbot
# 4. Create a symbolic link so you can run 'certbot' globally
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Issuing the Certificate
Important: You must stop any service using Port 80 (like a running Nginx container) before running this.
# Obtain the certificate
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
The Successful Output:
If successful, you will see:
Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/yourdomain.com/fullchain.pem
What just happened?
Certbot created a folder /etc/letsencrypt/ on your EC2. This folder contains your "Public Key" (fullchain.pem) and your "Private Key" (privkey.pem). We will "mount" this folder into Docker so Nginx can read it.
4. Production Configuration Files
nginx.conf (The Traffic Cop)
Create this in your project root. It handles the redirect from insecure HTTP (80) to secure HTTPS (443).
upstream app_server {
server web:8000;
}
# 1. Redirect ALL HTTP traffic to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
# 2. The Secure Server
server {
listen 443 ssl;
server_name yourdomain.com www.yourdomain.com;
# These paths are INSIDE the container (mapped via docker-compose)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location /static/ {
alias /app/staticfiles/;
}
location / {
proxy_pass http://app_server;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
# WEBSOCKET SUPPORT: Essential for real-time apps
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
docker-compose.yml (The Orchestrator)
The key here is the Volumes section. We map the host's SSL folder to the container's SSL folder.
services:
web:
build: .
command: ["daphne", "-b", "0.0.0.0", "-p", "8000", "myproject.asgi:application"]
env_file: .env
restart: always
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- static_volume:/app/staticfiles:ro
# MOUNT THE HOST CERTS HERE:
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- web
restart: always
volumes:
static_volume:
5. Django Security Settings (.env & settings.py)
When you use a proxy (Nginx) for SSL, Django needs to know that the request it receives (which is technically HTTP from Nginx to Web) was originally HTTPS.
In .env:
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com,your_ip
CSRF_TRUSTED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
In settings.py:
import os
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")
# Tells Django to trust the X-Forwarded-Proto header from Nginx
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# New in Django 4.0+: Required for form submissions over HTTPS
CSRF_TRUSTED_ORIGINS = os.getenv("CSRF_TRUSTED_ORIGINS", "").split(",")
6. Operational Commands
| Action | Command |
|---|---|
| Deploy / Update | docker compose up -d --force-recreate |
| Stop Everything | docker compose down |
| Check Logs | docker compose logs -f nginx |
| Verify Certs | ls -la /etc/letsencrypt/live/yourdomain.com/ |
| Check SSL Health | curl -Iv https://yourdomain.com |
7. Troubleshooting Common Errors
-
502 Bad Gateway: Nginx is running, but it can't find the
webcontainer. Ensure yourupstreamname innginx.confmatches the service name indocker-compose.yml. -
500 Internal Server Error: This is usually a Django crash. Check
docker compose logs web. Most common cause:DisallowedHost(Check your.env). -
Nginx fails to start: Check
docker compose logs nginx. If it says "file not found," your volume mapping for/etc/letsencryptis likely wrong. - Connection Refused: AWS Security Group is likely blocking Port 443.
Top comments (0)