Skip to content
블로그로 돌아가기
튜토리얼

Nginx location 우선순위: 매칭 순서 완벽 정리

nginx가 location 블록을 고르는 실제 순서: =, ^~, ~와 접두사 매칭 우선순위, 흔한 함정까지. 무료 온라인 테스터 제공.

12분 소요

Nginx location 우선순위: 매칭 순서 완벽 정리

nginx는 location 블록을 위에서 아래로 읽다가 처음 들어맞는 블록에서 멈추지 않습니다. 의도대로 동작하지 않는 location 블록 때문에 올라오는 버그 리포트는 대개 이 한 가지 오해에서 나옵니다. prefix location은 파일 안에서 어디에 놓이든 순서가 결과에 전혀 영향을 주지 않습니다. nginx는 전부 비교한 뒤 가장 긴 일치를 남깁니다.

실제 nginx location 우선순위는 고정된 네 단계입니다.

  1. 완전 일치. URI와 똑같은 location = /path가 있으면 nginx는 그 블록을 사용하고 탐색을 끝냅니다. prefix 비교도, regex 평가도 하지 않습니다.
  2. 가장 긴 prefix. URI가 그 문자열로 시작하는 모든 prefix location(location /pathlocation ^~ /path)을 비교합니다. 이 중 가장 긴 것은 아직 사용되지 않고 후보로 기억되기만 합니다.
  3. ^~ 단축 회로. 기억된 prefix에 ^~가 붙어 있으면 nginx는 regex 단계를 통째로 건너뛰고 그 블록을 사용합니다.
  4. regex, 파일에 적힌 순서대로. 그렇지 않으면 ~~* location을 설정 파일에 등장하는 순서대로 시도하고, 가장 먼저 일치한 블록이 이깁니다. 하나도 일치하지 않으면 2단계에서 기억해 둔 prefix가 쓰입니다.

이 중 두 규칙은 서로 반대 방향으로 작동합니다. prefix는 순서와 무관하게 길이로 뽑히고, regex는 길이와 무관하게 순서로 뽑힙니다. 설정 파일을 위에서 아래로 훑어서는 이 충돌이 드러나지 않습니다. 아래 예제가 아니라 직접 쓰고 있는 파일의 답이 궁금하다면 무료 nginx location 테스터에 붙여 넣어 보세요. 이 순서를 그대로 재현하면서 탈락한 블록이 어느 단계에서 떨어졌는지 보여 줍니다. 브라우저 안에서만 동작하므로 붙여 넣은 운영 설정은 페이지 밖으로 나가지 않습니다. 여기 적힌 매칭 규칙은 모두 2차 자료에서 옮겨 온 것이 아니라 실행 중인 nginx 1.27.5로 직접 확인한 것입니다.

한눈에 보는 nginx location 우선순위

URI 매칭에 참여하는 수식어는 다섯 가지이고, 참여하지 않는 형태가 하나 더 있습니다.

수식어문법매칭 기준regex 단계 중단주요 용도
=location = /path완전 일치//favicon.ico 같은 고빈도 경로
^~location ^~ /path시작 문자열가장 긴 prefix일 때만 예regex로 절대 넘어가면 안 되는 디렉터리
~location ~ regexPCRE, 대소문자 구분아니요대소문자가 중요한 확장자 라우팅
~*location ~* regexPCRE, 대소문자 무시아니요대소문자를 따지지 않는 확장자 라우팅
(없음)location /path시작 문자열아니요경로 기반 일반 라우팅
@location @nameURI로는 매칭되지 않음error_pagetry_files의 점프 대상

완전 일치가 모든 것을 이기고, regex가 prefix를 이기고, prefix끼리는 길이로 겨룹니다. 유일한 예외가 ^~인데 적용 범위가 생각보다 훨씬 좁습니다.

그래서 위 표는 아주 느슨한 의미에서만 순위표입니다. 표에서는 ^~~보다 위에 있지만, 실제로는 ^~ 블록이 regex에 지는 일이 흔합니다. 이 수식어는 길이 경쟁에서 이미 이긴 prefix에 대해서만 확인되기 때문입니다.

네 단계 선택 알고리즘

nginx location 매칭 순서는 모든 규칙을 한 번에 건드리는 설정으로 익히는 것이 가장 빠릅니다. 아래는 nginx 공식 문서에 실린 예제이고, 통째로 외워 둘 만합니다.

server {
    location = /                   { return 200 "A\n"; }
    location /                     { return 200 "B\n"; }
    location /documents/           { return 200 "C\n"; }
    location ^~ /images/           { return 200 "D\n"; }
    location ~* \.(gif|jpg|jpeg)$  { return 200 "E\n"; }
}

요청 다섯 개에 답이 다섯 개입니다.

요청승자이유
/A완전 일치. 탐색이 즉시 끝납니다.
/index.htmlB일치하는 regex가 없어 기억해 둔 prefix가 쓰입니다.
/documents/document.htmlC/보다 긴 prefix입니다.
/images/1.gifD^~가 prefix 단계에서 이겨 regex가 아예 실행되지 않았습니다.
/documents/1.jpgE^~가 없는, 더 긴 prefix를 regex가 이겼습니다.

마지막 두 줄을 비교해 보세요. /images//documents/는 둘 다 prefix이고, 둘 다 일치하며, 각자의 요청에 대해 가장 긴 일치입니다. 그런데 한 요청은 prefix 블록으로 가고 다른 요청은 regex로 갑니다. 두 줄에서 다른 것은 ^~ 두 글자뿐입니다.

여기서 가장 자주 놓치는 것이 2단계입니다. nginx는 가장 긴 prefix를 사용하는 것이 아니라 기억해 둡니다. 그 블록은 후보일 뿐이고, regex 단계가 요청을 가져가 버릴 수 있습니다. 탐색을 끝내는 단계는 1, 3, 4단계뿐입니다.

”가장 긴 prefix”를 경로 세그먼트가 아니라 글자 수로 재는 이유

prefix 비교는 단순 문자열 비교입니다. /가 경로 세그먼트를 나눈다는 사실을 모르고, 경계에서 멈추지도 않습니다. 설정 두 줄이면 바로 드러납니다.

server {
    location /static  { }
    location /static/ { }
}

/staticfoo 요청은 location /static이 처리합니다. URI가 그 일곱 글자로 시작하니 일치하기 때문입니다. /static/은 그 자리에 슬래시가 없으므로 아예 일치하지 않습니다. /static/x 요청은 반대로 둘 중 더 긴 /static/을 가져갑니다.

그래서 location /static/static-backup, /staticfiles처럼 같은 글자로 시작하기만 하는 경로까지 전부 함께 가져갑니다. 디렉터리를 의도했다면 끝에 슬래시를 붙이고, 슬래시 없는 경로용 완전 일치 location을 따로 추가하세요.

server {
    location /static/ { root /var/www; }
    location = /static { return 301 /static/; }
}

대부분의 튜토리얼은 이 문제가 드러나지 않는 깔끔한 경로만 예로 들기 때문에, 코드 리뷰에서도 그대로 통과하는 일이 잦습니다. nginx location 테스터는 일치한 블록을 전부 보여 주고 각 블록이 몇 글자를 일치시켰는지도 같이 보여 주므로, 형제 경로까지 삼키는 prefix를 눈으로 확인할 수 있습니다.

^~가 실제로 하는 일, 하지 않는 일

nginx location ^~ 수식어는 흔히 “이 블록이 regex보다 우선한다”고 설명됩니다. 위험할 만큼 그럴듯한 설명입니다.

^~는 prefix 비교 단계에서 아무 일도 하지 않습니다. 일치 길이를 늘려 주지도, 어떤 prefix가 기억될지를 바꾸지도 않습니다. 길이 경쟁에서 이미 이긴 단 하나의 prefix에 대해 그 뒤에 확인될 뿐입니다. 그 승자에 ^~가 붙어 있으면 regex 단계를 건너뛰고, 붙어 있지 않으면 regex 단계가 평소대로 실행됩니다.

즉, 수식어 없는 더 긴 prefix 하나가 ^~를 조용히 무력화합니다.

server {
    location ^~ /a/    { }
    location /a/b/     { }
    location ~ \.php$  { }
}

/a/b/x.php 요청은 ~ \.php$가 처리합니다. 일치하는 prefix 중 가장 긴 것은 /a/b/이므로 nginx는 그 블록을 기억하고, 수식어가 없으니 regex 단계가 그대로 진행되며, regex가 먼저 일치해 요청을 가져갑니다. ^~ 블록은 파일에 그대로 남아 여전히 무언가를 지켜 주는 것처럼 보이지만, 이 요청에는 아무 영향도 주지 않습니다.

URI를 /a/x.php로 바꾸면 같은 설정이 전혀 다르게 동작합니다. 이번에는 ^~ /a/가 가장 긴 일치라서 regex 단계가 생략되고 ^~ 블록이 이깁니다. 같은 파일, 같은 모양의 요청인데 결과는 정반대입니다.

탁상공론이 아닙니다. ^~의 대표적인 용도는 쓰기 가능한 디렉터리를 인터프리터에서 떼어 놓는 것입니다.

server {
    location ^~ /uploads/ { }
    location ~ \.php$     { fastcgi_pass unix:/run/php-fpm.sock; }
}

^~를 빼면 /uploads/evil.php 요청이 곧장 PHP-FPM으로 넘어갑니다. 업로드 취약점이 RCE로 이어지는 오래된 사고 유형이 바로 이 모양입니다. 앞에서 본 “무력화된 ^~” 사례가 중요한 이유도 여기에 있습니다. location /uploads/thumbs/ 같은 더 긴 일반 prefix를 하나 추가하면 그 아래 전체가 다시 열리는데, 그렇게 만드는 diff는 아무 문제 없어 보입니다.

^~가 억제할 수 있는 범위에도 한계가 있습니다. 자기가 놓인 레벨에 선언된 regex까지입니다. 자기 블록 안에 중첩된 regex는 절대 억제하지 못하고, 중첩 location에 붙인 ^~로는 server 레벨에 선언된 regex를 막을 수 없습니다. nginx location 테스터에서 수식어를 켜고 끄면서 승자가 어떻게 바뀌는지 확인해 보세요. 건너뛴 regex를 표에서 지워 버리지 않고 건너뛰었다고 표시해 줍니다.

regex location: 순서가 정밀도를 이깁니다

nginx location regex는 대소문자를 구분할 때 ~, 구분하지 않을 때 ~*를 씁니다. 반면 prefix 매칭은 Linux에서 언제나 대소문자를 구분합니다. (macOS처럼 대소문자를 구분하지 않는 파일 시스템에서는 nginx가 prefix도 대소문자 없이 비교하고, 모든 regex location을 ~*처럼 동작하게 만듭니다. Mac에서 개발해 Linux에 배포한다면 이 차이 때문에 잘못된 규칙이 배포 직전까지 숨어 있을 수 있습니다.)

정작 발목을 잡는 규칙은 순서입니다. regex는 설정 파일에 등장하는 순서대로 평가되고, 처음 일치한 곳에서 탐색이 끝납니다. 정밀도도, 길이도, 앵커도 순서에는 아무 영향을 주지 않습니다.

server {
    location ~ ^/a       { }
    location ~ ^/a/b/c$  { }
}

/a/b/c 요청은 ~ ^/a가 가져갑니다. 두 번째 블록은 URI와 정확히 일치하고 훨씬 더 정밀하지만, 어떤 요청에 대해서도 실행되지 않습니다. nginx -t가 아무 말 없이 통과시키는 죽은 설정입니다.

그래서 regex는 정밀한 것부터 느슨한 것 순으로 배치하고, 목록은 짧게 유지하는 편이 낫습니다. 위쪽에 넓은 패턴이 하나 있으면 그 아래 전부가 도달 불가능해지는데, 줄 순서만 바꾼 diff는 무해해 보이므로 이런 회귀는 기능 개발보다 정리 작업에서 들어오는 경우가 많습니다.

앵커에도 같은 주의가 필요합니다. location ~ /admin은 양쪽 어디에도 고정되어 있지 않아서 URI 안 어디든 훑고, /public/admin/x에도 그대로 일치합니다. 시작을 뜻했다면 ~ ^/admin이라고 쓰세요. ~ \.php$처럼 끝에만 앵커를 두는 것은 확장자 라우팅에서 올바른 사용입니다.

자바스크립트(JavaScript)식 감각으로는 틀리기 쉬운 PCRE 세부 사항 몇 가지입니다.

  • nginx는 location 패턴을 PCRE로 컴파일하는데, UTF 모드도 multiline 모드도 켜지 않습니다. 그래서 패턴은 바이트 단위로 동작하고 ^는 URI의 시작에만 고정됩니다.
  • PCRE의 $는 끝에 붙은 개행 문자 바로 앞에도 일치합니다. %0A로 끝나는 URI가 여전히 \.php$를 만족하는데, 파일 확장자를 기준으로 만든 규칙을 빠져나가는 알려진 수법입니다.
  • 자바스크립트에 대응물이 없는 문법도 PCRE에는 흔합니다. atomic group (?>…), 소유 수량자 a*+, (?i) 같은 인라인 수식어, [[:alpha:]] 같은 POSIX 문자 클래스, \A, \z, \K, \Q…\E 같은 이스케이프가 그렇습니다.
  • {}가 들어간 패턴은 따옴표로 감싸야 합니다. location ~ ^/a{2}$는 중괄호에서 토큰이 끊기는 탓에 unknown directive "2}$" 오류를 내며 로드에 실패합니다. location ~ "^/a{2}$"라고 쓰세요.

캡처 그룹은 기대하는 대로 동작하고, 블록 안에서 $1부터 차례로 쓸 수 있습니다.

upstream backend {
    server 127.0.0.1:8080;
}

server {
    location ~ ^/user/(\d+)/profile$ {
        proxy_pass http://backend/profiles/$1;
    }
}

파일 안에서의 위치가 아니라 패턴 자체를 아직 다듬는 중이라면 정규식 테스터에서 먼저 시험해 보세요. 문법은 정규식 치트시트에서 깊이 있게 다룹니다.

모두가 건너뛰는 단계: URI 정규화

location을 하나라도 살펴보기 전에 nginx는 요청 대상(request target)을 먼저 고쳐 씁니다. 설정에 적은 패턴은 회선으로 들어온 바이트가 아니라 정규화된 경로와 비교됩니다. location이 매칭되지 않는 사례의 상당수가 여기서 갈립니다.

정규화는 네 가지 일을 합니다. 쿼리 문자열을 떼어 내고, 경로를 퍼센트 디코딩하고, ... 세그먼트를 해석하고, 반복된 슬래시를 하나로 합칩니다.

요청 대상정규화된 $uri설명
//a//x/a/x반복된 슬래시가 합쳐집니다
/a/../b/x/b/x매칭 전에 ..가 해석됩니다
/a/b%2F..%2Fzz/a/zz%2F가 진짜 구분자로 디코딩되어 경로 해석에 참여합니다
/a/%2e%2e/b/x/b/x%2E도 점으로 디코딩되어 함께 해석됩니다
/a%20b/x/a b/x%20이 진짜 공백 문자가 됩니다
/a+b/x/a+b/x경로에서 +는 공백 문자가 아닙니다
/a?x=/b/a쿼리 문자열이 먼저 분리됩니다
/a%3Fx=1/a?x=1%3F는 그대로 남고 쿼리 문자열은 비어 있습니다

디코딩 결과가 예외로 처리되는 문자가 셋 있습니다. %25, %23, %3F는 디코딩된 뒤 다시 해석되지 않고 그대로 기록됩니다. /a%3Fx=1이 경로 안에 물음표를 품은 채 $args는 비어 있는 이유가 이것입니다.

location 선택까지 아예 도달하지 못하는 요청도 둘 있습니다. 루트 위로 올라가는 .. 세그먼트와 %00 같은 잘못된 이스케이프는 매칭이 시작되기 전에 400으로 거부됩니다.

location 블록을 접근 제어 경계로 쓰고 있다면 이 정규화가 곧바로 보안 문제가 됩니다. 작성한 경로는 원본이 아니라 해석이 끝난 경로와 비교되기 때문입니다.

server {
    location /a/ { }
    location /b/ { }
}

/a/b%2F..%2Fzz 요청은 /a/b/ 아래에 머물지 않습니다. /a/zz로 정규화되어 location /a/가 처리합니다. $uri가 아니라 원본 요청 대상을 기준으로 추론하면 여기서 답이 틀리고, 접근 제어 맥락에서 그 오판은 곧 우회 취약점입니다. location /admin으로 무언가를 지키려 한다면 그 전에 정규화된 경로가 실제로 무엇인지 확인하세요. nginx location 테스터는 원본 요청 대상, 정규화된 $uri, 떼어 낸 쿼리 문자열을 각각 별도의 행으로 보여 줍니다. 인코딩 자체만 따로 따져 보고 싶다면 URL 인코더 디코더가 그 부분만 처리해 줍니다.

정규화 단계에서 따라 나오는 결과가 하나 더 있습니다. 쿼리 문자열은 매칭에 전혀 관여하지 않습니다. location /search?q=/search?q=1 요청과 일치할 수 없습니다. 선택 단계가 보는 것은 /search뿐이기 때문입니다. 파라미터에 따라 분기하려면 블록 안에서 $arg_name을 읽으세요.

생각대로 동작하지 않는 설정 다섯 가지

더 긴 일반 prefix에 지는 ^~ 블록

증상: ^~가 붙은 디렉터리가 보호되는 것처럼 보이는데, 그 안의 요청을 regex가 가져갑니다. 원인: ^~는 길이 경쟁에서 이미 이긴 prefix에 대해서만 확인됩니다. 더 긴 일반 prefix가 대신 기억되고, 그 prefix는 아무것도 억제하지 않습니다. 해결: 더 긴 prefix에도 ^~를 붙이거나, 그 prefix를 없앱니다.

# Broken: /a/b/x.php goes to the regex
location ^~ /a/    { }
location /a/b/     { }
location ~ \.php$  { }

# Fixed: /a/b/x.php goes to ^~ /a/b/
location ^~ /a/    { }
location ^~ /a/b/  { }
location ~ \.php$  { }

끝에 슬래시가 없어 형제 경로까지 잡는 prefix

증상: 한 디렉터리를 겨냥한 블록이 같은 글자로 시작하기만 하는 경로까지 함께 처리합니다. 원인: prefix 매칭은 경로 세그먼트가 아니라 글자를 비교하므로 location /app/application에도 일치합니다. 해결: 끝에 슬래시를 붙이고, 슬래시 없는 경로도 처리해야 한다면 location = /app을 추가합니다.

넓은 regex 아래에 놓인 정밀한 regex

증상: 정확하게 쓴 규칙이 한 번도 동작하지 않는데 어디에도 오류가 나타나지 않습니다. 원인: regex는 파일 순서대로 시도되고 처음 일치한 곳에서 탐색이 끝나므로, 넓은 패턴 아래는 전부 도달 불가능합니다. 해결: 정밀한 패턴을 넓은 패턴 위로 옮기거나, 앵커를 붙여 넓은 패턴을 좁힙니다.

^~ 뒤에 regex를 적은 경우

증상: deny 규칙이 오류 없이 로드되는데 아무것도 막지 못합니다. 원인: ^~는 패턴이 아니라 문자열 그대로의 prefix를 받습니다. nginx는 불평하지 않고, 그 블록은 어떤 URI와도 일치하지 않을 뿐입니다. 해결: regex 수식어를 씁니다.

# Broken: matches nothing, loads without error
location ^~ "\.php$" { deny all; }

# Fixed
location ~ \.php$ { deny all; }

쿼리 문자열이 매칭에 참여한다고 가정한 경우

증상: ?가 들어간 location이 한 번도 일치하지 않습니다. 원인: 쿼리 문자열은 정규화 단계에서 분리되고, location 선택은 경로만 가지고 진행됩니다. 해결: 경로로 매칭하고 블록 안에서 $arg_name을 확인합니다.

location /search {
    if ($arg_q = "") { return 400; }
}

디버깅: 실제로 이긴 블록 찾아내기

디버그 로그가 가장 확실한 답입니다. 대신 준비에 손이 가장 많이 갑니다. 디버그 지원이 포함된 바이너리가 필요하니 먼저 확인하세요.

nginx -V 2>&1 | grep -o with-debug

그다음 디버그 로그를 켜고, 선택된 블록 이름이 찍히는 줄을 grep 합니다.

error_log /var/log/nginx/debug.log debug;
grep "using configuration" /var/log/nginx/debug.log

응답 헤더로 찔러 보는 방법은 더 빠르고 디버그 빌드도 필요 없습니다. 후보마다 표시를 달아 두고 헤더를 되받아 읽습니다.

location ^~ /uploads/ {
    add_header X-Debug-Location "uploads-caret" always;
    return 204;
}
location ~ \.php$ {
    add_header X-Debug-Location "php-regex" always;
    return 204;
}
curl -sI --path-as-is 'http://localhost/uploads/evil.php' | grep -i x-debug-location

--path-as-is가 중요합니다. 이 옵션이 없으면 curl이 친절하게 ..를 대신 해석해 버려서, 의도한 것과 다른 URI를 시험하게 됩니다. 조금 더 복잡한 요청을 조립하는 중이라면 cURL 명령어 생성기가 플래그를 대신 써 주고, 나머지는 curl 명령어 치트시트에서 다룹니다. 헤더 대신 리다이렉트나 404가 돌아온다면, 어느 모듈이 그 응답을 만들었는지는 HTTP 상태 코드 문서에서 대체로 확인할 수 있습니다.

nginx -Tinclude 파일까지 포함해 병합된 설정 전체를 출력합니다. 파일 여섯 개가 조립된 뒤 regex가 실제로 어떤 순서로 놓이는지는 이렇게 확인합니다. 편집하던 파일에서 보이던 순서와 같은 경우는 드뭅니다.

nginx -T | grep -n "location"

서버를 손대기 전에 nginx location 테스터에서 문제를 먼저 재현해 보세요. 아직 배포하지 않은 설정을 상대로 반복하는 편이 reload 주기를 도는 것보다 빠르고, 판정 표가 각 블록이 어느 단계에서 탈락했는지 단계 이름으로 알려 줍니다.

중첩 location, try_files, 슬래시 리다이렉트

중첩된 location은 한 단계 아래에서 같은 알고리즘을 다시 실행합니다. prefix location이 이기면 nginx는 그 자식들로 내려가 탐색을 반복하는데, 그래서 중첩된 regex가 상위 레벨의 regex보다 먼저 시도됩니다.

server {
    location ~ \.php$ { }
    location /a/ {
        location ~ \.php$ { return 200 "nested\n"; }
    }
}

/a/x.php는 중첩 블록이 처리합니다. 중첩에는 덜 알려진 효과가 하나 더 있습니다. 각 레벨의 승자에게만 내려가기 때문에, 전체로 보면 더 긴 prefix가 도달 불가능해질 수 있습니다. location /a/bb/location /a/ 안에 중첩되어 있고 바깥 레벨에 형제로 location /a/b가 있다면, /a/bb/x 요청은 /a/b로 갑니다. 바깥쪽 비교가 먼저 일어나고 거기서 /a/b가 이기기 때문입니다. 내려간 곳에서 아무것도 찾지 못해도 되돌아 나오지 않습니다. 요청은 부모 블록이 그대로 가져갑니다.

try_filesrewrite는 선택 과정의 일부가 아닙니다. 이미 이긴 블록 안에서 실행될 뿐이고, 거꾸로 그 선택을 바꾸지는 못합니다. 요청이 try_files가 들어 있는 블록에 애초에 도달하지 못한다면 그 지시어는 아무 의미가 없는데, 범인은 대개 prefix 블록이 차례를 받기도 전에 요청을 가져가는 ~ \.php$ regex입니다. 알아 둘 만한 예외가 하나 있습니다. 내부 리다이렉트(rewrite … lasterror_page 점프)는 매칭을 처음부터 다시 시작하므로, 고쳐 쓴 URI가 location 목록 맨 위부터 다시 평가됩니다.

끝에 슬래시 없이 디렉터리를 요청했을 때 돌아오는 301도 매칭 실패가 아닙니다. 서로 다른 두 가지 방식이 이 응답을 만듭니다. 이름이 /로 끝나는 location에 proxy_pass를 비롯한 *_pass 지시어가 있으면, 슬래시 없는 같은 경로 요청에는 선택 단계에서 301이 돌아갑니다. regex가 평가되기 전이고, 쿼리 문자열은 유지됩니다.

server {
    location /user/ { proxy_pass http://backend/; }
}
# GET /user?x=1  ->  301 to /user/?x=1

location = /user를 추가하면 이 리다이렉트를 막을 수 있습니다. 이와 별개로, 경로가 디스크의 실제 디렉터리로 해석될 때는 정적 파일 모듈이 자체적으로 301을 냅니다. 이쪽은 설정이 아니라 파일 시스템에 달려 있습니다.

FAQ

nginx location 수식어 다섯 가지는 무엇인가요?

nginx location 수식어 다섯 가지는 완전 일치용 =, regex 단계를 건너뛰는 prefix용 ^~, 일반 prefix인 수식어 없음, 대소문자를 구분하는 regex용 ~, 대소문자를 구분하지 않는 regex용 ~*입니다. 여섯 번째 형태인 location @name은 URI 매칭에 전혀 참여하지 않고 error_pagetry_files의 점프 대상으로만 존재합니다.

= 완전 일치를 쓰면 nginx가 더 빨라지나요?

완전 일치 location은 탐색을 즉시 끝내므로 prefix 스캔과 모든 regex 평가를 건너뜁니다. 절약되는 시간은 실재하지만 체감할 수 없을 만큼 작습니다. 헬스 체크나 /favicon.ico처럼 초당 수천 번씩 들어오는 엔드포인트라면 써 둘 만합니다. 평범한 페이지마다 = 블록을 쌓으면 얻는 것보다 설정 복잡도라는 비용이 더 큽니다.

~*^/api처럼 수식어를 공백 없이 붙여 써도 되나요?

그렇습니다. location ~*^/api/location ~* ^/api/는 완전히 같은 뜻입니다. nginx가 이름 앞쪽에서 수식어를 떼어 내고 가장 긴 수식어부터 맞춰 보기 때문에, ~보다 ~*가 먼저 인식됩니다. 그래도 공백은 넣으세요. 붙여 쓴 수식어는 패턴의 일부처럼 보여서 리뷰할 때 사람이 잘못 읽습니다.

location 블록 안에서 rootalias는 무엇이 다른가요?

root는 디렉터리 뒤에 URI 전체를 이어 붙이고, alias는 일치한 prefix를 그 경로로 갈아 끼웁니다. location /static/ { root /var/www; }에서 /static/x.css 요청은 /var/www/static/x.css를 찾고, alias /var/www/assets/;로 바꾸면 /var/www/assets/x.css를 찾습니다. alias를 쓸 때는 location과 경로 양쪽에 모두 끝 슬래시를 붙이거나, 양쪽 모두 붙이지 마세요.

하나의 요청이 여러 location 블록에 일치할 수 있나요?

여러 블록이 일치할 수는 있지만 요청을 처리하는 블록은 정확히 하나입니다. nginx는 모든 prefix location을, 필요하면 모든 regex location까지 비교한 뒤 단 하나의 승자에게 요청을 넘깁니다. 진 블록의 지시어는 상속되지 않습니다. 어디서나 필요한 설정은 serverhttp 레벨에 두거나 블록마다 반복해서 써야 합니다.

nginx -t로 어떤 location이 매칭될지 알 수 있나요?

아닙니다. nginx -t는 문법과 설정의 유효성만 확인하고 요청을 흉내 내지는 않으므로, 매칭 순서에 대해서는 아무것도 알려 주지 않습니다. 어떤 블록이 URI를 가져가는지 알아내려면 디버그 로그를 읽거나, 임시 응답 헤더를 붙이거나, 설정을 nginx location 테스터에 붙여 넣고 각 블록이 왜 이기고 졌는지 확인하세요.

태그: nginx web-server devops regex configuration