웹 서버·WAS 학습 노트 목차

nginx 설정 읽기 — server·location·upstream

설정 파일을 처음 열면 중괄호만 잔뜩 보인다. 그런데 요청 하나가 들어왔을 때 nginx 가 무엇을 순서대로 고르는지를 알면 파일이 그 순서대로 읽힌다.


1. 블록의 계층

# 전역 — 프로세스 수준
worker_processes auto;

events { ... }               # 연결 처리 방식

http {                       # HTTP 전반의 기본값
    include mime.types;
    sendfile on;
    keepalive_timeout 65;

    upstream backend { ... }  # 뒤로 보낼 서버 묶음

    server {                  # 가상 호스트 하나
        listen 443 ssl;
        server_name shop.example.com;

        location /api/ { ... }   # 경로별 처리
        location / { ... }
    }
}

바깥 블록의 지시자는 안쪽으로 상속된다 안쪽에서 같은 지시자를 다시 쓰면 그것이 이긴다

  • http — 사이트 전체 기본값
  • server — 이 도메인만
  • location 이 경로만

2. 어느 server 인가 — 가상 호스트 선택

한 IP·한 포트에 여러 사이트가 있을 수 있다. nginx 는 이렇게 고른다.

  • ① listen 이 맞는 server 들을 추린다 (IP · 포트 · 옵션)
  • ② 그중 Host 헤더와 server_name 이 맞는 것을 고른다
    • 정확히 일치 → 앞쪽 와일드카드(.example.com) → 뒤쪽(www.) → 정규식
  • ③ 맞는 게 없으면 default_server 로 간다
    • 지정하지 않았으면 그 listen 의 '첫 번째' server 가 기본이 된다
server {
    listen 443 ssl default_server;
    server_name _;                    # 어디에도 안 맞는 요청
    ssl_certificate     /etc/ssl/dummy.crt;
    ssl_certificate_key /etc/ssl/dummy.key;
    return 444;                       # 응답 없이 연결을 끊는다 (nginx 전용 코드)
}

왜 이런 걸 두나 — 도메인을 우리 IP 로 가리키게 해 놓고 들어오는 스캔 트래픽이 첫 번째 server 블록으로 흘러들어 엉뚱한 사이트를 노출하는 일이 실제로 생긴다. 명시적 default_server 를 두고 거기서 끊는 것이 안전하다.

HTTPS 는 순서가 하나 더 있다 — TLS 핸드셰이크가 먼저이므로 Host 헤더를 읽기 전에 인증서를 골라야 한다. 이때 쓰는 것이 SNI(Server Name Indication)다. SNI 로 인증서를 고르고, 복호화한 뒤 Host 로 server 를 확정한다.

3. 어느 location 인가 — 이게 가장 헷갈린다

location = /exact      { }   # ① 정확히 일치. 맞으면 즉시 확정
location ^~ /images/   { }   # ② 접두사 일치 + 정규식 검사 생략
location ~  \.php$     { }   # ③ 정규식 (대소문자 구분)
location ~* \.(jpg|png)$ { } # ④ 정규식 (대소문자 무시)
location /docs/        { }   # ⑤ 일반 접두사
location /             { }   # ⑥ 모든 것에 맞는 접두사

매칭 순서 — 파일에 적힌 순서가 아니다.

  1. '=' 정확히 일치가 있으면 → 즉시 확정. 끝
  2. 접두사 중 '가장 긴 것' 을 기억해 둔다
  3. 그 접두사가 '^~' 였다면 → 정규식을 건너뛰고 확정
  4. 아니면 정규식을 '파일에 적힌 순서대로' 검사해 첫 일치를 쓴다
  5. 정규식에 하나도 안 맞으면 → 2번에서 기억해 둔 접두사를 쓴다

접두사끼리는 '길이' 로 경쟁하고, 정규식끼리는 '순서' 로 경쟁한다 그리고 정규식이 접두사보다 우선한다 (^~ 로 막지 않는 한)

실제로 물려서 사고가 나는 예:

location /static/ { root /var/www; }
location ~ \.js$  { deny all; }        # 의도: 특정 경로의 js 차단

# /static/app.js 요청 →
#   접두사 /static/ 이 맞지만, 정규식 \.js$ 도 맞는다
#   정규식이 이기므로 → 403
#   /static/ 아래 js 가 통째로 막힌다
# 고치려면 — 정적 경로에 정규식 검사를 아예 건너뛰게 한다
location ^~ /static/ { root /var/www; }

4. rootalias — 한 글자 차이로 경로가 달라진다

location /images/ {
    root /var/www;          # → /var/www/images/logo.png
}

location /images/ {
    alias /var/www/pics/;   # → /var/www/pics/logo.png
}
  • rootlocation 경로를 '뒤에 붙인다'

  • alias — location 경로를 '대체한다'

  • alias 는 끝의 슬래시를 맞춰야 한다

    • location /images/ + alias /var/www/pics; ← 슬래시 불일치 → 경로가 어긋난다

alias 를 정규식 location 과 함께 쓰면 캡처 그룹을 명시해야 해서 실수가 잦다. 가능하면 root 로 푼다 는 것이 실무 기본값이다.

5. proxy_pass — 슬래시 하나가 경로를 바꾼다

location /api/ {
    proxy_pass http://backend;      # URI 없음 → 원래 경로를 그대로 붙인다
    # /api/orders  →  http://backend/api/orders
}

location /api/ {
    proxy_pass http://backend/;     # URI 있음(/) → location 부분을 '대체' 한다
    # /api/orders  →  http://backend/orders
}
  • proxy_pass 에 경로가 있으면location 접두사를 그 경로로 바꾼다
  • proxy_pass 에 경로가 없으면 — 요청 URI 를 그대로 넘긴다

"백엔드에서 404 가 난다" 의 절반이 이 슬래시다 로그에서 업스트림이 실제로 받은 경로를 확인하면 바로 보인다

6. upstream — 뒤에 여러 대를 둔다

upstream backend {
    least_conn;                                  # 분배 방식

    server 10.0.1.10:8080 max_fails=3 fail_timeout=10s;
    server 10.0.1.11:8080 max_fails=3 fail_timeout=10s;
    server 10.0.1.12:8080 backup;                # 위가 다 죽으면 쓴다

    keepalive 32;                                # 업스트림 연결 재사용
}
분배 방식
  (기본)      라운드로빈
  least_conn  활성 연결이 가장 적은 곳으로 — 요청 처리 시간이 들쭉날쭉할 때 유리
  ip_hash     클라이언트 IP 해시 — 같은 사용자를 같은 서버로
  hash $key   임의 키 기준

max_fails / fail_timeout fail_timeout 안에 max_fails 번 실패하면, 그 시간 동안 그 서버를 뺀다

  • 죽은 서버로 계속 보내지 않는 최소한의 장치다 ※ 능동 헬스체크(주기적으로 찔러 보는 것)는 상용판 기능이다.

    • 오픈소스판은 '실제 요청이 실패하는 것' 을 보고 판단한다

keepalive 를 빠뜨리면 생기는 일

upstream backend {
    server 10.0.1.10:8080;
    keepalive 32;
}

location /api/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;        # ← keepalive 는 1.1 이 필요하다
    proxy_set_header Connection "";  # ← 기본 'close' 를 지운다
}

이 세 줄이 없으면 nginx 는 요청마다 업스트림에 새 TCP 연결을 만든다

  • 매번 3-way 핸드셰이크
  • 업스트림에 TIME_WAIT 소켓이 쌓인다
  • 트래픽이 오르면 포트 고갈로 이어진다

keepalive 만 쓰고 아래 두 줄을 빠뜨리는 것이 가장 흔한 미완성 설정이다

7. 자주 쓰는 실전 블록

정적 자산은 길게 캐시하고, HTML 은 캐시하지 않는다

location ~* \.(?:css|js|woff2|png|jpg|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

location = /index.html {
    add_header Cache-Control "no-cache";   # 재검증은 하되 매번 받지는 않는다
}
  • 빌드 산출물에 해시가 붙는다면(app.9f2c1.js) 내용이 바뀌면 이름이 바뀐다

  • 그 파일은 영원히 캐시해도 안전하다 (immutable)

  • 대신 그것을 가리키는 HTML 은 캐시하면 안 된다

  • "배포했는데 옛 화면이 보인다" 의 대부분이 이 조합이 뒤집힌 경우다

압축

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1024;      # 너무 작은 응답은 압축이 손해다
gzip_comp_level 5;         # 6 이상은 CPU 대비 이득이 급감한다
gzip_vary on;              # Vary: Accept-Encoding — 캐시가 섞이지 않게

주의

  • 이미 압축된 것(jpg·png·woff2·zip)은 다시 압축하지 않는다 — 오히려 커진다
  • gzip_vary 를 빼면 중간 캐시가 압축본을 비압축 클라이언트에게 줄 수 있다
  • 정적 파일은 gzip_static 으로 미리 압축해 두면 요청마다 CPU 를 안 쓴다

업로드 크기

client_max_body_size 20m;      # 기본 1m — 넘으면 413 Payload Too Large

"파일 업로드가 413 으로 실패한다" → 대개 여기다 그리고 계층이 여럿이면 '전부' 올려야 한다

  • CDN → LB → nginx → WAS
  • 하나만 낮아도 거기서 막힌다

8. 설정을 확인하는 방법

nginx -t                      # 문법 검사
nginx -T                      # include 를 전부 펼친 '최종' 설정을 출력

-T 가 실무에서 훨씬 유용하다

  • include 로 흩어진 파일이 실제로 어떻게 합쳐졌는지 보여 준다
  • "어디서 이 지시자가 들어왔지" 를 여기서 찾는다
  • 배포 전후 diff 를 뜨면 변경이 정확히 무엇인지 드러난다

한눈에 정리

  • server 선택 — listen → server_name(정확 > 앞 와일드카드 > 뒤 > 정규식) → default_server
    • HTTPS 는 SNI 로 인증서를 먼저 고른다
  • location — '=' 즉시 확정 → 가장 긴 접두사 기억 → '^~' 면 정규식 생략
    • 정규식은 '적힌 순서' 로 → 없으면 기억해 둔 접두사
    • 정규식이 접두사를 이긴다는 점이 사고의 근원
  • root/alias — root 는 붙이고 alias 는 대체한다. 되도록 root 를 쓴다
  • proxy_pass — 끝에 경로가 있으면 location 을 대체, 없으면 URI 를 그대로
  • upstreamleast_conn · max_fails/fail_timeout
    • keepalive 는 proxy_http_version 1.1 + Connection "" 과 세트
  • 캐시 — 해시 붙은 자산은 immutable, HTML 은 no-cache
  • 확인nginx -t 는 문법, nginx -T 는 펼쳐진 최종 설정

출처 — nginx docs: ngx_http_core_module(server_name · location · root · alias · client_max_body_size) · ngx_http_proxy_module(proxy_pass · proxy_set_header) · ngx_http_upstream_module(keepalive · max_fails) · ngx_http_gzip_module · "Server names" 문서

nginx 프로세스 모델 — 만 개의 연결TLS 종단과 HTTP/2·3