서버 공통 설정에 헤더를 추가했는데 특정 경로에서만 보이지 않는다면 설정 파일에서 해당 문자열을 찾는 것만으로는 원인을 알기 어렵다. 실제 응답을 만드는 location과 응답 상태에 따라 결과가 달라지기 때문이다. 이번에는 네 개의 작은 경로를 가진 Nginx를 별도로 띄워 어느 경우에 헤더가 사라지는지 비교했다.

검사 성공과 응답 정책 충족은 다르다

설정 범위와 오류 응답 확인: 상위 범위, 하위 범위, 오류 응답
AI 생성 개념도 · 헤더는 설정 범위의 상속 조건과 응답 상태를 함께 확인해야 한다는 개념도입니다. 실제 버전별 동작은 본문 실험과 공식 문서를 따릅니다. 본문 근거와 적용 조건 · 그림을 누르면 원본 크기로 볼 수 있습니다.

실험 설정은 nginx -t 종료 코드 0으로 문법 검사를 통과했다. 서버 공통 범위에는 X-Content-Type-Options: nosniff를 설정했다. /inherited는 자체 add_header 없이 200, /overridden은 다른 이름의 헤더를 추가하고 200, /error는 404를 반환했다. /fixed는 두 헤더를 그 location에 명시하고 always를 붙여 404를 반환했다.

server {
    # listen 등은 내려받을 수 있는 전체 설정에 포함
    add_header X-Content-Type-Options nosniff;
    location = /inherited { return 200 'inherited'; }
    location = /overridden {
        add_header X-Lab-Route local;
        return 200 'overridden';
    }
    location = /error { return 404; }
    location = /fixed {
        add_header X-Lab-Route local always;
        add_header X-Content-Type-Options nosniff always;
        return 404;
    }
}
직접 관찰한 결과 · 2026-09-12
경로HTTP 상태X-Content-Type-OptionsX-Lab-Route
/inherited200nosniff없음
/overridden200없음local
/error404없음없음
/fixed404nosnifflocal

네 경로는 같은 Nginx 프로세스에서 순서대로 GET 요청했다. /inherited에는 nosniff가 있었고 /overridden에는 없었다. /error에서도 없었지만 /fixed의 404에는 있었다. HTTPS, CDN, 애플리케이션 프레임워크를 거치지 않았으므로 이 차이를 만드는 요소를 Nginx의 작은 설정 안으로 좁힐 수 있었다.

첫 번째 조건: 현재 범위에 add_header가 있는가

Nginx 공식 문서의 기본 상속 규칙은 현재 설정 단계에 add_header가 없을 때 상위 단계의 add_header를 물려받는 것이다. /overridden에 추가한 X-Lab-Route는 nosniff와 이름이 다르지만, 이 location에 자체 add_header가 생겼다는 조건은 같다. 이름이 겹치는 헤더만 교체된다고 생각하면 표의 결과를 예상하기 어렵다.

하위 location의 add_header에 always만 붙이면 상위 헤더도 돌아올까요? 2026년 10월 25일 별도 루프백 Nginx 1.18.0에서 세 경로를 모두 404로 만들었습니다. 상위 nosniff always만 물려받은 경로에는 nosniff가 있었지만, 하위에 X-Lab-Route만 always로 선언한 경로에서는 nosniff가 없었습니다. 두 헤더를 하위에 명시하고 always를 붙인 경로에서만 둘 다 나왔습니다. always의 상태 코드 조건과 add_header의 기본 상속 조건은 별개라는 추가 관찰입니다. 운영 Nginx 설정이나 서비스는 바꾸지 않았습니다. 세 경로의 실제 응답·버전·전체 실험 설정 · 자기 루프백 프로세스만 쓰는 재현 코드

두 번째 조건: 오류 응답에도 붙이는가

두 번째 조건: 오류 응답에도 붙이는가의 판단 조건을 설명하는 개념 그림
AI 생성 개념도 · 두 번째 조건: 오류 응답에도 붙이는가에 관한 설명용 그림입니다. 구체적인 조건·계산·확인 범위는 아래 본문을 따르며, 실제 화면이나 관찰·측정 자료가 아닙니다. 본문 근거와 적용 조건 · 그림을 누르면 원본 크기로 볼 수 있습니다.

add_header는 always가 없을 때 지정된 상태 코드에서만 헤더를 추가한다. 실험의 /error는 상위 선언을 물려받는 구조였지만 404라서 nosniff가 나오지 않았다. /fixed에서는 해당 location에 헤더를 명시하고 always를 붙여 404에서도 보이게 했다. 상속 문제와 응답 상태 조건을 분리해서 수정한 셈이다.

이 결과는 모든 헤더에 always를 기계적으로 붙이라는 뜻이 아니다. 예를 들어 오류 응답을 오래 저장하는 캐시 정책을 의도 없이 추가하면 다른 문제가 생길 수 있다. 각 헤더를 어느 경로와 상태에서 보내려는지 먼저 정하고, 실제 응답이 그 정책을 따르는지 비교해야 한다.

버전을 확인하고 수정 방식을 고른다

이 실험은 원자료에 기록한 Nginx 1.18.0에서 수행했다. 공식 문서에는 1.29.3부터 사용할 수 있는 add_header_inherit도 나오지만 여기서는 사용하지 않았다. 최신 문서의 구문이 현재 서버에서 지원된다고 가정하지 말고 nginx -v로 설치 버전을 확인한다. 이 글의 재현 스크립트는 기존 시스템 설정을 reload하지 않는다.

실제 사이트에서는 대표 응답을 나눠 검사한다

# 주소는 자신의 검사 대상으로 바꾼다. -D -는 헤더 출력, -o는 본문 저장 위치다.
curl -sS -D - -o /dev/null https://example.com/
curl -sS -D - -o /dev/null https://example.com/known-missing-page
curl -sS -D - -o /dev/null https://example.com/assets/existing.css

정상 HTML 하나만 확인하면 404나 정적 자산에만 적용된 차이를 놓칠 수 있다. 위 명령의 없는 페이지는 실제로 없는 주소인지 확인하고, CSS는 실제 존재하는 주소를 선택한다. 리디렉션을 따라가는 옵션을 넣지 않았으므로 301이 보이면 Location 목적지를 별도로 검사해야 한다.

관찰한 차이먼저 볼 부분
특정 location에서만 헤더가 없음하위 add_header와 최종 선택된 location
정상 응답에는 있고 오류에는 없음상태 코드 조건과 always 사용 여부
원본에는 있는데 공개 주소에서 다름프록시·CDN의 응답 변경 또는 저장 상태
설정 검사만 성공함실제 GET 응답과 기대 헤더를 추가 대조

공개 주소에 프록시가 있다면 원본 응답과 공개 응답이 같은지 구분해야 한다. 이번 관찰은 루프백 원본만 대상으로 했기 때문에 Cloudflare나 다른 CDN이 헤더를 변경하는 동작을 측정하지 않았다. 원본 확인이 끝난 뒤 실제 독자가 접근하는 주소에서도 필요한 응답을 확인하는 것이 다음 단계다.

편집자 의견

헤더 정책은 설정 파일에서 원하는 문장을 찾는 것보다 대표 응답을 직접 비교하는 쪽을 권합니다. 하위 범위의 add_header와 오류 응답의 적용 조건이 달라지면 문법 검사는 통과해도 기대한 정책이 빠질 수 있었습니다.

정상 HTML 한 곳만 확인하고 끝내지 않고 실제 사용하는 자산·프록시·오류 경로를 표본에 넣겠습니다. 헤더의 이름이 존재하는지와 그 값이 해당 응답에 맞는지도 따로 확인합니다.

판단 근거: 첫 번째 조건: 현재 범위에 add_header가 있는가

의견 추가 2026-10-23 · 본문 자료를 바탕으로 한 편집 판단

실험 환경과 원자료

관찰 시각: 2026-09-12T15:07:44.600003+00:00
os: Linux · python: 3.10.12 · nginx: nginx version: nginx/1.18.0 (Ubuntu)

별도 임시 디렉터리, 루프백 Nginx와 합성 응답 서버에서 순차 요청. 운영 설정과 외부 API는 사용하지 않음.

확인하지 않은 범위: 단일 실행. CDN, 브라우저 캐시, 동시 부하, 인증, NFS, 디스크 장애, 운영 데이터는 시험하지 않음.

Python 3이 있는 별도 Linux 실험 환경에서 실행합니다. Nginx 실험에는 Nginx도 필요합니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.

python3 lab-nginx-boundaries.py --output new-observations.json

참고한 공식 문서

원고 수정: 2026-10-24 · 정정 요청

이 글의 보완 기록

  • · 문서·404·자산별 기대 헤더를 대조하는 검수표를 추가했다.
  • · 본문에서 이미 설명한 정상·오류·자산 검사표의 반복을 삭제했습니다. 상속과 always의 두 원인, 버전 조건, 실제 요청 절차를 유지했습니다.
  • · 본문 근거에 대한 편집자의 판단과 결론이 달라지는 조건을 추가했습니다. 기존 관찰일·공식 자료 확인일은 유지했습니다.
  • · 글별 적용 사례와 반례를 보강했습니다. 기존 관찰일과 원자료 확인일은 유지했습니다.