nginx 정규식 location 블록이 유발한 trailing slash
목차
nginx 설정 파일 하나를 건드렸는데, 그게 꽤 오래된 지뢰였다는 걸 이번에 확인했다.
무슨 일이었냐면
deploy/nginx-job.conf 안에 아래 같은 location 블록이 있었다.
location ~* \.html$ {
# ... 정적 파일 처리 로직
}
문제는 이 정규식 location이 trailing slash 없는 디렉토리 요청을 중간에 가로채고 있었다는 것이다. 예를 들어 /some/path로 요청이 들어오면 nginx는 내부적으로 이게 디렉토리인지 파일인지 판단하는 과정을 거치는데, 이 ~* \.html$ 블록이 우선순위 경쟁에 끼어들면서 의도치 않게 매칭 흐름을 꼬아버린 케이스였다.
결과는 단순했다. /some/path 요청 → 404. /some/path/ 요청 → 정상.
이런 류의 버그는 로컬에서 재현이 잘 안 된다. 개발 환경은 대부분 webpack dev server나 별도 serve 툴을 쓰고, nginx를 직접 태우는 건 스테이징이나 프로덕션 배포 이후인 경우가 많기 때문이다. 실제로 이번에도 "왜 특정 경로만 404 나냐"는 제보가 먼저 들어왔고, 로그를 뒤지다가 nginx conf까지 파고들었다.
nginx location 매칭 우선순위, 다시 짚고 넘어가자
nginx location 매칭에는 우선순위 규칙이 있고, 이걸 모르면 conf 수정이 쌓일수록 예측 불가한 동작이 나온다.
| 타입 | 예시 | 우선순위 |
|---|---|---|
= (exact match) |
location = /foo |
최우선 |
^~ (prefix, 정규식 중단) |
location ^~ /static/ |
두 번째 |
~ / ~* (정규식) |
location ~* \.html$ |
세 번째 (선언 순서) |
| prefix (일반) | location /foo |
가장 낮음 |
여기서 포인트는 정규식 location은 선언 순서대로 최초 매칭이 적용된다는 것. \.html$은 .html로 끝나는 URL만 잡아야 할 것 같지만, 내부 rewrite나 try_files 흐름에서 예상치 못한 경로가 이 블록에 떨어질 수 있다. 특히 trailing slash 없는 디렉토리 요청은 nginx가 index.html을 찾으러 가는 과정에서 내부적으로 .html suffix를 붙인 경로로 처리를 시도하는 케이스가 있고, 그게 이 정규식과 충돌한 것으로 추정했다.
수정은 단순했다. 해당 location 블록 자체를 제거했다. 이미 try_files와 상위 prefix location으로 정적 파일 서빙이 충분히 커버되고 있었기 때문에, 이 블록이 왜 처음에 추가됐는지 이력을 추적해봤는데 명확한 이유를 찾기 어려웠다. 아마 예전에 캐싱 헤더나 mime type 처리용으로 추가했다가 그 이후 구조가 바뀌면서 유령 블록으로 남은 것 같다.
팀 입장에서 남기고 싶은 것
nginx conf는 코드리뷰가 소홀해지기 쉬운 영역이다. "인프라 담당자가 보겠지"라는 암묵적 분리가 생기고, 결과적으로 변경 히스토리가 불투명해진다.
이번 건을 계기로 몇 가지를 정리했다.
- nginx conf도 PR 리뷰 대상에 명시적으로 포함시킨다
- location 블록 추가/변경 시 왜 이 블록이 필요한지 주석을 남기도록 컨벤션화
- 스테이징 배포 후 trailing slash 유무 양쪽 경로를 기본 스모크 테스트 항목에 넣는다
작은 conf 수정이지만, trailing slash 하나 때문에 사용자 요청이 404로 떨어지는 건 조용하지만 꽤 임팩트 있는 장애다. 특히 외부에서 URL을 직접 입력하거나 링크를 공유하는 케이스에서 슬쩍 발생하기 때문에, 모니터링에 잘 안 잡히고 제보 의존도가 높아진다는 것도 문제다.
conf 파일 하나 손댈 때도 "이게 어떤 흐름을 타는지"를 그려보는 습관, 계속 가져가야겠다고 느꼈다.
다음엔 이번에 같이 정리한 스모크 테스트 체크리스트 쪽을 정리해볼 것 같다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.