파일에 적힌 순서가 아니다. 정해진 우선순위로 고른다.
location = /exact { } # ① 정확히 일치. 맞으면 즉시 확정
location ^~ /images/ { } # ② 접두사 + 정규식 검사 생략
location ~ \.php$ { } # ③ 정규식 (대소문자 구분)
location ~* \.(jpg|png)$ { } # ④ 정규식 (대소문자 무시)
location /docs/ { } # ⑤ 일반 접두사
location / { } # ⑥ 모든 것에 맞는 접두사
매칭 순서
- '=' 정확히 일치가 있으면 → 즉시 확정. 끝
- 접두사 중 '가장 긴 것' 을 기억해 둔다
- 그 접두사가 '^~' 였다면 → 정규식을 건너뛰고 확정
- 아니면 정규식을 '파일에 적힌 순서대로' 검사해 첫 일치를 쓴다
- 정규식에 하나도 안 맞으면 → 2번에서 기억해 둔 접두사를 쓴다
접두사끼리는 '길이' 로 경쟁하고, 정규식끼리는 '순서' 로 경쟁한다 그리고 정규식이 접두사를 이긴다 (^~ 로 막지 않는 한)
실제로 물리는 예
location /static/ { root /var/www; }
location ~ \.js$ { deny all; } # 의도: 특정 경로의 js 차단
# /static/app.js 요청 →
# 접두사 /static/ 도 맞고 정규식 \.js$ 도 맞는다
# 정규식이 이기므로 → 403
# /static/ 아래 js 가 통째로 막힌다
# 고치려면 — 정적 경로에서 정규식 검사를 건너뛰게 한다
location ^~ /static/ { root /var/www; }
= 를 쓰면 빠르다
location = / { ... }
루트 요청처럼 '정확히 하나' 로 확정되는 경로는 = 로 두면 나머지 매칭 과정을 건너뛴다. 트래픽이 몰리는 경로에서 의미가 있다
헬스체크 엔드포인트도 = 로 두는 편이 좋다
- location = /healthz { access_log off; return 200; }
try_files 와의 조합
location / {
try_files $uri $uri/ /index.html; # SPA 라우팅
}
왼쪽부터 차례로 찾고, 없으면 마지막으로 넘긴다 SPA 에서 새로고침 시 404 가 나는 문제를 이 한 줄이 푼다
주의 — 마지막 인자를 '=404' 로 두지 않고 파일명으로 두면
- 존재하지 않는 API 경로까지 index.html 을 돌려준다
- API 는 별도 location 으로 먼저 분리한다
named location 과 내부 리다이렉트
location / {
try_files $uri @fallback;
}
location @fallback { # '@' 는 외부에서 직접 접근할 수 없다
proxy_pass http://backend;
}
error_page 와 함께 쓰면 오류 처리를 한곳에 모을 수 있다
-
error_page 502 503 504 = @maintenance;
-
internal; — 을 붙이면 내부에서만 접근 가능하다
- 점검 페이지 · 인증 서브요청 대상에 쓴다
매칭 확인이 안 될 때
어느 location 이 잡혔는지 로그로 확인한다
log_format dbg '$request_uri -> $uri (loc)'; 또는 임시로 각 location 에 서로 다른 헤더를 붙인다 add_header X-Loc "static" always;
error_log 를 debug 레벨로 올리면 매칭 과정이 전부 찍힌다
- error_log /var/log/nginx/debug.log debug;
- 운영에서는 로그가 폭증하므로 잠깐만 켠다 (디버그 빌드 필요)
면접 함정
- ❌ "위에서부터 순서대로 매칭한다" → 접두사는 길이, 정규식만 순서다.
- ❌ "접두사가 더 구체적이면 이긴다" → 정규식이 우선한다.
^~로 막아야 한다.