DEV Community

Ajit Kumar
Ajit Kumar

Posted on

From IP to Identity: The Complete Guide to Deploying Django with SSL, Nginx, and Docker

1. The Architectural Blueprint

Before we type a single command, understand the "Flow of Traffic":

  1. The User types yourdomain.com.
  2. GoDaddy DNS points that name to your AWS Elastic IP.
  3. AWS Security Group acts as a firewall, allowing traffic on Port 80 (HTTP) and 443 (HTTPS).
  4. 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.

  1. Go to EC2 Dashboard > Elastic IPs.
  2. Click Allocate Elastic IP and then Associate it with your instance.
  3. 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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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";
    }
}

Enter fullscreen mode Exit fullscreen mode

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:

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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(",")

Enter fullscreen mode Exit fullscreen mode

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 web container. Ensure your upstream name in nginx.conf matches the service name in docker-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/letsencrypt is likely wrong.
  • Connection Refused: AWS Security Group is likely blocking Port 443.

Top comments (0)