새 프로젝트 배포를 맡았습니다. 개발팀이 빌드한 React 앱 정적 파일을 서버에 올려야 하고, 추후 백엔드 API 요청도 프록시해야 합니다. EC2 인스턴스에 SSH 접속하니 웹 서버가 아무것도 설치되어 있지 않습니다. 팀장은 "Nginx 써"라고 했지만, nginx.conf가 왜 저렇게 생겼는지, restart와 reload가 다른 건지, 설정 바꾸면 어디서 확인해야 하는지 — 막막합니다.
이 모듈은 그 막막함을 없애는 것이 목표입니다. Nginx를 직접 설치하고, 설정 파일 구조를 이해하고, 정적 파일을 실제로 서빙하는 것까지 해봅니다.
- 1Ubuntu와 CentOS 각각에서 Nginx를 설치할 수 있다
- 2nginx.conf의 worker_processes, events, http, server, location 블록 구조를 설명할 수 있다
- 3정적 파일 서빙 설정을 작성하고 nginx -t로 검증할 수 있다
- 4systemctl reload와 restart의 차이를 이해하고 상황에 따라 선택할 수 있다
- 5액세스 로그와 에러 로그 위치를 알고 tail -f로 실시간 확인할 수 있다
sudo apt-get update && sudo apt-get install -y nginxsudo dnf install -y nginxnginx -vsudo systemctl enable --now nginxcurl -s http://localhost | head -5Nginx 서비스 관리
systemctl로 Nginx 제어하기
설정을 변경하고 systemctl restart nginx를 실행했더니 서비스가 0.3초 끊겼다는 모니터링 알림이 왔습니다. reload를 써야 한다는 것을 나중에 알았습니다. 반대로 서버 재부팅 후 Nginx가 안 뜬다는 제보를 받았는데, enable 없이 start만 했기 때문이었습니다. start와 enable의 차이, restart와 reload의 차이를 모르면 이런 실수가 반복됩니다.
Nginx를 설치했다고 바로 동작하지 않습니다. Linux 서비스 관리자인 systemd를 통해 시작, 중지, 상태 확인을 해야 합니다. 그리고 설정을 바꿀 때마다 restart와 reload 중 어떤 것을 써야 할지 판단할 수 있어야 합니다.
# 상태 확인 (active/inactive/failed)
sudo systemctl status nginx
# 시작 / 중지 / 재시작
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx # 완전 재시작 — 순간적으로 연결 끊김
# 설정 재로드 (처리 중인 요청 유지, 권장)
sudo systemctl reload nginx
# 또는 Nginx 고유 시그널로도 가능
sudo nginx -s reload
# 부팅 시 자동 시작 등록/해제
sudo systemctl enable nginx
sudo systemctl disable nginx
restart vs reload — 언제 무엇을 쓰나:
| 상황 | 권장 명령 | 이유 |
|---|---|---|
| 일반 설정 변경 (server_name, location 등) | reload | 기존 연결 유지, 무중단 |
| Nginx 바이너리 업그레이드 후 | restart | 바이너리 교체는 reload 불가 |
| 서비스 기동이 안 될 때 초기화 | restart | 완전 재시작으로 상태 초기화 |
| SSL 인증서 갱신 반영 | reload | 연결 끊김 없이 새 인증서 적용 |
# 설정 검증 → 이상 없으면 reload (운영 서버 표준 절차)
sudo nginx -t && sudo systemctl reload nginx
Nginx가 요청 하나를 처리하는 단계
Nginx는 요청 한 건을 어떻게 받아 응답하나 — 프로세스 기동부터 응답까지 5단계
systemctl start nginx로 Nginx가 떴습니다. 그런데 브라우저가 GET /index.html 하나를 보내면, 그 요청이 Nginx 안에서 실제로 어떤 경로를 밟는지 알아야 "왜 404지", "왜 403이지", "왜 502지"를 설정의 어느 줄과 연결할 수 있습니다. Nginx의 요청 처리는 아래 5단계로 이뤄지며, 이어지는 절에서 다룰 nginx.conf 블록 구조와 location 매칭이 바로 이 흐름의 ③~④단계입니다.
[기동] systemctl start nginx
│
① master가 설정 로드 + 포트 bind(80/443)
│ └ worker 프로세스를 CPU 코어 수만큼 fork (worker_processes auto)
│
[요청] 클라이언트 → :80
│
② worker의 이벤트 루프(epoll)가 연결 수락 (연결 1개 = worker_connections 1칸)
│
③ Host 헤더 → server 블록 선택(server_name) (없으면 default_server)
│
④ URL → location 매칭(= 먼저, 그다음 ^~, 정규식, prefix)
│ ├─ 정적 파일: root 경로에서 파일 읽어 응답
│ └─ 동적 요청: proxy_pass 로 upstream(WAS)에 전달
│
⑤ 응답 반환 + access_log 기록 (Keep-Alive면 연결 유지·재사용)
▼
[클라이언트] 200 / 403 / 404 / 502 …
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① 기동·bind | master가 설정 파싱 후 포트를 bind하고 worker를 fork | 포트 선점 → bind() to 0.0.0.0:80 failed (98: Address already in use) / 문법 오류 → 기동 실패(nginx -t로 사전 확인) |
| ② 연결 수락 | worker가 이벤트 루프로 논블로킹 연결 수락. 연결마다 fd 1개 소비 | 동시연결 폭주 → worker_connections are not enough / fd 한도 초과 → Too many open files(에러 24) |
| ③ server 매칭 | 요청 Host 헤더로 server_name 매칭, 없으면 default_server | 매칭 server 없고 default도 없음 → 첫 server 블록이 처리(의도치 않은 사이트 응답) |
| ④ location 매칭 | = → ^~ → 정규식 → prefix 우선순위로 처리 위치 결정 → 정적 서빙 or proxy_pass | 파일 권한·소유자 → 403 / 파일·경로 없음 → 404 / upstream 미기동·포트 오지정 → 502 |
| ⑤ 응답·로깅 | 상태코드·헤더·본문 반환, access_log에 기록, Keep-Alive면 연결 유지 | 응답 지연은 이 단계가 아니라 ④의 upstream(WAS·DB) 문제 → 504 |
즉 Nginx가 반환하는 상태코드는 몇 번째 단계에서 갈렸는지를 알려줍니다 — 403·404는 ④에서 정적 파일을 못 읽거나 못 찾은 것(권한·경로), 502는 ④에서 upstream에 연결을 못 한 것(백엔드 문제), Too many open files는 ②에서 fd 한도에 막힌 것(OS 설정)입니다. 그래서 문제가 나면 error.log의 메시지가 어느 단계 어휘(bind, worker_connections, open(), connect() failed)를 쓰는지로 단계를 좁히고, access_log의 상태코드로 교차 확인합니다.
nginx.conf 구조 이해
설정 파일 계층 구조
Nginx 설정이 뜻대로 적용되지 않을 때 가장 먼저 의심해야 할 것은 "올바른 블록에 설정을 넣었는가"입니다. server 블록에 있어야 할 설정이 events 블록에 들어가거나, location 설정이 http 블록 바깥에 위치하면 Nginx는 이를 인식하지 못합니다. 블록 계층을 이해하면 설정 변경이 왜 반영되지 않는지, 어느 블록을 수정해야 하는지를 파일을 열자마자 판단할 수 있습니다.
확대
nginx.conf를 처음 열면 블록 안에 블록이 중첩되어 있어 어디를 수정해야 할지 막막합니다. server 블록을 건드려야 하는데 http 블록 안에서 찾지 못하거나, location 설정을 엉뚱한 레벨에 추가해 설정이 적용되지 않는 경우가 잦습니다. 어느 블록에서 어떤 설정을 하는지 구조를 알면 원하는 위치를 바로 찾을 수 있습니다.
Nginx 설정이 처음 보면 낯선 것은 블록이 중첩되는 구조 때문입니다. 이 구조는 의도적인 설계입니다 — 전역 설정, 연결 설정, HTTP 처리, 가상 호스트, URL 패턴이 각자의 레이어에 있어 관심사가 분리됩니다.
기본 설정 파일 위치:
- Ubuntu/Debian:
/etc/nginx/nginx.conf - CentOS/RHEL:
/etc/nginx/nginx.conf - 사이트별 설정:
/etc/nginx/conf.d/*.conf(권장) - Ubuntu 전통 방식:
/etc/nginx/sites-available/+sites-enabled/
# /etc/nginx/nginx.conf (기본 구조)
# -- 전역 블록 --
# Nginx 프로세스 전체에 적용되는 설정
user nginx;
worker_processes auto; # CPU 코어 수만큼 worker 생성 (auto 권장)
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# -- events 블록 --
# 연결(connection) 처리 방식 설정
events {
worker_connections 1024; # worker당 최대 동시 연결 수
# multi_accept on; # 한 번에 여러 연결 수락 (고트래픽 시 활성화)
}
# -- http 블록 --
# HTTP 처리 전반 설정 (server 블록의 부모)
http {
include /etc/nginx/mime.types; # 파일 확장자 → Content-Type 매핑
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
sendfile on; # 커널 레벨 파일 전송 (정적 파일 성능 향상)
keepalive_timeout 65; # 클라이언트 연결 유지 시간(초)
# -- server 블록 (가상 호스트) --
# 한 서버에서 여러 도메인/포트를 서비스할 때 블록을 추가
server {
listen 80 default_server;
server_name _; # _ 는 모든 Host 헤더에 매칭 (catch-all)
# -- location 블록 (URL 패턴 매칭) --
location / {
root /usr/share/nginx/html;
index index.html;
}
}
# 추가 설정 파일 포함 (conf.d/*.conf)
include /etc/nginx/conf.d/*.conf;
}
블록별 역할 요약:
| 블록 | 위치 | 주요 설정 |
|---|---|---|
| 전역 | 최상단 | worker_processes, error_log, pid |
| events | 전역 하위 | worker_connections (동시 연결 한도) |
| http | 전역 하위 | mime.types, 로그 형식, keepalive |
| server | http 하위 | 포트, 도메인, root 경로 |
| location | server 하위 | URL 패턴별 처리 방식 |
확대
location 블록 — URL 패턴 매칭
/api/ 경로만 Tomcat으로 보내도록 설정했는데 정적 파일도 Tomcat으로 넘어가고 있었습니다. location 매칭 규칙의 우선순위를 몰라서 / 블록이 모든 요청을 먹고 있었기 때문입니다. 설정을 바꿔도 의도대로 동작하지 않는 상황이 되면, 어떤 블록이 실제로 요청을 처리하는지 파악하는 것이 디버깅의 시작입니다. 매칭 규칙을 정확히 알아야 원하는 라우팅을 만들 수 있습니다.
location 블록은 URL 패턴에 따라 요청을 다르게 처리합니다. 실무에서 가장 자주 수정하는 부분입니다. 어떤 요청이 어느 location에 매칭되는지 이해하지 못하면, 설정을 바꿔도 의도대로 동작하지 않습니다.
server {
listen 80;
server_name example.com;
root /var/www/html;
# ① 일반 prefix 매칭 (가장 길게 일치하는 것 선택)
location / {
try_files $uri $uri/ /index.html; # SPA 라우팅에 필수
}
# ② 더 구체적인 prefix — /api/로 시작하는 요청
location /api/ {
proxy_pass http://localhost:3000; # 백엔드 서버로 프록시
}
# ③ 정확히 일치 (= 기호)
location = /favicon.ico {
log_not_found off; # 404 로그 억제
access_log off;
}
# ④ 정규식 매칭 (~: 대소문자 구분, ~*: 구분 안 함)
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d; # 브라우저 캐시 30일
add_header Cache-Control "public, immutable";
}
}
매칭 우선순위 (헷갈리는 부분):
=정확히 일치 (최우선)^~prefix 매칭 (정규식보다 우선)~,~*정규식 (선언 순서대로)- 일반 prefix 매칭 (가장 길게 일치하는 것)
정적 파일 서빙
/etc/nginx/conf.d/myapp.conf 파일을 아래 내용으로 작성합니다. nginx.conf 에 include /etc/nginx/conf.d/*.conf; 가 있어서 이 파일이 자동으로 포함됩니다. 사이트별로 설정을 분리하면 관리가 훨씬 쉽습니다.
server {
listen 80 default_server;
server_name _;
root /var/www/myapp;
index index.html;
# SPA (React/Vue 등) — 모든 경로를 index.html로 fallback
location / {
try_files $uri $uri/ /index.html;
}
# 정적 자산 캐시
location ~* \.(js|css|png|jpg|gif|ico|woff2)$ {
expires 7d;
add_header Cache-Control "public";
}
# 로그 위치
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
}
sudo vim /etc/nginx/conf.d/myapp.conf- ls /etc/nginx/conf.d/ 로 파일 존재 먼저 확인, 그 다음 nginx -t 로 문법 검사 — 파일이 있어도 문법 오류이면 reload 시 Nginx 전체가 설정 반영 불가
- nginx -t 기준: "syntax is ok"와 "test is successful" 두 줄 모두 나와야 정상 — 한 줄만 나오거나 에러 메시지와 함께 나오면 오류 위치(파일명:줄번호)로 이동해 수정
- conf.d/myapp.conf 파일이 있는데 nginx -t 에서 이 파일 관련 에러가 나오면 → 설정 안에 server_name, root, location / 블록 중 하나가 누락된 것 — cat /etc/nginx/conf.d/myapp.conf 로 내용 재확인
Nginx worker 프로세스는 nginx 유저로 실행됩니다. root 디렉터리 파일의 소유자가 nginx여야 읽기 권한 문제가 발생하지 않습니다. Ubuntu는 www-data 유저를 쓰기도 합니다(cat /etc/nginx/nginx.conf | grep user로 확인).
sudo mkdir -p /var/www/myapp && echo '<h1>Hello Nginx</h1>' | sudo tee /var/www/myapp/index.html && sudo chown -R nginx:nginx /var/www/myapp- cat /etc/nginx/nginx.conf | grep user 로 Nginx 실행 계정 먼저 확인, 그 다음 ls -la /var/www/myapp/ 로 소유자 확인 — 실행 계정과 파일 소유자가 일치해야 읽기 가능
- 소유자 기준: nginx(CentOS/RHEL 기본) 또는 www-data(Ubuntu 기본) 중 하나여야 정상 — 소유자가 root이면 Nginx worker가 파일을 못 읽어 403 Forbidden 발생
- 소유자가 맞는데 curl http://localhost 에서 403이 나오면 → 디렉터리 권한이 문제 — ls -la /var/www/ 로 myapp 디렉터리 자체의 실행(x) 권한이 nginx 계정에 있는지 확인
nginx -t가 syntax is ok / test is successful을 반환하면 설정에 문제가 없습니다. 이상이 없을 때만 reload가 실행됩니다(&& 덕분에). 실제 운영 서버에서는 이 두 명령을 항상 세트로 씁니다.
sudo nginx -t && sudo systemctl reload nginx- nginx -t 로 문법 확인 먼저, 통과하면 curl -I http://localhost 로 상태 코드 확인 — nginx -t 없이 reload하면 문법 오류 시 Nginx가 새 설정 반영 안 하고 이전 설정으로 계속 동작
- 응답 코드 기준: 200=정상 서빙, 403=파일 권한 또는 디렉터리 실행 권한 없음(chown/chmod 확인), 404=root 경로 또는 index 파일 없음, 500=nginx.conf 문법 오류로 reload 실패
- curl 200인데 내용이 다른 서버 응답이면 → 기존 default.conf 또는 다른 conf.d 파일과 server_name이 충돌 — nginx -T | grep server_name 으로 모든 설정에서 server_name 목록 확인
로그 확인
액세스 로그와 에러 로그
Nginx가 이상하게 동작할 때 가장 먼저 열어야 하는 것이 로그입니다. 설정 문제인지, 요청 자체에 문제가 있는지를 로그로 구분합니다. 로그 경로를 모르면 장애 대응이 한참 늦어집니다.
기본 로그 위치:
| 파일 | 내용 |
|---|---|
/var/log/nginx/access.log | 모든 HTTP 요청 기록 (IP, URI, 상태코드, 응답크기) |
/var/log/nginx/error.log | Nginx 에러 및 경고 (설정 오류, 권한 문제, upstream 실패 등) |
# 실시간 액세스 로그 모니터링
sudo tail -f /var/log/nginx/access.log
# 출력 예시:
# 192.168.1.10 - - [30/May/2026:09:23:14 +0000] "GET / HTTP/1.1" 200 345 "-" "curl/7.81.0"
# 192.168.1.10 - - [30/May/2026:09:23:20 +0000] "GET /favicon.ico HTTP/1.1" 404 153 "-" "Mozilla/5.0"
# 에러 로그 확인 (warn 이상)
sudo tail -f /var/log/nginx/error.log
# 특정 상태코드만 필터링 (500 에러 찾기)
sudo grep ' 500 ' /var/log/nginx/access.log | tail -20
# 특정 IP의 요청만 확인
sudo grep '192.168.1.100' /var/log/nginx/access.log | tail -10
# 최근 1분간 요청 수 집계
sudo awk '{print $4}' /var/log/nginx/access.log | grep "$(date +'%d/%b/%Y:%H:%M')" | wc -l
# 404 많이 나는 URI 찾기
sudo awk '$9==404{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
에러 로그 레벨 (error_log 지시어에서 설정):
debug— 매우 상세 (개발 환경만, 운영 금지)info— 정보성warn— 경고 (기본값)error— 에러crit— 크리티컬
백그라운드로 로그를 팔로우하면서 요청을 보내면 로그가 실시간으로 찍히는 것을 볼 수 있습니다. 200(성공), 404(없는 경로), try_files fallback 동작을 한 번에 확인합니다.
sudo tail -f /var/log/nginx/access.log &
sleep 1
curl -s http://localhost/
curl -s http://localhost/nonexistent
curl -s http://localhost/api/test
wait- curl http://localhost/ 요청 후 tail -1 /var/log/nginx/access.log 로 기록 확인 — 요청 후 1초 내 로그가 찍혀야 정상, access.log 파일 자체가 없으면 설정에서 access_log 경로 확인
- 로그 상태 코드 기준: 200=정상, 404=try_files 설정 누락 또는 실제 파일 없음, 403=권한 문제 — error.log에 "permission denied"가 있으면 chown nginx:nginx 로 파일 소유자 변경
- access.log에 200이 찍히는데 브라우저에서 SPA 새로고침 시 404가 나오면 → try_files $uri $uri/ /index.html 설정 누락 — location / 블록에 try_files 지시어 추가 필요
트러블슈팅
원인: 80번 포트를 이미 다른 프로세스(Apache, 기존 Nginx, Node.js 등)가 점유하고 있습니다. 서버에 Apache가 설치되어 있었거나, Nginx를 중복 실행했을 때 발생합니다.
# 80번 포트를 점유한 프로세스 확인
sudo ss -tlnp | grep ':80'
# 또는
sudo lsof -i:80
# 출력 예시:
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=1234,fd=4))
# Apache가 충돌하는 경우
sudo systemctl stop apache2
sudo systemctl disable apache2 # 부팅 시 자동 시작도 해제
# 다른 Nginx 인스턴스인 경우
sudo nginx -s quit
sudo systemctl start nginx
# 임시 프로세스인 경우 (PID 확인 후)
sudo kill -TERM <PID>
원인: Nginx가 root 경로의 파일을 읽을 권한이 없습니다. 세 가지 경우가 가장 흔합니다: ① 파일/디렉터리 소유자 문제, ② SELinux 컨텍스트 문제(CentOS/RHEL), ③ 디렉터리에 execute 권한 없음.
# 에러 로그에서 원인 확인 (가장 먼저)
sudo tail -20 /var/log/nginx/error.log
# 예시: [error] 1234#0: *1 open() "/var/www/myapp/index.html" failed
# (13: Permission denied), ...
# ① 파일 소유자 및 권한 확인
ls -la /var/www/myapp/
# Nginx 유저 확인
grep '^user' /etc/nginx/nginx.conf
# 소유권 수정 (Ubuntu는 www-data, CentOS는 nginx)
sudo chown -R nginx:nginx /var/www/myapp
sudo chmod -R 755 /var/www/myapp
# ② SELinux 문제 (CentOS/RHEL에서 자주 발생)
# 임시로 SELinux 컨텍스트 확인
ls -lZ /var/www/myapp/
# 웹 서빙 컨텍스트로 변경
sudo chcon -Rt httpd_sys_content_t /var/www/myapp/
# ③ 부모 디렉터리 execute 권한 확인
# /var/www 또는 /var 에 x 권한이 없으면 403
sudo chmod +x /var/www
원인: 설정 파일에 오타가 있습니다. Nginx는 알 수 없는 지시어를 만나면 즉시 에러를 냅니다. 에러 메시지에 파일 경로와 줄 번호가 명시되어 있어 찾기 쉽습니다.
# 에러 메시지 예시:
# nginx: [emerg] unknown directive "server_namee" in /etc/nginx/conf.d/myapp.conf:3
# nginx: configuration file /etc/nginx/nginx.conf test failed
# 해당 파일의 해당 줄 확인
sed -n '1,10p' /etc/nginx/conf.d/myapp.conf
# 수정 후 재검증
sudo nginx -t
# 설정 파일 전체 덤프 (include 포함 확인)
sudo nginx -T 2>/dev/null | head -50
심화 — worker·event 모델과 '동시 연결'의 진짜 상한
심화: Nginx는 왜 프로세스 몇 개로 수만 연결을 감당하나 — 그리고 어디서 막히나
앞에서 worker_processes auto와 worker_connections 1024를 봤습니다. 이 두 값이 실제로 무엇을 결정하는지 알아야, 트래픽이 몰릴 때 연결이 거부되는 원인을 짚을 수 있습니다.
- 요청마다 스레드를 만들지 않습니다: Nginx는
worker_processes개의 워커 프로세스만 두고, 각 워커가epoll같은 이벤트 루프로 수천 개 연결을 논블로킹으로 다룹니다(Tomcat의 스레드-per-요청과 반대). 그래서 적은 프로세스로 많은 연결을 감당하고 메모리도 적게 씁니다.worker_processes auto는 CPU 코어 수만큼 워커를 띄우는 설정입니다. - 이론상 동시 연결 = worker_processes × worker_connections: 다만 이건 '연결' 상한이지 '클라이언트' 상한이 아닙니다. 리버스 프록시로 쓰면 클라이언트 연결 1개당 upstream 연결 1개를 또 쓰므로, 프록시 환경의 실질 동시 클라이언트 수는 대략 그 절반입니다.
- 진짜 상한은 파일 디스크립터: 모든 연결·열린 파일·로그가 fd를 하나씩 소비합니다.
worker_connections를 아무리 키워도 워커 프로세스의RLIMIT_NOFILE(즉ulimit -n)이 낮으면 거기서 먼저 막혀worker_connections are not enough나Too many open files가 납니다. 그래서worker_connections를 올릴 땐worker_rlimit_nofile(워커의 fd 한도)을 그 이상으로 함께 올려야 값이 실제로 유효해집니다. systemd로 띄운다면 서비스의LimitNOFILE도 확인해야 하고 — 둘 중 낮은 쪽이 실제 상한입니다.
정리하면 튜닝 순서는 worker_processes auto → worker_connections 상향 시 worker_rlimit_nofile을 넉넉히(여유 2배) → systemd LimitNOFILE 확인입니다. 한계도 있습니다: gzip·TLS 핸드셰이크처럼 CPU를 태우는 작업이 병목이면 워커를 코어 수 이상으로 늘려도 이득이 없습니다 — 이벤트 모델이 잘 듣는 건 I/O 대기가 많은 경우입니다.
상황: 평소엔 멀쩡한데 트래픽이 몰리는 순간부터 일부 사용자만 연결이 안 되고 간헐적으로 502·연결 실패가 섞입니다. error.log를 보면 worker_connections are not enough 또는 accept4() failed (24: Too many open files)가 반복해서 찍힙니다. 서버 CPU·메모리는 아직 여유가 있습니다.
원인: 동시 연결이 한도에 닿은 것입니다. 그런데 한도가 두 겹입니다 — ① worker_connections(워커당 연결 상한), ② 워커 프로세스의 파일 디스크립터 한도(RLIMIT_NOFILE). 리버스 프록시라 클라이언트 연결과 upstream 연결로 fd를 두 배 가까이 쓰는데, worker_connections만 올리고 fd 한도를 안 올려서 에러 코드 24(Too many open files)에서 먼저 막힌 것입니다. 즉 키워 둔 worker_connections 값이 실제로는 낮은 fd 한도에 갇혀 무효였습니다.
진단: 워커의 실제 fd 상한을 봅니다 — cat /proc/$(pgrep -f 'nginx: worker' | head -1)/limits | grep 'open files'. nginx -T | grep -E 'worker_connections|worker_rlimit_nofile'로 설정값과 대조하고, ss -s로 현재 소켓 수가 상한 근처인지 확인합니다. error.log의 문구도 단서입니다 — worker_connections are not enough면 그 지시어 상한에, Too many open files면 fd 한도에 막힌 것입니다.
해결: worker_rlimit_nofile을 worker_connections보다 넉넉히(예: 2배 이상) 올리고, systemd 서비스라면 LimitNOFILE도 함께 상향합니다(둘 중 낮은 값이 실제 상한). worker_processes는 auto로 두고, 프록시 환경은 연결이 두 배로 드는 것을 감안해 여유를 줍니다. worker_rlimit_nofile은 마스터가 시작 시점에 적용하므로, 값에 따라 reload가 아니라 restart가 필요할 수 있습니다.
실제 업무에서 이 지식이 쓰이는 상황:
팀에 새로 합류한 주니어 개발자가 가장 먼저 맞닥뜨리는 Nginx 상황은 크게 세 가지입니다.
1. 프론트엔드 배포: React 빌드 결과물(npm run build 후 /dist)을 서버에 올릴 때, Nginx root 경로와 try_files 설정을 잡아주는 것이 역할입니다. SPA 라우팅을 위해 try_files 없이 배포하면 페이지 새로고침 시 404가 납니다.
2. 설정 변경 요청 처리:
# 실무에서 반복되는 패턴:
# 1. 설정 수정
sudo vim /etc/nginx/conf.d/myapp.conf
# 2. 검증
sudo nginx -t
# 3. 이상 없으면 무중단 재로드
sudo systemctl reload nginx
# 4. 로그로 확인
sudo tail -f /var/log/nginx/access.log
3. 장애 대응 첫 30초:
# Nginx 기동 안 됨 → 원인 파악 루틴
sudo systemctl status nginx # 상태와 최근 로그
sudo journalctl -u nginx -n 30 --no-pager # 상세 에러
sudo nginx -t # 설정 파일 검증
sudo ss -tlnp | grep ':80' # 포트 충돌 확인
sudo tail -20 /var/log/nginx/error.log # 에러 로그 직접 확인
Nginx 설정을 직접 바꿀 줄 알고, nginx -t로 검증하고, reload로 무중단 반영하는 것만 해도 팀의 배포 속도가 눈에 띄게 빨라집니다.
명령어·단축키 빠른 참조
이 모듈에서 다룬 Nginx 설치·서비스 제어·검증·로그 명령을 실전 옵션과 함께 모았습니다. "예" 열의 조합을 그대로 써도 됩니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
nginx -t | 설정 문법 검사(무중단) | 운영 표준 nginx -t && systemctl reload nginx |
nginx -T | include까지 합친 전체 설정 덤프 | 중복 확인 nginx -T | grep server_name |
systemctl reload nginx | 무중단 설정 재적용 | 기존 요청 유지, 일반 설정 변경에 사용 |
systemctl restart nginx | 완전 재시작(순간 끊김) | 바이너리 업그레이드·상태 초기화 시 |
systemctl enable --now nginx | 기동+부팅 자동시작 등록 | enable 누락 시 재부팅 후 미기동 |
nginx -s reload / -s quit | 시그널로 재적용/우아한 종료 | 중복 프로세스 정리 nginx -s quit |
ss -tlnp | grep ':80' | 80 포트 점유 프로세스 확인 | 대안 lsof -i:80, 충돌원(Apache) 파악 |
tail -f /var/log/nginx/access.log | 요청 실시간 확인 | 에러는 /var/log/nginx/error.log |
awk '$9==404{print $7}' access.log | sort | uniq -c | sort -rn | 404 많은 URI 집계 | 상태코드별 grep ' 500 ' access.log |
journalctl -u nginx -n 30 --no-pager | 기동 실패 상세 원인 | systemctl status nginx 와 함께 |
location / try_files | URL 매칭·SPA fallback | try_files $uri $uri/ /index.html; |
ss -s | 소켓 요약(연결 상한 근접 확인) | fd 한도 cat /proc/<worker>/limits |
관련 모듈로 더 깊이:
- Nginx 리버스 프록시와 로드밸런싱 설정 — 정적 서빙을 넘어 백엔드로 요청을 넘기는 리버스 프록시 설정
- Let's Encrypt 인증서 발급과 Nginx SSL 설정 — 이 Nginx에 도메인을 연결하고 HTTPS를 적용하는 다음 단계
- HTTP 요청이 서버에 도달하기까지의 전체 흐름 — Nginx가 전체 요청 흐름 중 어디에 위치하는지 큰 그림으로 보기
다음 모듈에서는 이 Nginx 앞에 도메인을 연결하고 HTTPS를 적용하는 방법을 다룹니다.