웹 서비스를 운영하다 보면 기존 웹 서버와 함께 다양한 백엔드 애플리케이션을 연동해야 하는 상황이 자주 발생합니다. 특히 Python으로 작성된 FastAPI 애플리케이션을 기존 Nginx 웹 서버와 함께 운영하는 경우, 리버스 프록시 설정이 필요합니다. 이 글에서는 Nginx를 사용하여 FastAPI 애플리케이션을 프록시하는 과정에서 발생할 수 있는 문제점들과 해결 방법에 대해 자세히 알아보겠습니다.
배경: Nginx와 FastAPI의 조합
Nginx는 높은 성능과 안정성을 자랑하는 웹 서버로, 정적 콘텐츠 제공과 리버스 프록시 기능에 탁월합니다. FastAPI는 Python 기반의 현대적인 웹 프레임워크로, 비동기 처리와 자동 문서화 기능이 특징입니다. 이 두 기술을 함께 사용하면 다음과 같은 이점이 있습니다:
- Nginx를 프론트에 두어 SSL 종료, 캐싱, 로드 밸런싱 처리
- FastAPI는 백엔드에서 비즈니스 로직 처리에 집중
- 여러 애플리케이션을 단일 도메인 아래에서 서비스 가능
기본 설정: Nginx 리버스 프록시 구성
기본적인 Nginx 리버스 프록시 설정은 다음과 같습니다:
server {
server_name example.com;
location /api {
proxy_pass http://localhost:8000;
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;
}
# SSL 설정
listen 443 ssl;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
}
이 설정은 /api
경로로 들어오는 모든 요청을 로컬 포트 8000에서 실행 중인 애플리케이션(예: FastAPI)으로 전달합니다.
주요 문제점과 해결 방법
1. 경로 불일치 문제
문제: Nginx의 location
블록과 FastAPI 라우팅 경로가 일치하지 않을 때 404 에러가 발생합니다.
증상: 브라우저에서 example.com/api
에 접속하면 404 “Not Found” 오류가 발생합니다.
원인: Nginx는 /api
요청을 http://localhost:8000/api
로 전달하지만, FastAPI 애플리케이션은 루트 경로(/
)에서 응답하도록 설정되어 있습니다.
해결책: 다음과 같이 trailing slash를 추가하여 경로 재작성을 구현합니다:
location /api/ {
# trailing slash 추가
proxy_pass http://localhost:8000/;
# 다른 설정 유지
}
또는 URL 경로 재작성 규칙을 사용할 수도 있습니다:
location /api {
rewrite ^/api$ / break;
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://localhost:8000;
# 다른 설정 유지
}
2. 콘텐츠 타입 불일치 문제
문제: API 응답의 Content-Type 헤더가 프록시 과정에서 손실되어 클라이언트에서 파싱 오류가 발생합니다.
증상: JavaScript에서 API 응답을 처리할 때 “Unexpected token ‘<‘, ‘<!DOCTYPE html>’ is not valid JSON” 같은 오류가 발생합니다.
원인: FastAPI가 반환하는 Content-Type 헤더가 Nginx에 의해 변경되거나 전달되지 않을 수 있습니다.
해결책: Content-Type 헤더를 명시적으로 전달하도록 설정합니다:
location /api {
proxy_pass http://localhost:8000;
# 기본 헤더 설정
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Content-Type 헤더 보존
proxy_pass_header Content-Type;
# 버퍼링 관련 이슈 방지
proxy_buffering off;
}
3. CORS(Cross-Origin Resource Sharing) 관련 문제
문제: 다른 도메인에서 API에 접근할 때 CORS 정책으로 인한 오류가 발생합니다.
증상: 브라우저 콘솔에 “Access to fetch at ‘https://example.com/api’ from origin ‘https://another-site.com’ has been blocked by CORS policy” 오류가 표시됩니다.
해결책: Nginx에서 CORS 헤더를 추가합니다:
location /api {
proxy_pass http://localhost:8000;
# 기본 헤더 설정
# CORS 헤더 추가
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
# OPTIONS 요청 처리
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
4. 타임아웃 문제
문제: 장시간 실행되는 API 요청이 Nginx 타임아웃으로 인해 중단됩니다.
증상: 긴 처리 시간이 필요한 요청이 중간에 끊어지며, Nginx 로그에 타임아웃 관련 오류가 기록됩니다.
해결책: 타임아웃 설정을 늘립니다:
location /api {
proxy_pass http://localhost:8000;
# 기본 헤더 설정
# 타임아웃 설정 증가
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
5. 대용량 요청/응답 처리 문제
문제: 큰 파일 업로드나 다운로드 시 실패가 발생합니다.
증상: 대용량 파일 전송 중 연결이 끊어지거나, 413 “Request Entity Too Large” 에러가 발생합니다.
해결책: 클라이언트 요청 본문 크기 제한을 늘립니다:
http {
# 최대 요청 본문 크기 증가 (기본값: 1m)
client_max_body_size 100m;
# 서버 설정
server {
# 기존 설정 유지
}
}
서브도메인을 활용한 고급 구성
여러 백엔드 애플리케이션을 운영해야 할 경우, 서브도메인을 사용하면 더 깔끔한 구성이 가능합니다:
# FastAPI 애플리케이션을 위한 서브도메인
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8000;
# 기본 헤더 및 설정
}
}
# 메인 웹사이트
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 기존 웹사이트 설정
}
이 방식은 경로 충돌 문제를 완전히 해결하고, 각 애플리케이션을 독립적으로 관리할 수 있게 해줍니다.
보안 고려사항
프록시 설정 시 반드시 고려해야 할 보안 사항들:
- 헤더 필터링: 클라이언트에서 전송된 위험한 헤더가 백엔드에 전달되지 않도록 필터링합니다.
location /api {
proxy_pass http://localhost:8000;
# 안전한 헤더만 전달
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;
# 다른 헤더는 전달하지 않음
proxy_set_header X-Forwarded-Host "";
proxy_set_header X-Forwarded-Server "";
}
- 내부 네트워크 보호: FastAPI 애플리케이션은 localhost나 내부 네트워크에서만 접근 가능하도록 설정합니다.
# FastAPI 실행 (외부에서 직접 접근 불가)
uvicorn main:app --host 127.0.0.1 --port 8000
- SSL 종료: 모든 SSL 처리는 Nginx에서 담당하고, 내부 통신은 HTTP로 진행하여 성능을 최적화합니다.
디버깅 및 문제 해결 팁
Nginx 설정 문제를 해결하기 위한 실용적인 팁:
- 로그 활성화: 자세한 디버깅 정보를 얻기 위해 로그 레벨을 높입니다.
location /api {
proxy_pass http://localhost:8000;
# 기본 설정
# 디버깅 로그 추가
error_log /var/log/nginx/api_error.log debug;
access_log /var/log/nginx/api_access.log;
}
- 설정 테스트: 변경 사항을 적용하기 전에 항상 문법을 확인합니다.
sudo nginx -t
- curl로 테스트: 클라이언트 측 문제와 서버 측 문제를 구분하기 위해 curl을 사용합니다.
curl -v https://example.com/api
- 직접 백엔드 접근 테스트: 문제가 Nginx에 있는지 백엔드에 있는지 확인합니다.
curl -v http://localhost:8000
결론
Nginx와 FastAPI의 조합은 강력하지만, 프록시 설정 과정에서 여러 문제가 발생할 수 있습니다. 경로 재작성, 헤더 설정, CORS 설정, 그리고 타임아웃 조정은 대부분의 문제를 해결하는 핵심 요소입니다. 이러한 설정을 올바르게 구성하면 안정적이고 확장 가능한 웹 서비스를 구축할 수 있습니다.
복잡한 설정이 필요한 경우, 서브도메인 접근 방식을 고려해 보세요. 이는 설정을 단순화하고 유지 관리를 용이하게 만들어 줍니다. 무엇보다 문제가 발생했을 때 체계적인 디버깅 방법을 활용하면 빠르게 해결할 수 있습니다.
답글 남기기