본문 중간의 쿠팡 추천 상품 구매시 쿠팡 파트너스에서 일정액의 수수료를 제공받습니다.

Nginx proxy_next_upstream은 프록시 요청이 한 업스트림 서버에서 실패했을 때 다른 서버로 넘겨 볼 조건을 정하는 지시어다. 단순한 장애 자동 복구 옵션처럼 보이지만, 실제 기준은 더 좁다. 어떤 실패를 재시도로 볼지, 응답을 이미 클라이언트에 보냈는지, 쓰기 요청을 다시 보내도 안전한지까지 함께 봐야 한다.
핵심 기준은 세 가지다. 첫째, error와 timeout 같은 네트워크 성격의 실패와 HTTP 상태 코드 기반 실패를 구분한다. 둘째, 클라이언트로 응답이 이미 전송되기 시작한 뒤에는 다음 업스트림으로 복구할 수 없다는 제한을 이해한다. 셋째, proxy_next_upstream_tries와 proxy_next_upstream_timeout으로 재시도 범위를 제한한다.

proxy_next_upstream은 무엇을 결정하나?
Nginx 공식 ngx_http_proxy_module 문서 기준으로 proxy_next_upstream은 요청을 다음 서버로 넘길 조건을 정의한다. 기본값은 error timeout이다. 즉 업스트림과 연결하거나, 요청을 보내거나, 응답 헤더를 읽는 과정에서 오류나 시간 초과가 나면 다음 서버를 시도할 수 있다.
location /api/ {
proxy_pass http://app_backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 5s;
}
이 설정은 "무조건 성공할 때까지 계속 재시도한다"는 뜻이 아니다. 조건에 맞아야 하고, 시도 횟수와 시간 제한 안에 있어야 하며, 무엇보다 Nginx가 아직 클라이언트에 응답 일부를 보내지 않은 상태여야 한다.
응답을 이미 보냈다면 왜 복구할 수 없나?
Nginx 문서는 다음 서버로 넘기는 일이 가능한 조건을 분명히 둔다. 클라이언트에 아무것도 보내지 않은 경우에만 다음 서버로 넘길 수 있다. 응답 본문을 전송하는 중간에 오류나 timeout이 발생했다면, 이미 시작된 응답을 다른 업스트림 응답으로 자연스럽게 바꾸는 것은 불가능하다.
이 제한은 운영에서 중요하다. 업스트림 장애가 모두 재시도로 감춰질 것이라고 기대하면, 큰 파일 다운로드나 스트리밍 응답에서 중간 실패를 잘못 해석할 수 있다. 재시도 정책은 응답 시작 전의 실패를 다루는 장치로 보는 편이 정확하다.
어떤 실패가 재시도 대상인가?
공식 문서에 따르면 error, timeout, invalid_header는 지시어에 명시하지 않아도 항상 실패한 시도로 계산된다. 반면 http_500, http_502, http_503, http_504, http_429는 proxy_next_upstream에 명시했을 때만 실패한 시도로 계산된다. http_403과 http_404는 실패한 시도로 계산되지 않는다.

| 조건 | 의미 | 실무 판단 |
|---|---|---|
error |
연결, 요청 전송, 응답 헤더 읽기 중 오류 | 기본값에 포함된다. 업스트림 장애 재시도의 기본 축이다. |
timeout |
연결, 요청 전송, 응답 헤더 읽기 중 시간 초과 | 기본값에 포함된다. upstream timeout 설정과 함께 봐야 한다. |
invalid_header |
업스트림이 유효하지 않은 응답을 보냄 | 항상 실패한 시도로 계산된다. |
http_502, http_503, http_504 |
명시한 상태 코드 응답 | 재시도할 상태 코드만 좁게 고르는 편이 안전하다. |
http_429 |
요청 과다 응답 | 다른 서버로 넘기는 것이 정책상 맞는지 먼저 확인해야 한다. |
상태 코드 기반 재시도는 특히 조심해야 한다. 예를 들어 한 업스트림이 503을 반환했을 때 같은 요청을 다른 업스트림에 보내도 안전한지는 애플리케이션의 상태 공유, 세션 처리, 중복 처리 방식에 따라 달라진다.
POST도 다시 보내도 될까?
Nginx 문서는 비멱등 요청에 대한 별도 기준을 둔다. 요청이 이미 업스트림 서버로 전송된 경우, 일반적으로 POST, LOCK, PATCH 같은 비멱등 메서드는 다음 서버로 넘기지 않는다. 이 동작을 바꾸려면 non_idempotent 파라미터를 명시해야 한다.
이 기본값은 보수적이다. 결제, 주문, 글 작성, 상태 변경 API처럼 한 번만 처리되어야 하는 요청은 첫 번째 업스트림에서 실제 처리가 되었는지 프록시가 완전히 알기 어렵다. 이런 요청을 자동으로 다른 서버에 다시 보내면 중복 처리 위험이 생긴다.

location /readonly/ {
proxy_pass http://app_backend;
proxy_next_upstream error timeout http_502 http_503;
}
location /write/ {
proxy_pass http://app_backend;
proxy_next_upstream error timeout;
proxy_next_upstream_tries 1;
}
읽기 요청과 쓰기 요청은 같은 정책으로 묶지 않는 편이 좋다. 읽기 경로는 장애 시 짧게 다른 서버를 시도할 수 있지만, 쓰기 경로는 애플리케이션의 idempotency key, 트랜잭션 처리, 중복 요청 방어가 준비되어 있을 때만 재시도를 넓히는 것이 안전하다.
tries와 timeout은 왜 같이 봐야 하나?
proxy_next_upstream_tries는 다음 서버로 넘길 수 있는 시도 횟수를 제한한다. 기본값 0은 이 제한을 끈다는 뜻이다. proxy_next_upstream_timeout도 기본값 0이면 시간 제한을 두지 않는다. 운영에서는 둘 중 하나 이상을 명시해 재시도가 과하게 길어지는 상황을 피하는 편이 낫다.
예를 들어 업스트림 서버가 5대이고 각 서버의 응답 헤더 timeout이 길다면, 제한 없는 재시도는 사용자 응답 지연을 크게 늘릴 수 있다. 반대로 너무 짧게 잡으면 일시적인 서버 교체나 rolling deploy 중에 복구 기회를 충분히 주지 못할 수 있다. 기준은 사용자에게 허용할 총 대기 시간과 요청의 중복 처리 위험이다.
upstream max_fails와는 무엇이 다를까?
proxy_next_upstream은 현재 요청을 다음 서버로 넘길지 정하는 위치에 가깝다. 반면 upstream 서버 설정의 max_fails와 fail_timeout은 특정 서버를 일정 시간 사용할 수 없는 대상으로 볼지와 관련된다. 두 설정은 함께 작동할 수 있지만 같은 의미는 아니다.
문제 분석에서는 "이번 요청이 다음 서버로 넘어갔는가"와 "특정 upstream peer가 잠시 제외되었는가"를 나눠 봐야 한다. access log에 upstream 주소와 상태 코드를 남기면 이 차이를 확인하기 쉬워진다.
FAQ
proxy_next_upstream off는 언제 쓰나?
자동 재시도 자체를 막고 싶을 때 쓴다. 쓰기 요청처럼 중복 전송이 위험하거나, 애플리케이션이 실패를 직접 처리해야 하는 경로에서는 더 명확한 선택일 수 있다.
모든 5xx를 재시도하면 가용성이 좋아지나?
항상 그렇지는 않다. 여러 업스트림이 같은 데이터베이스나 외부 API에 의존한다면 다른 서버로 넘겨도 같은 실패가 반복될 수 있다. 또한 쓰기 요청에서는 중복 처리 위험이 커질 수 있다.
http_404를 넣으면 실패 서버로 계산되나?
Nginx 문서 기준으로 http_403과 http_404는 실패한 시도로 계산되지 않는다. 이 상태 코드는 보통 서버 장애보다 애플리케이션 응답으로 보는 편이 자연스럽다.
정리
Nginx proxy_next_upstream의 기준은 "실패하면 다음 서버"보다 좁다. 응답을 아직 보내지 않았고, 설정한 조건에 맞으며, 요청을 다시 보내도 안전할 때만 의미가 있다. 특히 비멱등 요청은 기본적으로 보수적으로 다루는 편이 맞다.
운영에서는 읽기와 쓰기 경로를 나누고, 재시도할 HTTP 상태 코드를 좁게 고르며, tries와 timeout으로 상한을 둔다. 이렇게 잡으면 재시도는 장애를 숨기는 장치가 아니라, 짧은 업스트림 실패를 제한된 범위에서 흡수하는 정책이 된다.
참고 문서
'프로그래밍 > 서버, DBMS' 카테고리의 다른 글
| SQLite STRICT tables 기준 (0) | 2026.10.04 |
|---|---|
| PostgreSQL CTE materialization 기준 (0) | 2026.09.29 |
| PostgreSQL HOT update 기준 (0) | 2026.09.26 |
| Nginx HTTP/3 기준 (0) | 2026.09.22 |
| PostgreSQL WITHOUT OVERLAPS 기준 (0) | 2026.09.20 |





