🟣 중첩된 블록이 왜 이렇게 생겼나
복사해 쓰던 설정 파일을 열면 항상 이런 모양이었다. http 안에 server가 있고, server 안에 location이 여럿 들어 있고, 각 블록에 중괄호가 겹겹이 쳐져 있다. 어느 줄을 어디에 넣어야 하는지 감이 없어서, 새 규칙이 필요하면 비슷해 보이는 location을 통째로 복사해 조금 고쳐 붙였다. 그러다 두 location이 같은 요청을 두고 겹칠 때, 어느 쪽이 이기는지 몰라서 손을 놓은 적이 있다.
이 중첩은 장식이 아니다. 하나의 Nginx가 여러 도메인과 여러 경로를 동시에 감당해야 한다는 요구가 이 계층 구조를 만들었다. 구조를 알면 설정은 복사가 아니라 읽고 쓰는 대상이 된다.
🟣 컨텍스트와 디렉티브
Nginx 설정은 두 가지 요소로만 이뤄진다. 디렉티브는 worker_processes auto;처럼 세미콜론으로 끝나는 한 줄짜리 지시다. 하나의 설정값을 정한다. 컨텍스트는 http { ... }처럼 중괄호로 감싸 여러 디렉티브를 묶는 블록이다. 컨텍스트는 다른 컨텍스트를 품을 수 있어서 계층이 생긴다.
가장 바깥은 이름이 없는 main 컨텍스트다. worker_processes처럼 프로세스 전체에 걸리는 설정이 여기 온다. 그 안에 연결 처리 방식을 정하는 events, HTTP 관련 설정을 총괄하는 http가 놓인다. http 안에 도메인 단위의 server가, server 안에 경로 단위의 location이 들어간다.
이 계층이 중요한 이유는 디렉티브가 아래로 상속되기 때문이다. http에 gzip on;을 두면 그 아래 모든 server와 location이 그 설정을 물려받는다. 안쪽에서 같은 디렉티브를 다시 쓰면 그 자리에서 덮어쓴다. 그래서 공통 설정은 바깥에 한 번 적고, 예외만 안쪽에서 조정한다. 복사해 붙이던 설정이 장황했던 건, 상속을 몰라서 같은 값을 location마다 반복해 적었기 때문이었다.
🟣 하나의 서버가 여러 도메인을 받는 법
한 대의 Nginx는 보통 여러 도메인을 함께 서비스한다. api.example.com과 www.example.com이 같은 기계, 같은 443 포트로 들어온다. 이걸 갈라 주는 것이 server 블록과 server_name이다.
server {
listen 443 ssl;
server_name api.example.com;
# api 도메인 처리
}
server {
listen 443 ssl;
server_name www.example.com;
# www 도메인 처리
}같은 포트로 들어온 요청이 어느 server로 갈지는 HTTP 요청의 Host 헤더로 정해진다. 클라이언트가 어느 도메인에 접속하려 했는지가 그 헤더에 담겨 있고, Nginx는 그 값을 각 server의 server_name과 대조한다. 이렇게 하나의 IP와 포트로 여러 도메인을 나눠 받는 것을 이름 기반 가상 호스트라고 한다. 어느 server_name과도 안 맞으면 첫 번째 server나 default_server로 지정된 블록이 받는다. 이 기본값 규칙을 몰라서, 존재하지 않는 도메인으로 온 요청이 엉뚱한 서비스로 들어가던 문제를 겪은 적이 있다.
🟣 location 매칭의 우선순위
server가 정해지면, 그 안에서 요청 경로에 맞는 location을 찾는다. 여기가 내가 가장 오래 헷갈린 지점이다. location은 여러 형태로 쓸 수 있고, 여러 개가 동시에 맞을 수 있으며, 그때 이기는 규칙이 정해져 있다.
location = /health { ... } # 정확히 일치
location ^~ /assets/ { ... } # 접두 일치, 여기서 매칭 중단
location ~ \.php$ { ... } # 정규식(대소문자 구분)
location ~* \.(jpg|png)$ { ... } # 정규식(대소문자 무시)
location /images/ { ... } # 일반 접두 일치Nginx는 이들을 적힌 순서대로 확인하지 않는다. 정해진 우선순위로 고른다. 먼저 =로 완전히 일치하는 게 있으면 즉시 그걸로 확정한다. 없으면 접두 일치 중 가장 긴 것을 후보로 기억해 두는데, 그게 ^~로 시작하면 정규식을 아예 보지 않고 확정한다. 그렇지 않으면 정규식들을 적힌 순서대로 검사해 첫 번째로 맞는 것을 쓴다. 정규식이 하나도 안 맞으면 아까 기억해 둔 가장 긴 접두 일치로 돌아간다.
이 순서를 알고 나면 앞의 예제가 다르게 읽힌다. /assets/를 ^~로 둔 건, 그 아래 정적 파일 요청이 뒤의 이미지 정규식(~*)까지 내려가지 않고 바로 확정되게 하려는 의도다. 정적 파일 경로에서 굳이 정규식을 매번 돌릴 이유가 없으니, 접두에서 끊어 준다. 설정 한 줄의 기호가 성능 판단을 담고 있었던 것이다. 복사할 때는 보이지 않던 의도다.
🟣 경로를 파일로 바꾸는 root와 alias
location을 찾으면 그 경로를 실제 파일 시스템 위치로 바꿔야 한다. 여기서 root와 alias가 갈리는데, 둘을 혼동하면 파일을 못 찾는다.
root는 요청 경로 전체를 지정한 디렉터리 뒤에 그대로 붙인다. location /images/에 root /data;면 /images/cat.png 요청은 /data/images/cat.png를 찾는다. 반면 alias는 location에 매칭된 접두 부분을 잘라내고 지정한 경로로 대체한다. 같은 요청에 alias /data/pics/;면 /data/pics/cat.png를 찾는다. location 접두가 경로에 남느냐 사라지느냐가 차이다. 이 미묘한 차이 때문에 파일이 있는데도 404가 나던 경험이, 두 디렉티브의 정의를 정확히 읽고 나서야 풀렸다.
🟣 정리
- Nginx 설정은 디렉티브(한 줄 지시)와 컨텍스트(블록)로만 이뤄지고, 컨텍스트가 계층으로 중첩된다.
- 디렉티브는 아래로 상속되므로 공통 설정은 바깥에 한 번, 예외만 안쪽에서 덮어쓴다. 반복 복사는 상속을 몰라서 생긴다.
- 하나의 서버는
server_name과 요청의Host헤더로 여러 도메인을 나눠 받는다. 어디에도 안 맞으면 기본server가 받는다. location은 적힌 순서가 아니라 정해진 우선순위(=→^~→ 정규식 → 최장 접두)로 매칭된다. 기호 선택에 성능 의도가 담긴다.root는 경로를 뒤에 붙이고alias는 접두를 대체한다. 이 차이가 404의 흔한 원인이다.
설정 파일을 읽을 수 있게 됐으니, 이제 Nginx가 가장 많이 하는 일 하나를 본다. 자기가 응답을 만드는 대신, 뒷단의 앱 서버에 요청을 넘기고 결과를 전달하는 리버스 프록시다. 왜 앱 서버를 클라이언트에 직접 노출하지 않고 Nginx를 앞에 세우는지, 다음 장에서 다룬다.