Nginx 운영·보안 체크리스트
Nginx 운영·보안 체크리스트
요약: 배포 전에 타임아웃, 업로드 한도, 보안 헤더, 로그, 압축, 인증서 갱신이 앱 특성에 맞는지 확인한다.
언제 이 목록을 보는가
리버스 프록시와 TLS(전송 계층 보안)가 열린 뒤에 본다. 문법만 통과한 설정은 배포 구성이 아니다.
앱이 60초 걸리는 내보내기를 쓰는데 프록시가 30초에 끊으면 사용자는 앱 버그로 느낀다. 헤더·로그·인증서 갱신은 장애 때 원인 구간을 가르는 도구이다. 아래는 옵션 나열이 아니라 배포 전에 한 번씩 통과할 항목이다.
1. 프록시·타임아웃
앱이 느린데 Nginx가 먼저 끊으면 사용자는 502/504만 본다. 값은 앱의 실제 응답 시간에 맞춘다.
-
proxy_connect_timeout을 짧게 둔다. 업스트림이 꺼졌을 때 연결 대기만 길어진다. -
proxy_read_timeout을 가장 긴 API(리포트·내보내기)보다 조금 크게 둔다. 짧으면 정상 작업이 504로 죽는다. - 파일 업로드가 있으면
client_max_body_size를 앱·스토리지 한도와 같게 둔다. 기본 1m이면 큰 폼이 413으로 거절된다. - WebSocket이면
Upgrade와Connection을 넘긴다. 빼면 핸드셰이크가 HTTP로 남는다. - 업스트림 다운 시 재시도·에러 페이지를 정한다. 무한 재시도는 장애를 키운다.
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
client_max_body_size 20m;
# WebSocket location 예
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
2. 보안 헤더
브라우저에 “이 응답을 어떻게 다룰지”를 알려 주는 헤더이다. 앱이 이미 같은 헤더를 내면 중복·충돌을 먼저 본다.
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
-
X-Content-Type-Options: nosniff를 둔다. MIME을 추측해 스크립트로 실행하는 길을 줄인다. - 클릭재킹이 문제면
X-Frame-Options또는 CSP(콘텐츠 보안 정책)의frame-ancestors를 둔다. iframe으로 넣을 사이트가 있으면SAMEORIGIN이 막는다. -
Referrer-Policy를 정한다. 기본값이면 외부 사이트로 경로가 과도하게 새어 나갈 수 있다. -
server_tokens off로 버전 문자열을 줄인다. 정찰을 완전히 막지는 못하지만 노출은 줄어든다. - CSP는 앱의 스크립트·스타일·폰트 출처와 맞춰 단계적으로 넣는다. 한 번에
default-src 'self'만 넣으면 화면이 한꺼번에 깨진다. - HTTPS가 안정된 뒤에만 HSTS(HTTP 엄격 전송 보안)를 켠다. 인증서 문제가 남은 상태에서는 넣지 않는다.
add_header는 일부 오류 응답에서 빠질 수 있다. always를 붙인 이유를 주석으로 남긴다. if 안에서 add_header를 쓰면 부모 헤더가 사라지는 함정이 있다.
3. 로그·관측
장애 때 요청이 Nginx에서 멈췄는지 앱에서 멈췄는지 나뉘어야 한다.
-
access_log와error_log경로가 디스크 용량·로테이션 대상이다. 안 돌리면 파티션이 찬다. - 로그 포맷에
$request_time과$upstream_response_time을 넣는다. 앞은 전체, 뒤는 앱 시간이다. - 헬스체크 URL은
access_log off를 검토한다. 로드밸런서 폴링이 액세스 로그를 덮는다. - 5xx가 늘면 에러 로그의 upstream 메시지를 먼저 본다. 타임아웃·연결 거부가 여기에 찍힌다.
log_format main '$remote_addr $status $request_time $upstream_response_time '
'"$request"';
access_log /var/log/nginx/access.log main;
4. 성능
미세 worker 튜닝보다 불필요한 프록시와 큰 바디를 먼저 줄인다.
| 항목 | 확인 이유 |
|---|---|
| gzip / brotli | HTML·JSON·CSS·JS는 압축 이득이 크다. 이미 압축된 이미지는 제외한다 |
sendfile |
정적 파일을 커널이 바로 보낼 때 복사 비용이 줄어든다 |
| 캐시 헤더 | 해시가 붙은 자산은 장기 expires. HTML은 짧게 둔다 |
worker_connections |
동시 연결 상한. 부족하면 대기열만 늘고 원인 로그가 빈약하다 |
- 텍스트 응답에 gzip(또는 brotli)이 켜져 있다.
gzip_types에application/json이 빠지기 쉽다. -
/assets/app.abc123.js처럼 파일명에 해시가 있으면 긴 캐시를 준다. 배포마다 URL이 바뀌므로 구버전을 붙잡지 않는다. - HTML·API는 짧게 캐시하거나
private로 둔다. 로그인 페이지를 공용 캐시하면 내용이 섞인다. - 정적 location이 앱
proxy_pass로 안 새는지 본다. 정적인데 앱 로그가 찍히면 location 우선순위 문제이다.
5. 배포 직전
설정을 바꾼 직후와 인증서 갱신 직후에 같은 목록을 탄다.
-
nginx -t가 성공한 뒤에만 reload한다. 문법 오류를 운영 프로세스에 넣지 않는다. - reload 후 헬스 URL과 로그인·결제 같은 핵심 경로를
curl -I로 본다. 프로세스는 살아 있어도 location이 틀릴 수 있다. - HTTP→HTTPS, www/non-www 정규 호스트가 기획과 같다. 인증서 SAN(주제 대체 이름)에 둘 다 있어야 한다.
- 인증서 만료일과 자동 갱신 hook에 reload가 있다. 파일만 바뀌고 프로세스가 옛 키를 들고 있으면 브라우저만 실패한다.
-
privkey권한이 최소이다. 백업 경로에 비밀키가 평문으로 복사되지 않았는지 본다. - 스테이징과 프로덕션
server_name·업스트림 포트가 뒤바뀌지 않았는지 한 줄로 대조한다.
정리
Nginx 운영은 옵션을 많이 켜는 일이 아니다. 앱이 끝나기 전에 프록시가 끊지 않는지, 브라우저에 필요한 헤더가 나가는지, 로그로 지연 구간을 나눌 수 있는지, 인증서가 갱신 후에도 로드되는지가 체크리스트이다. 배포 전에 위 순서로 한 바퀴 돌리면 된다.
댓글
0아직 댓글이 없습니다. 첫 댓글을 남겨보세요.