/인프라/Too many open files(EMFILE errno 24) 30초 진단·복구 런북
인프라too many open filesulimit

Too many open files(EMFILE errno 24) 30초 진단·복구 런북

리눅스·Nginx·Node·Java의 'Too many open files'(EMFILE, errno 24)를 30초에 진단하는 매칭표와 계층별 복붙 복구 명령. ulimit -n, systemd LimitNOFILE, worker_rlimit_nofile, FD 누수 판별까지 정리했습니다.

Too many open files(EMFILE errno 24) 30초 진단·복구 런북

새벽 3시, 로그에 'Too many open files'가 찍혔다

개념 설명은 나중에. 지금은 서비스가 소켓을 못 열고 있고, 로그에 Too many open files(EMFILE, errno 24)가 계속 쌓이는 중이다. 이 런북은 에러 원문 매칭 → 상태 확인 명령 → 계층별 복붙 복구 순서로만 구성돼 있다. 위에서 아래로 순서대로 붙여넣으면 된다.

한 가지만 기억하자. 이 에러는 "파일 디스크립터(FD)"가 한계에 닿았다는 뜻이고, 문제는 항상 세션 / 프로세스(서비스) / 워커 / 커널 네 계층 중 하나에 있다. 어느 계층인지만 30초 안에 판별하면 복구는 명령 한두 줄이다.

적용 범위: Linux(RHEL/CentOS 79, Ubuntu 18.0424.04), systemd 기반 서비스, Nginx 1.1x, Node.js/Java 애플리케이션, Docker/containerd·K8s 컨테이너 환경.


30초 진단 — 에러 원문 3종 매칭표

로그에 찍힌 원문을 아래 표에서 찾으면 어느 계층 문제인지 바로 나온다.

에러 원문 (로그에서 검색)발생 계층우선 확인 명령
bash: cannot create temp file for here-document: Too many open files세션(로그인 셸의 ulimit)ulimit -Sn
accept() failed (24: Too many open files) (Nginx error.log)워커 프로세스cat /proc/$(pgrep -o nginx)/limits | grep "open files"
worker_connections are not enough + too many open filesNginx 워커 설정nginx -T | grep -E 'worker_(rlimit_nofile|connections)'
java.io.IOException: Too many open filesJVM 프로세스cat /proc/$(pgrep -f java)/limits | grep "open files"
java.net.SocketException: Too many open filesJVM 프로세스(소켓 누수 의심)lsof -p $(pgrep -f java) | grep -c 'TCP'
Error: EMFILE: too many open files (Node.js)Node 프로세스cat /proc/$(pgrep -f node)/limits | grep "open files"

핵심 판별 규칙 한 줄:

  • 셸에서 명령 실행조차 안 되면 → 세션 계층. ulimit -n만 올리면 된다.
  • 서비스 로그에만 찍히고 셸은 멀쩡하면 → 프로세스/서비스 계층. systemd·nginx 설정을 봐야 한다.
  • file-nrfile-max에 근접했으면 → 커널 계층(전체 시스템). 이건 드물지만 치명적이다.

현재 상태 확인 명령 세트

복구 전에 "지금 얼마나 열려 있고 한계가 얼마인지"를 확인한다. 여기서 ulimit -n보다 /proc/PID/limits를 신뢰하라. 데몬은 로그인 셸과 다른 한계로 뜨는 경우가 많기 때문이다.

1) 실제 프로세스가 연 FD 수

Bash
# 앱 프로세스가 현재 열고 있는 FD 개수
lsof -p $(pgrep -f myapp) | wc -l

예상 출력:

TEXT
4832

2) 그 프로세스에 실제 적용된 한계 (가장 신뢰)

Bash
cat /proc/$(pgrep -f myapp)/limits | grep "open files"

예상 출력:

TEXT
Max open files            1024                 4096                 files
#                         ^soft(현재 적용)      ^hard(상향 가능 최대)

위 예시라면 soft가 1024이고 현재 4832개를 열려던 셈 → 한계 초과가 명백하다.

3) 세션 ulimit (셸 계층 확인용)

Bash
ulimit -Sn   # soft limit
ulimit -Hn   # hard limit

예상 출력:

TEXT
1024
1048576

soft가 1024로 낮으면 셸에서 뜬 앱은 1024에 걸린다. hard가 크면 soft만 올리면 즉시 해결.

4) 시스템 전체 (커널 계층)

Bash
cat /proc/sys/fs/file-nr
# 출력: 사용중  미사용(할당됐다 반납)  최대치

예상 출력:

TEXT
9856    0    2097152
#할당    미사용  file-max

첫 숫자가 세 번째(file-max)의 80%를 넘으면 커널 상한 자체가 부족한 것이다. 대부분은 여기까지 오지 않고 프로세스 계층에서 끝난다.

판단 기준 요약: lsof 개수 / soft limit0.8 이상이면 위험, 1.0 근접이면 이미 에러 발생 중.


계층별 복구 — 위에서 아래로 복붙

문제 계층에 해당하는 블록만 실행하면 된다. 어디까지 올려야 할지는 각 단계 끝의 분기를 보라.

① 세션 임시 상향 (지금 당장, 재부팅 시 사라짐)

Bash
ulimit -n 65535        # 현재 셸에만 적용
ulimit -n              # 확인

이 셸에서 재기동하는 프로세스에만 적용된다. 여기서 앱을 다시 띄우면 즉시 복구되지만, 재부팅하면 원상복귀 → 반드시 아래 영구 설정을 함께 한다.

분기: hard limit보다 큰 값은 일반 사용자가 못 올린다(ulimit: value exceeds hard limit). 그럴 땐 root로 hard까지 올리거나 ②로 간다.

② 영구 설정 — /etc/security/limits.conf

Bash
sudo tee -a /etc/security/limits.conf <<'EOF'
*        soft    nofile    65535
*        hard    nofile    65535
root     soft    nofile    65535
root     hard    nofile    65535
EOF

pam_limits가 활성화돼야 이 파일이 적용된다. 확인:

Bash
grep pam_limits /etc/pam.d/common-session /etc/pam.d/login 2>/dev/null
# 출력에 session required pam_limits.so 가 있어야 함

주의: limits.conf로그인 세션에만 적용된다. 즉 systemd로 뜨는 데몬에는 안 먹는다. 이게 "limits.conf 고쳤는데 왜 안 돼?"의 8할이다. 그럴 땐 ③으로.

③ systemd 서비스 — LimitNOFILE (데몬은 여기가 정답)

Nginx, 애플리케이션 등 systemd가 관리하는 서비스는 unit의 LimitNOFILE이 우선한다. drop-in으로 안전하게 추가:

Bash
sudo systemctl edit myapp.service

열린 편집기에 아래를 입력:

INI
[Service]
LimitNOFILE=65535

적용:

Bash
sudo systemctl daemon-reload
sudo systemctl restart myapp.service

# 검증 — 실제 적용됐는지 반드시 확인
cat /proc/$(pgrep -f myapp)/limits | grep "open files"

예상 정상 결과:

TEXT
Max open files            65535                65535                files

분기: 여전히 1024로 나오면 → daemon-reload를 빠뜨렸거나 drop-in 경로(/etc/systemd/system/myapp.service.d/override.conf)가 다른 unit을 가리킨 것. systemctl show myapp -p LimitNOFILE로 최종값을 확인하라.

④ Nginx — worker_rlimit_nofile ↔ worker_connections

Nginx는 systemd LimitNOFILE과 별개로 자체 지시어가 필요하다. worker_connections가 실제 열 수 있는 FD보다 크면 accept() failed (24)가 난다.

Nginx
# /etc/nginx/nginx.conf 최상단(main 컨텍스트)
worker_rlimit_nofile 65535;

events {
    worker_connections 16384;   # worker_rlimit_nofile 이하로
}

관계식(대략): 필요 FD ≈ worker_connections × 2 (클라이언트+업스트림) + 예비. 그래서 worker_rlimit_nofileworker_connections의 2배 이상 여유를 준다.

Bash
sudo nginx -t          # 문법 검사
sudo systemctl reload nginx
cat /proc/$(pgrep -o nginx)/limits | grep "open files"   # 65535 확인

⑤ 커널 상한 — fs.file-max (전체가 부족할 때만)

file-nrfile-max에 근접한 극단적 상황에서만:

Bash
# 즉시 적용
sudo sysctl -w fs.file-max=2097152

# 영구화
echo 'fs.file-max = 2097152' | sudo tee /etc/sysctl.d/99-nofile.conf
sudo sysctl --system

대부분 서버는 기본 file-max가 이미 수백만이라 여기까지 올 일은 거의 없다. 이 단계까지 왔다면 십중팔구 FD 누수를 의심해야 한다. 아래로.


FD 누수 vs 한계 부족 — 재발을 막는 결정적 구분

한계만 올리고 끝내면 며칠 뒤 더 큰 값으로 또 터진다. 둘을 반드시 구분하라.

추이 관찰

Bash
# 5초마다 FD 개수 추이 관찰
watch -n5 'ls /proc/$(pgrep -f myapp)/fd | wc -l'
  • 재시작 직후부터 계속 우상향 → FD 누수. 코드에서 소켓/파일/커넥션을 close() 안 하고 있다.
  • 평상시 낮다가 트래픽 피크에서만 한계 접근 → 한계 부족. 위 계층별 상향으로 끝.

누수 의심 시 FD 타입 집계

Bash
lsof -p $(pgrep -f myapp) | awk '{print $5}' | sort | uniq -c | sort -rn

예상 출력:

TEXT
  38210 IPv4     # 소켓이 압도적 → 커넥션 close 누락 / keep-alive 과다
    412 REG      # 일반 파일
     88 pipe

IPv4/sock이 비정상적으로 많으면 → HTTP 클라이언트 커넥션 풀 미반환, DB 커넥션 미반환, keep-alive 설정을 점검한다. REG가 계속 늘면 → 파일 핸들을 안 닫는 코드다.

컨테이너·K8s 환경의 함정

호스트에서 ulimit -n을 올려도 컨테이너 안 프로세스는 별개다. Docker/containerd 기본 nofile이 호스트와 달라 혼선이 잦다.

Bash
# 컨테이너 실행 시 명시
docker run --ulimit nofile=65535:65535 myimage

# 실행 중 컨테이너 내부 실제 한계 확인
docker exec <cid> sh -c 'cat /proc/1/limits | grep "open files"'

K8s는 노드의 containerd 기본값과 Pod의 securityContext를 함께 확인해야 한다. "호스트는 65535인데 왜 컨테이너는 1024?"라면 여기가 원인이다.


정상 / 이상 판별 기준표

지표정상주의위험(조치)
lsof 개수 / soft limit< 50%50~80%≥ 80% → 상향 검토
재시작 후 FD 추이안정(수평)완만한 증가지속 우상향 → 누수
IPv4/sock 비율워크로드에 비례급증 추세압도적 다수 → 커넥션 close 점검
file-nr 1열 / file-max< 50%50~80%≥ 80% → fs.file-max 상향

이 표 기준으로 "지금 올릴지, 코드를 볼지"가 갈린다.


리소스 한계 트러블슈팅 — 함께 보면 좋은 런북

이 글은 "FD 한계" 편이다. 리소스 한계 장애는 대부분 한계 확인 → 임시 상향 → 영구 상향 → 누수 판별의 같은 4단 흐름으로 해결된다. 다른 자원도 같은 패턴으로:

  • fork: Resource temporarily unavailable — 프로세스/스레드 한계(nproc, pids.max) 편
  • PostgreSQL too many clients already — DB 커넥션 한계(max_connections, 커넥션 풀) 편

세 편 모두 "한계인가 누수인가"를 구분하는 지점이 핵심이다.


자주 묻는 질문 (FAQ)

Q. limits.conf를 65535로 고쳤는데 서비스에는 왜 안 먹나요? A. /etc/security/limits.conf는 로그인 세션(PAM)에만 적용됩니다. systemd로 뜨는 데몬은 unit의 LimitNOFILE이 우선하므로 systemctl edit으로 drop-in을 추가하고 daemon-reload && restart 해야 합니다. 적용 여부는 cat /proc/PID/limits로 확인하세요.

Q. ulimit -n을 올렸는데 재부팅하니 원래대로 돌아갑니다. A. ulimit은 현재 셸에만 적용되는 임시 설정입니다. 영구화하려면 세션은 limits.conf, 데몬은 systemd LimitNOFILE, Nginx는 worker_rlimit_nofile을 각각 설정해야 합니다.

Q. 한계를 올려도 며칠 뒤 또 터집니다. A. FD 누수 가능성이 높습니다. watch -n5 'ls /proc/PID/fd | wc -l'로 추이를 보고, 재시작 직후부터 계속 우상향이면 코드에서 소켓/파일/커넥션 close() 누락을 점검하세요. 한계 상향은 임시방편일 뿐입니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·

댓글

첫 번째 댓글을 남겨보세요.