고른다, 합친다, 정렬한다 — nginx·Apache·Caddy 의 요청 매칭
같은 요청 경로 하나를 넣어도 세 웹서버는 서로 다른 설정 블록을 고릅니다. 각 서버 공식 문서에 적힌 규칙을 따라가며 탈락 과정까지 눌러봅니다.

목차
웹서버 설정은 "맞는 규칙을 찾는다"가 아닙니다. nginx 는 우선순위로 하나를 고르고, Apache 는 맞는 섹션을 전부 합치고, Caddy 는 설정을 먼저 정렬한 뒤 첫 매칭 하나만 실행합니다.
세 서버 모두 "경로에 규칙을 붙인다"는 점은 같습니다. 그런데 규칙이 여러 개 겹칠 때 무슨 일이 일어나는지는 완전히 다릅니다. 이 차이를 모르면 분명히 써 둔 설정이 왜 동작하지 않는지 알 수 없습니다.
nginx — 긴 게 이기는 게 아닙니다
가장 흔한 오해입니다. "더 구체적인 규칙이 이긴다"고 생각하기 쉽지만, nginx 의 순서는 그렇게 단순하지 않습니다. 공식 문서가 설명하는 순서는 이렇습니다.
=로 시작하는 정확히 일치를 먼저 본다. 걸리면 그 자리에서 끝난다.- 전위(prefix) 규칙 중 가장 긴 것을 기억해 둔다.
- 기억해 둔 것이
^~면 정규식은 검사조차 하지 않는다. - 아니면 정규식을 설정 파일에 쓴 순서대로 검사하고, 첫 매칭에서 멈춘다.
- 맞는 정규식이 없을 때만 2번에서 기억해 둔 전위를 쓴다.
핵심은 4번입니다. 정규식이 하나라도 걸리면, 그것이 더 긴 전위 규칙을 이깁니다.
데모의 설정에 /api/v1/users.php 를 넣어보면 이렇게 됩니다.
location /api/v1/ → 후보 (가장 긴 전위지만 진다)
location ~ \.php$ → 적용됨 ← 승자
location ~* \.(js|css)$ → 검사 안 함 (정규식 검색이 이미 끝남)
/api/v1/ 이 여덟 글자로 가장 길지만, ~ \.php$ 정규식에 밀립니다. nginx 공식 문서의
예제도 같은 구조입니다 — /documents/1.jpg 요청은 location /documents/ 가 아니라
location ~* \.(gif|jpg|jpeg)$ 로 갑니다.
반대로 ^~ 를 붙이면 정규식을 아예 막을 수 있습니다. 데모에 /static/app.js 를
넣으면 ^~ /static/ 이 걸리고, ~* \.(js|css)$ 는 분명히 맞는데도 검사되지
않습니다.
Apache — 고르는 게 아니라 합칩니다
Apache 는 승자를 뽑지 않습니다. 매칭되는 섹션이 전부 적용되고, 문서화된 순서로 합쳐집니다.
<Directory>(정규식 아닌 것) 와.htaccess<DirectoryMatch><Files>와<FilesMatch><Location>과<LocationMatch><If>
<Directory> 는 한 가지가 더 특이합니다. 설정 파일에 쓴 순서가 아니라 짧은 경로부터
처리됩니다. <Directory "/var/web/dir"> 이 <Directory "/var/web/dir/subdir"> 보다
항상 먼저입니다.
데모에 /api/v1/users.php 를 넣으면 여섯 개 섹션이 병합됩니다. 하나가 이기는 게
아니라 여섯 개가 겹겹이 쌓입니다.
여기서 또 하나 주의할 점 — 합치는 방식은 지시어마다 다릅니다. 공식 문서는 매칭된 다음 섹션이 나오면 "각 모듈이 자신의 설정을 병합할 기회를 받는다"고 설명합니다. 즉 무조건 나중 것이 앞의 것을 덮는 게 아닙니다. 데모가 쓰는 두 지시어만 봐도 규칙이 다릅니다.
Header set은 같은 이름의 헤더를 교체합니다.<Location "/api">가 정한X-Space를<LocationMatch "^/api/v1/">가 덮어씁니다.Options는 접두사가 있느냐에 따라 갈립니다. 이름만 쓰면 상속받은 목록을 통째로 교체하고,+/-를 붙이면 상속과 합칩니다. 데모에서는Indexes FollowSymLinks로 깔아둔 뒤-Indexes로 빼서FollowSymLinks만 남습니다.
그리고 무엇에 대고 맞춰보는지가 섹션마다 다릅니다. <Directory> 는 파일시스템 경로,
<Files> 는 파일 이름, <Location> 은 URL 입니다. 그래서 <Location> 은 대응
파일이 없어도 걸립니다. 데모에 /healthz 를 넣으면 그런 파일이 없는데도
<Location "/healthz"> 가 적용됩니다.
Caddy — 쓴 순서가 실행 순서가 아닙니다
Caddy 는 앞의 둘과 또 다릅니다. Caddyfile 을 읽어서 실행 구조로 바꿀 때, 어댑터가 먼저 순서를 다시 정합니다.
그래서 이렇게 써도
handle /api/* {
reverse_proxy api:3000
}
handle /api/v1/* {
reverse_proxy v1:3000
}
실제 실행 순서는 /api/v1/* 가 앞으로 옵니다. 더 구체적인 경로가 먼저 오도록
정렬해 주기 때문입니다. caddy adapt 로 변환 결과를 직접 확인할 수 있습니다.
즉 "먼저 쓴 게 이긴다"가 아닙니다. 오히려 Caddy 는 순서를 신경 쓰지 않아도 되게 도와줍니다.
그런데 route 로 감싸는 순간 그 도움이 사라집니다. 공식 문서는 route 에 대해
"위의 모든 규칙을 무시하고, 디렉티브가 나타난 순서를 보존한다"고 못 박습니다.
route {
handle /api/* { ... }
handle /api/v1/* { ... }
}
이제 /api/* 가 먼저 걸리고, handle 은 상호배타적이라 /api/v1/* 는 영원히
실행되지 않습니다. 같은 내용인데 route 하나로 결과가 뒤집힙니다. 데모의 Caddy 화면에서
두 형태를 전환해 보면 바로 보입니다.
마지막으로 대소문자입니다. Caddy 의 path matcher 는 공식 문서에 따르면 "정확히 일치하되
대소문자를 구분하지 않습니다". 반면 nginx 의 전위 매칭은 Linux 에서 대소문자를
구분합니다. 그래서 /Static/app.js 하나로 두 서버의 결과가 갈립니다.
/static/app.js | /Static/app.js | |
|---|---|---|
| nginx | ^~ /static/ | ~* \.(js|css)$ |
| Caddy | handle /static/* | handle /static/* |
세 서버 한눈에 보기
같은 /api/v1/users.php 를 넣었을 때입니다.
| 서버 | 방식 | 결과 |
|---|---|---|
| nginx | 선택 | location ~ \.php$ 하나가 선택됨 |
| Apache | 병합 | 6개 섹션이 병합됨 (승자가 아니라 합계) |
| Caddy (일반) | 정렬 후 분기 | handle /api/v1/* 가 실행됨 |
Caddy (route) | 순서 보존 | handle /api/* 가 실행됨 |
같은 경로, 같은 의도로 쓴 설정인데 결말이 넷으로 갈립니다.
이 데모의 한계
데모는 실제 서버를 실행하지 않습니다. 각 서버 공식 문서에 적힌 매칭 규칙만 재현한 교육용 모델입니다.
다루지 않는 것들이 있습니다. Alias, .htaccess, nginx 의 try_files 재진입,
rewrite 이후 재매칭, 가상호스트 선택, 그리고 %XX 인코딩·..·연속 슬래시의 정규화는
범위 밖입니다. 서버마다 URI 정규화 방식이 달라서 하나의 모델로 흉내내면 오히려 틀린
답을 주기 때문에, 데모 입력은 안전한 ASCII 경로로 제한했습니다.
nginx 의 전위 매칭 대소문자는 Linux 기준입니다. 공식 문서는 macOS·Cygwin 같은 대소문자를 구분하지 않는 운영체제에서는 전위 매칭도 대소문자를 무시한다고 밝히고 있습니다.
Caddy 정렬은 한 가지 단서가 더 있습니다. 길이가 같은 matcher 사이의 타이브레이크
규칙까지는 문서로 확정할 수 없어서, 데모에 실린 설정에 대해 caddy adapt(v2.11.4)로
확인한 순서를 그대로 씁니다. 규칙을 지어내지 않았다는 뜻이기도 하고, 다른 설정에는
그대로 적용되지 않는다는 뜻이기도 합니다.
실제 설정을 검증하려면 이 데모가 아니라 서버에 직접 물어야 합니다. 주의할 점은
nginx -T 와 apachectl -S 가 특정 요청의 승자를 보여주지 않는다는 겁니다. 앞의
것은 설정 덤프, 뒤의 것은 가상호스트 목록입니다. 확실한 방법은 각 블록이 서로 다른
응답 헤더나 본문을 내도록 임시로 바꿔두고 curl 로 확인하는 것입니다. Caddy 는
caddy adapt 로 정렬 결과까지 볼 수 있습니다.
참고 자료
- nginx
location디렉티브 — 매칭 알고리즘,^~의 정규식 건너뛰기, 대소문자 관련 주석 - Apache 설정 섹션 — 병합 순서와 "각 모듈이 병합할 기회를 받는다"
- Apache
DocumentRoot— 허용 컨텍스트는 서버 설정과 가상호스트뿐 - Apache
Options— bare 와+/-의 병합 차이 - Apache
mod_headers— 처리 순서가 중요한 이유 - Caddyfile 디렉티브 — 정렬 알고리즘과
route의 순서 보존 - Caddyfile matcher — "Path matches are exact but case-insensitive."

