IP에서 Identity까지: Django를 SSL, Nginx, Docker와 함께 배포하는 완전 가이드
Source: Dev.to
1. 아키텍처 청사진
명령을 하나도 입력하기 전에, 트래픽 흐름을 이해하세요:
| 구성 요소 | 역할 |
|---|---|
| User | yourdomain.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는 재부팅 시 변경됩니다.
- EC2 Dashboard → Elastic IPs 로 이동합니다.
- Allocate Elastic IP 를 클릭한 뒤 Associate 를 선택해 인스턴스와 연결합니다.
- 이제 인스턴스에 영구적인 “홈 주소”가 할당되었습니다.
Step 2 – GoDaddy DNS 업데이트
yourdomain.com 과 www.yourdomain.com 모두 작동하도록 두 개의 레코드를 생성합니다.
| 레코드 유형 | 호스트 | 값 |
|---|---|---|
| A | @ | Your_Elastic_IP |
| CNAME | www | @ (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 Gateway | Nginx가 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.py에 SECURE_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을 차단하고 있을 가능성이 높습니다. 해당 포트에 대한 인바운드 트래픽을 허용하도록 보안 그룹 규칙을 조정하세요.
배포 즐겁게 하세요! 🚀
