웹 서버·WAS 용어 사전
nginxulimit · worker_rlimit_nofile · Too many open files · EMFILE

파일 디스크립터

연결 하나가 곧 디스크립터 하나. worker_connections를 아무리 키워도 여기서 막힌다.

연결 하나 = 소켓 하나 = 파일 디스크립터 하나. 동시 연결의 진짜 상한이다.

디스크립터를 쓰는 것

  • 클라이언트 연결
  • 업스트림 연결 (프록시면 요청당 2개)
  • 로그 파일 · 열어 둔 정적 파일 · 캐시 파일

worker_connections 를 아무리 키워도 OS 한도나 worker_rlimit_nofile 이 낮으면 거기서 막힌다

증상과 확인

  • 증상 — error log 에 "Too many open files" (EMFILE)
    • accept 가 실패하며 연결이 거부된다
    • 부하가 오를 때만 나타나 재현이 어렵다
확인
  ulimit -n                                    # 셸 기준 (프로세스와 다를 수 있다)
  cat /proc/<worker pid>/limits | grep 'open files'   # 실제 프로세스 한도
  ls /proc/<worker pid>/fd | wc -l             # 현재 사용량

systemd 로 띄웠다면 유닛의 LimitNOFILE 이 진짜 값이다

  • ulimit 만 올리고 넘어가는 것이 가장 흔한 실수

세 곳을 함께 올린다

  • 커널 전체 — /proc/sys/fs/file-max

  • 프로세스 한도 — systemd LimitNOFILE 또는 /etc/security/limits.conf

  • nginx 설정 — worker_rlimit_nofile

  • 규칙

    • worker_rlimit_nofile ≥ worker_connections + 여유(로그·업스트림·캐시)
    • ② 는 ③보다 크거나 같아야 한다 — 프로세스가 자기 한도를 넘겨 올릴 수 없다
worker_rlimit_nofile 65535;
events { worker_connections 16384; }

컨테이너에서

컨테이너는 호스트의 한도를 그대로 물려받지 않는다

  • 런타임 기본값이 낮게 잡혀 있는 경우가 있다
  • k8s 라면 파드 스펙이나 런타임 설정에서 정해진다

"로컬에서는 되는데 배포하면 특정 부하에서 거부된다" 의 후보 1순위다 확인은 컨테이너 안에서 /proc/<pid>/limits 를 직접 본다

디스크립터 누수와 구분한다

  • 한도 부족 — 사용량이 한도 근처에서 평평하다 (천장에 붙어 있다)
  • 누수 — 사용량이 시간에 따라 계속 우상향한다
    • 닫지 않은 소켓·파일이 쌓이는 것이다

lsof -p <pid> | wc -l 를 주기적으로 재 두면 둘이 바로 갈린다 lsof | grep deleted 는 '삭제됐는데 열려 있는' 파일을 잡는다 (로그 회전 후 SIGUSR1 을 안 보낸 전형적 사례)

에페메랄 포트도 함께 마른다

프록시가 업스트림으로 나갈 때 출발지 포트를 하나씩 쓴다 기본 범위는 대략 32768~60999 → 약 2.8만 개

keepalive 를 안 걸면 요청마다 새 연결 → 포트를 빠르게 소모 게다가 닫힌 연결은 TIME_WAIT 로 한동안 남는다 (기본 60초)

  • 증상 — "Cannot assign requested address" (EADDRNOTAVAIL)

  • 확인 — ss -s # TIME_WAIT 수

    • cat /proc/sys/net/ipv4/ip_local_port_range
  • 대응 — upstream keepalive (근본 대응)

    • 포트 범위 확대 · 업스트림 IP 를 늘려 (IP,포트) 조합을 넓힌다

한도를 올리기 전에 확인할 것

  • 한도를 올려도 메모리가 따라가야 한다

    • 소켓 하나당 커널 버퍼가 잡힌다 (수 KB ~ 수십 KB)
    • 10만 연결이면 버퍼만으로 수 GB 가 될 수 있다
  • net.core.somaxconn — listen 백로그 상한

  • net.ipv4.tcp_max_syn_backlog — SYN 큐 크기

  • net.core.rmem_max / wmem_max — 소켓 버퍼 상한

  • 한도만 올리고 메모리·백로그를 안 보면 다른 곳에서 막힌다

면접 함정

  • "ulimit -n을 올렸으니 됐다" → 서비스 프로세스의 실제 한도는 systemd 유닛이 정한다.
  • "연결 수만큼만 있으면 된다" → 프록시는 2배, 로그·캐시도 쓴다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — nginx 프로세스 모델 — 만 개의 연결