IP에서 Identity까지: Django를 SSL, Nginx, Docker와 함께 배포하는 완전 가이드

발행: (2025년 12월 27일 오후 12:10 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

Ajit Kumar

1. 아키텍처 청사진

명령을 하나도 입력하기 전에, 트래픽 흐름을 이해하세요:

구성 요소역할
Useryourdomain.com을 입력합니다.
GoDaddy DNS도메인을 귀하의 AWS Elastic IP에 연결합니다.
AWS Security Group방화벽 역할을 하며 포트 80 (HTTP)443 (HTTPS) 트래픽을 허용합니다.
Nginx (inside Docker)요청을 받아 SSL 핸드쉐이크를 처리하고 트래픽을 Daphne(the Django ASGI server)로 프록시합니다.

2. 인프라 설정 (GoDaddy & AWS)

Step 1 – AWS에서 IP 고정하기

기본적으로 EC2 IP는 재부팅 시 변경됩니다.

  1. EC2 Dashboard → Elastic IPs 로 이동합니다.
  2. Allocate Elastic IP 를 클릭한 뒤 Associate 를 선택해 인스턴스와 연결합니다.
  3. 이제 인스턴스에 영구적인 “홈 주소”가 할당되었습니다.

Step 2 – GoDaddy DNS 업데이트

yourdomain.comwww.yourdomain.com 모두 작동하도록 두 개의 레코드를 생성합니다.

레코드 유형호스트
A@Your_Elastic_IP
CNAMEwww@ (www를 메인 도메인에 연결)

3. Certbot(Let’s Encrypt)으로 SSL 획득

Certbot은 Let’s Encrypt와 통신하여 도메인 소유권을 증명하고 인증서를 발급합니다.
Nginx가 Docker 내부에서 실행되고 호스트‑레벨 Nginx와 충돌을 피하고 싶기 때문에 Standalone 방식을 사용합니다.

설치 및 링크 만들기

# 1. 시스템 업데이트
sudo apt update

# 2. Snap 설치 (Certbot 권장 방식)
sudo snap install core; sudo snap refresh core

# 3. Certbot 설치
sudo snap install --classic certbot

# 4. 전역에서 'certbot'을 실행할 수 있도록 심볼릭 링크 생성
sudo ln -s /snap/bin/certbot /usr/bin/certbot

인증서 발급

중요: 실행하기 전에 포트 80을 사용 중인 모든 서비스(예: 실행 중인 Nginx 컨테이너)를 중지하세요.

# 인증서 획득
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com

성공 출력

다음과 유사한 메시지가 표시됩니다:

Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/yourdomain.com/fullchain.pem

무엇이 일어났나요?
Certbot이 EC2 인스턴스에 /etc/letsencrypt/ 디렉터리를 생성했습니다. 이 폴더에는 다음 파일들이 들어 있습니다:

파일설명
fullchain.pem공개 인증서(체인 포함)
privkey.pem개인 키

우리는 이 폴더를 Docker에 마운트하여 Nginx가 인증서를 읽을 수 있도록 할 것입니다.

4. 프로덕션 설정 파일

nginx.conf – 트래픽 관리인

이 파일을 프로젝트 루트에 생성합니다. HTTP(포트 80)를 HTTPS(포트 443)로 리다이렉트하고 Daphne에 요청을 프록시합니다.

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;

    # 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 – 오케스트레이터

중요한 부분은 volumes 섹션으로, 호스트의 SSL 폴더를 Nginx 컨테이너에 마운트합니다.

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 certificates here:
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - web
    restart: always

volumes:
  static_volume:

5. Django 보안 설정 (.env & settings.py)

Nginx가 SSL을 종료하면, Django는 프록시로부터 일반 HTTP를 받게 됩니다. 원래 요청이 HTTPS였음을 Django에 알려야 합니다.

.env

ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com,your_ip
CSRF_TRUSTED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com

settings.py

import os

ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")

# Trust the X-Forwarded-Proto header from Nginx
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Django 4.0+ – required for HTTPS form submissions
CSRF_TRUSTED_ORIGINS = os.getenv("CSRF_TRUSTED_ORIGINS", "").split(",")

6. Operational Commands

동작명령
배포 / 업데이트docker compose up -d --force-recreate
모두 중지docker compose down
로그 확인docker compose logs -f nginx
인증서 확인ls -la /etc/letsencrypt/live/yourdomain.com/
SSL 상태 확인curl -Iv https://yourdomain.com

7. 일반적인 오류 해결

증상가능한 원인해결 방법
502 Bad GatewayNginx가 web 컨테이너에 접근할 수 없습니다.upstream 이름(web)이 docker‑compose.yml의 서비스 이름과 일치하는지 확인하세요. 컨테이너가 실행 중인지 확인(docker compose ps)하십시오.
SSL 핸드셰이크 실패잘못된 인증서 경로나 마운트 누락./etc/letsencrypt가 Nginx 컨테이너에 올바르게 마운트되었는지, ssl_certificate/ssl_certificate_key가 올바른 파일을 가리키는지 확인하세요.
리다이렉트 루프HTTP와 HTTPS 블록이 같은 포트를 청취하고 있습니다.HTTP 서버 블록은 80만, HTTPS 블록은 443만 청취하도록 설정하세요.
CSRF 오류CSRF_TRUSTED_ORIGINS가 설정되지 않았거나 SECURE_PROXY_SSL_HEADER가 누락되었습니다.두 환경 변수가 로드되었는지, settings.pySECURE_PROXY_SSL_HEADER가 있는지 확인하세요.
도메인에 접근할 수 없음DNS가 전파되지 않았거나 Elastic IP가 연결되지 않았습니다.GoDaddy A/CNAME 레코드와 Elastic IP가 실행 중인 EC2 인스턴스에 연결되어 있는지 다시 확인하세요.

추가 문제

500 Internal Server Error
일반적으로 Django 충돌입니다. 다음 명령으로 로그를 확인하세요:

docker compose logs web

가장 흔한 원인은 DisallowedHost 오류입니다(.env 설정을 확인하세요).

Nginx 시작 실패
Nginx 로그를 확인하세요:

docker compose logs nginx

로그에 “file not found”가 포함되어 있으면 /etc/letsencrypt에 대한 볼륨 매핑이 잘못된 것입니다.

연결 거부
AWS 보안 그룹이 포트 443을 차단하고 있을 가능성이 높습니다. 해당 포트에 대한 인바운드 트래픽을 허용하도록 보안 그룹 규칙을 조정하세요.

배포 즐겁게 하세요! 🚀

Back to Blog

관련 글

더 보기 »