- Published on
Nginx 뒤 OAuth2 리다이렉트 루프 5분 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버를 Nginx 뒤에 두고 OAuth2 로그인(Authorization Code Flow)을 붙였는데, 로그인 성공처럼 보이다가 다시 로그인 화면으로 튕기고, 브라우저 네트워크 탭에는 302가 계속 반복되는 상황이 자주 나옵니다. 이 문제는 대개 앱은 로그인됐다고 생각하지만 브라우저가 세션 쿠키를 저장하지 못하거나, 혹은 리다이렉트 URL이 프록시 환경에서 다르게 계산되어 인증 서버와 앱이 서로 다른 세계를 바라볼 때 발생합니다.
이 글은 “5분 해결”을 목표로, 가장 흔한 원인을 체크리스트 순서대로 잡아내고, Nginx와 애플리케이션 설정을 한 번에 정리합니다.
증상 패턴을 먼저 확정하기
리다이렉트 루프는 보통 아래 패턴 중 하나입니다.
GET /302Location: /oauth2/authorization/...같은 로그인 시작 URL로 이동- 인증 서버 로그인 완료 후
GET /login/oauth2/code/...콜백으로 돌아옴 - 앱이
Set-Cookie로 세션을 내려줌 - 다음 요청에서 세션 쿠키가 안 붙어서 다시 1로 돌아감
즉, 핵심은 콜백 이후 세션이 유지되는지입니다.
1분 진단: 브라우저에서 쿠키가 실제로 저장되나
Chrome DevTools 기준으로:
- Network 탭에서 콜백 응답(예:
/callback,/login/oauth2/code/...)을 클릭 - Response Headers에
Set-Cookie가 있는지 확인 - Application 탭
Cookies에서 해당 쿠키가 저장됐는지 확인
여기서 쿠키가 저장되지 않으면 거의 항상 아래 중 하나입니다.
Secure쿠키인데 브라우저가http로 인식SameSite정책에 의해 차단Domain/Path가 현재 호스트와 불일치- 프록시가
Set-Cookie를 훼손하거나, 앱이 잘못된 외부 URL로 리다이렉트
원인 1: X-Forwarded-Proto 미설정으로 스킴이 뒤집힘
Nginx는 외부에서 https로 받고 내부 업스트림에는 http로 전달하는 구성이 흔합니다. 그런데 앱이 프록시 헤더를 신뢰하지 않으면, 자기가 http로 서비스 중이라고 착각합니다.
그 결과:
- 앱이
http기준으로 리다이렉트 URL을 만들거나 - 세션 쿠키를
Secure로 내려야 하는데 조건이 맞지 않아 내려주지 않거나 - 인증 서버에 등록된 redirect URI(https)와 앱이 생성한 redirect URI(http)가 달라져 반복 시도
해결: Nginx에 Forwarded 헤더를 정확히 전달
아래는 가장 기본이면서도 효과가 큰 설정입니다.
server {
listen 443 ssl;
server_name app.example.com;
location / {
proxy_pass http://app_upstream;
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;
# 필요 시 WebSocket 등
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
중요 포인트는 X-Forwarded-Proto가 반드시 외부 스킴(대개 https)을 반영해야 한다는 점입니다.
앱도 프록시 헤더를 신뢰하도록 설정해야 함
프레임워크별로 “프록시 신뢰” 옵션이 따로 있습니다.
- Spring Boot:
server.forward-headers-strategy=framework또는 환경에 맞는 설정 - Express:
app.set('trust proxy', 1) - Django:
SECURE_PROXY_SSL_HEADER설정
앱이 이 헤더를 무시하면 Nginx 설정만으로는 반쪽 해결이 됩니다.
원인 2: OAuth2 콜백 URL이 외부 URL과 불일치
인증 서버에는 redirect URI가 등록돼 있고, 앱은 런타임에 redirect URI를 구성합니다. 프록시 뒤에서는 이 값이 흔히 어긋납니다.
대표 케이스:
- 인증 서버 등록:
https://app.example.com/oauth/callback - 앱이 생성:
http://app:8080/oauth/callback또는http://app.example.com/oauth/callback
이러면 로그인 후 돌아오긴 하지만, 다음 단계에서 다시 로그인으로 돌아가거나, 인증 서버가 다시 인증을 요구하면서 루프처럼 보입니다.
해결: 외부 기준(base URL) 명시
프레임워크에 따라 다음 중 하나로 해결합니다.
- 외부 URL을 명시하는 설정(예:
PUBLIC_URL,BASE_URL) - 프록시 헤더 신뢰 설정을 켠 후, redirect URI 구성 로직이 헤더를 반영하도록 수정
Nginx 레벨에서 호스트/스킴을 정확히 전달(Host, X-Forwarded-Proto)하는 것이 선행 조건입니다.
원인 3: SameSite, Secure 쿠키 정책으로 세션이 버려짐
OAuth2는 보통 “인증 서버 도메인”에서 “앱 도메인”으로 돌아오는 교차 사이트 흐름이 포함됩니다. 이때 쿠키 정책이 맞지 않으면 브라우저가 쿠키를 저장하지 않거나, 다음 요청에 쿠키를 붙이지 않습니다.
빠른 체크
Set-Cookie에SameSite=Strict가 붙어 있으면 콜백 이후 동작이 꼬일 수 있습니다.SameSite=None을 쓰려면 반드시Secure가 필요합니다.- 그런데 브라우저가 현재 연결을
http로 인식하면Secure쿠키는 저장되지 않습니다.
즉, 이 이슈는 종종 “원인 1의 스킴 뒤집힘”과 같이 나타납니다.
Nginx에서 쿠키 속성 보정(필요한 경우)
애플리케이션 수정이 어렵다면 Nginx에서 쿠키 속성을 리라이트할 수도 있습니다.
location / {
proxy_pass http://app_upstream;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# 업스트림이 Set-Cookie를 덜 엄격하게 주는 경우 보정
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
}
주의:
- 모든 쿠키에 일괄 적용되므로 영향 범위를 확인해야 합니다.
- 가능한 한 애플리케이션에서 세션 쿠키 정책을 올바르게 설정하는 것이 정석입니다.
원인 4: 프록시 리다이렉트가 내부 호스트로 새는 문제
업스트림이 Location: http://app:8080/... 같은 내부 주소로 리다이렉트를 내보내면, 브라우저는 접근할 수 없거나, 다시 Nginx로 돌아오면서 루프가 생깁니다.
해결: proxy_redirect 점검
기본적으로 Nginx는 일부 Location을 수정하지만, 환경에 따라 명시가 필요합니다.
location / {
proxy_pass http://app_upstream;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# 내부 리다이렉트를 외부 도메인으로 교정
proxy_redirect http://app_upstream/ https://app.example.com/;
}
또는 업스트림이 호스트 기반으로 리다이렉트를 만들도록 Host 헤더를 정확히 전달하는 것만으로 해결되기도 합니다.
5분 해결 체크리스트(이 순서대로)
- 브라우저에서 콜백 응답의
Set-Cookie확인- 저장이 안 되면
Secure/SameSite/스킴 문제 가능성 큼
- 저장이 안 되면
- Nginx에
proxy_set_header X-Forwarded-Proto $scheme;추가 - 앱에서 “프록시 헤더 신뢰” 옵션 활성화
- 인증 서버에 등록된 redirect URI와 실제 외부 콜백 URL이 완전히 동일한지 확인
- 스킴, 호스트, 포트, 경로, 트레일링 슬래시까지
- 내부 호스트로 리다이렉트가 새면
proxy_redirect또는 앱의 base URL 설정
이 다섯 단계로 대부분의 리다이렉트 루프는 끊깁니다.
재현과 확인을 위한 curl 로그 패턴
브라우저가 아니라 서버에서 빠르게 확인하려면 curl로 리다이렉트를 따라가며 헤더를 보는 방법이 좋습니다.
curl -I -L https://app.example.com/ \
-c cookies.txt -b cookies.txt
관찰 포인트:
Set-Cookie가 내려오는지- 다음 요청에서
Cookie:가 붙는지 Location:이http로 떨어지거나 내부 호스트로 떨어지지 않는지
운영 환경에서 자주 같이 터지는 Nginx 이슈들
OAuth2 루프를 잡다 보면 “프록시 뒤에서만 재현되는” 다른 문제도 함께 발견됩니다. 특히 업로드나 스트리밍과 엮이면 Nginx 기본값 때문에 증상이 더 복잡해지기도 합니다.
- 대용량 업로드가 같이 실패한다면: Nginx 413 Request Entity Too Large 업로드 실패 해결
- SSE나 스트리밍이 프록시 뒤에서 끊기며 인증 흐름도 불안정하다면: FastAPI Uvicorn에서 SSE 웹소켓 LLM 스트리밍이 프록시 뒤에서 끊길 때 Cloudflare Nginx ALB 버퍼 타임아웃 gzip으로 EventSource failed 100% 재현 해결 체크리스트
결론: 루프의 본질은 “외부 세계를 앱이 모르고 있다”
Nginx 뒤 OAuth2 리다이렉트 루프는 복잡해 보이지만, 본질은 단순합니다.
- 앱이 자기 자신을
http로 착각하거나 - 외부 호스트를 모르거나
- 그 결과 세션 쿠키가 유지되지 않거나
- redirect URI가 불일치한다
가장 먼저 X-Forwarded-Proto와 Host 전달, 그리고 앱의 프록시 신뢰 설정을 맞추면, 5분 안에 루프가 끊기는 경우가 많습니다. 이후에도 남는 문제는 쿠키 정책(SameSite, Secure)과 redirect URI 정합성을 순서대로 좁혀가면 안정적으로 해결됩니다.