LabHub

Tomcat & nginx 운영 · nginx 리버스프록시 설정 · 이론

프록시 설정 여섯 줄과, 그 여섯 줄이 없을 때 벌어지는 일

LabHub 에서 이어서 보기

한 줄 요약

프록시 설정에서 사고는 대개 여섯 줄짜리 헤더 전달과 proxy_pass 의 슬래시 하나에서 나며, 둘 다 빠져도 화면은 멀쩡히 뜨기 때문에 늦게 발견된다.

왜 앞에 웹서버를 두는가

톰캣도 HTTP 를 말할 줄 아는데 왜 앞에 nginx 를 두는가.
SI 현장의 실제 이유는 성능보다 운영 편의와 정책이다.

특히 마지막 줄이 크다. **정책을 애플리케이션에 넣으면 배포해야 바뀌지만,
프록시에 넣으면 reload 로 바뀐다.** 운영 조직이 프록시를 좋아하는 이유다.

프록시 설정 여섯 줄

location / {    proxy_pass http://127.0.0.1:8080;    proxy_set_header Host              $host;    proxy_set_header X-Real-IP         $remote_addr;    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;    proxy_set_header X-Forwarded-Proto $scheme;    proxy_set_header X-Forwarded-Host  $host;    proxy_set_header X-Forwarded-Port  $server_port;    proxy_connect_timeout 3s;    proxy_send_timeout   30s;    proxy_read_timeout   60s;}

각 줄이 없을 때 무슨 일이 나는지 알아야 외운 게 아니라 아는 것이 된다.

그리고 중요한 보안 원칙 하나: 이 헤더들은 최전방 프록시에서 반드시 덮어써야 한다.
클라이언트가 X-Forwarded-For: 10.0.0.1 을 직접 보낼 수 있기 때문이다.
proxy_set_header 로 덮어쓰면 클라이언트가 보낸 값은 무시된다(이어 붙는 변수는 예외).

proxy_pass 의 슬래시 — 가장 흔한 nginx 버그

location /api/ {    proxy_pass http://backend;      # 슬래시 없음 → /api/users/1 이 그대로 전달}location /api/ {    proxy_pass http://backend/;     # 슬래시 있음 → /api 부분이 잘리고 /users/1 로 전달}

URI 부분이 있으면(슬래시 포함) location 에 매칭된 부분이 잘려 나간다.
백엔드가 /api 컨텍스트를 기대하는데 슬래시를 붙이면 404 가 쏟아지고,
반대로 백엔드가 루트를 기대하는데 슬래시를 빼면 /api/api/users 가 된다.

이 한 글자로 반나절을 쓰는 사람을 매년 본다. 헷갈리면
액세스 로그에 $upstream_addr 와 백엔드 로그의 요청 경로를 함께 보면 즉시 판별된다.

요청 크기 — 413 의 정체

nginx 의 client_max_body_size 기본값은 1MB 다.
파일 업로드가 있는 시스템에서 이 값을 안 올리면 3MB 짜리 첨부를 올릴 때
413 Request Entity Too Large 가 난다. 그리고 이 오류는 **nginx 가 반환하므로
애플리케이션 로그에는 아무것도 안 남는다.** 개발자는 "서버에서 아무 로그도 없어요"라며
며칠을 헤맨다.

client_max_body_size 20m;client_body_buffer_size 128k;large_client_header_buffers 4 16k;

large_client_header_buffers 는 헤더가 클 때 쓴다. SSO 를 붙이면 쿠키가
커지는데, 기본 버퍼를 넘으면 400 Bad Request 가 난다. 이것도 로그가 애매하다.

압축과 정적 파일

gzip on;gzip_comp_level 6;gzip_min_length 1000;gzip_types text/plain text/css application/json application/javascript text/xml;gzip_vary on;

정적 파일은 아예 프록시를 태우지 않는 것이 낫다.

location /static/ {    alias /app/static/;    expires 7d;    access_log off;}

rootalias 를 헷갈리는 것도 단골이다. location /static/ 에 대해
root /app; 이면 /app/static/파일, alias /app/static/; 이면 /app/static/파일.
같아 보이지만 location /s/alias /app/static/; 라면 /s/a.js/app/static/a.js 다.
location 경로와 실제 디렉터리 이름이 다를 때 alias 를 쓴다.

관리자 화면 차단

톰캣 매니저나 액추에이터가 외부에 열려 있는 것은 실제 사고로 이어진다.
프록시에서 막는 것이 가장 확실하다.

location ~ ^/(manager|host-manager)/ { return 403; }location /actuator/ {    allow 10.0.0.0/8;    deny  all;    proxy_pass http://127.0.0.1:8080;}

여기서 배울 점: **애플리케이션 설정으로도 막을 수 있지만, 프록시에서 막으면
애플리케이션을 배포하지 않고도 정책을 바꿀 수 있다.** 보안 점검 지적이
금요일 오후에 내려와도 reload 로 끝난다.

액세스 로그에 무엇을 남길 것인가

기본 combined 포맷으로는 장애 분석이 안 된다. 최소한 이 네 개는 넣는다.

log_format labhub '$remote_addr - $remote_user [$time_local] '                  '"$request" $status $body_bytes_sent '                  'ua="$upstream_addr" us=$upstream_status '                  'urt=$upstream_response_time rt=$request_time';

$request_time 은 크고 $upstream_response_time 은 작으면 문제는 백엔드가 아니라
클라이언트 네트워크나 응답 전송이다. 이 두 값의 차이 하나로
"백엔드가 느리다"는 오해를 몇 번이나 깰 수 있다.

현장에서 만나는 모습

헤더 여섯 줄이 빠졌을 때 나타나는 증상은 서로 무관해 보인다. 접속 로그의 클라이언트 IP 가 전부 프록시 IP 한 개로 찍히고 (X-Real-IP·X-Forwarded-For 누락), 로그인 뒤 리다이렉트가 내부 주소나 http 로 튀고 (Host·X-Forwarded-Proto 누락), IP 기반 접근 제어나 감사 로그가 통째로 무의미해진다.

공통점은 평상시에는 아무 문제가 없다는 것이다. 그래서 오픈하고 몇 주 뒤 보안 감사나 장애 분석에서야 드러나고, 그때는 이미 그 로그로 아무것도 추적할 수 없다.

proxy_pass 슬래시는 더 조용하다. 끝에 슬래시가 있으면 location 경로를 떼고 넘기고 없으면 붙여서 넘기는데, 개발 환경에서는 경로가 루트라 차이가 안 드러나다가 운영에서 컨텍스트 경로가 붙는 순간 404 가 된다.