/인프라/Too many open files 해결: ulimit·limits.conf·systemd LimitNOFILE 실전
인프라Too many open filesulimit nofile

Too many open files 해결: ulimit·limits.conf·systemd LimitNOFILE 실전

java.io.IOException: Too many open files, EMFILE(errno 24)로 서비스가 죽었나요? ulimit 진단부터 limits.conf·systemd LimitNOFILE·Docker·fs.file-max 설정과 FD 누수 재발 방지까지 복붙 단계별로 정리했습니다.

Too many open files 해결: ulimit·limits.conf·systemd LimitNOFILE 실전

Too many open files 완전 해결: ulimit·limits.conf·systemd LimitNOFILE 실전

새벽 3시, 서비스가 커넥션을 못 받기 시작했다면

배포는 안 건드렸는데 갑자기 헬스체크가 실패하고, 로그에는 Too many open files가 끝없이 찍힌다. 정상으로 보이던 서버가 어느 순간 새 연결을 받지 못하는 이 증상의 정체는 파일 디스크립터(FD) 고갈입니다. 리눅스는 소켓·파일·파이프 전부를 FD로 관리하는데, 프로세스에 허용된 FD 개수(nofile)를 넘는 순간 새 open()/accept()EMFILE(errno 24)로 거부됩니다.

이 글은 에러 식별 → 현재 한계 진단 → 진짜 부하인지 누수인지 분리 → 세션/영구/컨테이너/커널 레벨 복붙 설정 → 재발 방지까지 한 번에 끝내는 플레이북입니다. 급하면 진단 명령부터 바로 치세요.

1. 증상부터 정확히 식별하기 (스택별 에러 로그 4종)

아래 메시지 중 하나라도 봤다면 FD 고갈이 맞습니다. errno 24 = EMFILE이 공통 신호입니다.

TEXT
# Node.js
Error: EMFILE: too many open files, open '/app/uploads/x.tmp'

# Java
java.io.IOException: Too many open files
    at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)

# Nginx
2026/06/13 03:11:02 [crit] 12345#0: accept4() failed (24: Too many open files)

# MySQL
[ERROR] Out of resources when opening file './db/t.MYD' (errno: 24 - Too many open files)

식별 포인트: errno 24, EMFILE, Too many open files 문구가 보이면 디스크 용량이나 권한 문제가 아니라 FD 한계 문제입니다. Nginx의 경우 accept() failed가 동반되면 새 연결 자체를 못 받는 상태입니다.

2. 현재 한계 진단: 몇 개까지 열 수 있고, 몇 개 쓰고 있나

먼저 한계값과 실사용량을 비교합니다.

Bash
# 현재 셸의 soft / hard limit
ulimit -n      # soft limit
ulimit -Hn     # hard limit

# 실제 동작 중인 프로세스의 진짜 한계 (가장 신뢰)
cat /proc/$(pgrep -f java)/limits | grep "open files"

# 실사용 FD 개수 — ls /proc 방식이 가장 정확하고 빠름
ls /proc/<pid>/fd | wc -l
lsof -p <pid> | wc -l

ls /proc/<pid>/fd | wc -l 결과가 한계값에 바짝 붙어 있으면 범인을 잡은 겁니다. 한발 더 들어가 무엇이 FD를 먹고 있는지 타입별로 집계합니다.

Bash
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -rn
# 예) 48000 IPv4  ← 소켓이 대부분이면 커넥션/소켓 누수 의심
#       1200 REG   ← 일반 파일이 비정상적으로 많으면 파일 미close

3. 원인 분리: 진짜 부하 vs FD 누수

설정을 올리기 전에 반드시 원인을 가르세요. 누수라면 한계만 올려도 시간이 지나면 또 터집니다.

패턴판단조치
FD 수가 한계 근처에서 안정적진짜 부하한계 상향
시간이 갈수록 계속 증가 후 터짐FD 누수코드 수정 + 한계 상향

누수 의심 시 CLOSE_WAIT 소켓 누적을 확인합니다. 이 수치가 계속 쌓이면 애플리케이션이 소켓을 닫지 않고 있다는 강력한 증거입니다.

Bash
ss -tan state close-wait | wc -l

코드 레벨에서는 다음을 점검하세요.

  • Java: try-with-resources 미사용, finally에서 close 누락, JDBC/HTTP 커넥션 풀 반환 누락
  • Node: 스트림 close 이벤트 미처리, keep-alive 에이전트의 소켓 미해제
  • 공통: HTTP keep-alive 증가, 마이크로서비스 간 커넥션 풀 과다 설정

실무 경험담: 한 결제 연동 서비스에서 Too many open files가 주기적으로 터졌는데, 원인은 외부 API 호출 시 HttpClient를 매 요청마다 새로 만들고 닫지 않은 것이었습니다. ulimit을 65535로 올려 급한 불은 껐지만 이틀 뒤 다시 재발했고, 결국 클라이언트를 싱글톤으로 재사용하도록 고친 뒤에야 FD 그래프가 평평해졌습니다. 한계 상향은 응급처치, 누수 수정이 완치라는 걸 잊지 마세요.

4. 단계별 복붙 설정

(1) 세션 임시 (테스트/즉시 확인용)

Bash
ulimit -n 65535   # 현재 셸과 자식 프로세스에만 적용, 재로그인 시 사라짐

(2) 영구 — /etc/security/limits.conf

Config
# /etc/security/limits.conf
*       soft    nofile  65535
*       hard    nofile  65535
root    soft    nofile  65535
root    hard    nofile  65535

주의: *는 root에 적용되지 않으므로 root용 줄을 별도로 넣습니다. 또한 /etc/pam.d/의 로그인 모듈에 session required pam_limits.so가 있어야 적용됩니다. 적용하려면 재로그인 또는 서비스 재시작이 필요합니다.

(3) systemd 서비스

limits.conf는 systemd로 띄운 데몬에 적용되지 않습니다. 유닛 파일에 직접 박아야 합니다.

INI
# /etc/systemd/system/myapp.service
[Service]
LimitNOFILE=65535
Bash
systemctl daemon-reload
systemctl restart myapp

배포판마다 systemd 기본 LimitNOFILE 값이 다르니(과거엔 작게, 최근 배포판은 크게) 반드시 명시하는 걸 권장합니다.

(4) Docker / Compose

호스트 ulimit이 아니라 컨테이너 런타임 설정이 적용됩니다.

Bash
docker run --ulimit nofile=65535:65535 myimage
YAML
# docker-compose.yml
services:
  app:
    ulimits:
      nofile:
        soft: 65535
        hard: 65535

쿠버네티스에서는 노드 런타임(containerd/CRI-O) 또는 Pod의 securityContext/런타임 설정을 따릅니다. 호스트에서 아무리 올려도 컨테이너 안에서는 안 먹을 수 있으니 컨테이너 내부에서 cat /proc/1/limits로 확인하세요.

(5) 커널 전역 한계 — fs.file-max

프로세스 한계를 아무리 올려도 시스템 전체 상한을 넘을 수 없습니다.

Bash
# /etc/sysctl.conf
fs.file-max = 2097152
Bash
sysctl -p
cat /proc/sys/fs/file-nr   # 현재 사용/할당/최대 확인

5. 적용 후 검증 — 가장 흔한 함정

limits.conf만 고치고 서비스를 재시작하지 않으면 절대 반영되지 않습니다. 셸에서 ulimit -n이 바뀌어 보여도, 이미 떠 있는 프로세스는 옛 한계를 그대로 들고 있습니다. 반드시 실제 프로세스 기준으로 확인하세요.

Bash
# 서비스 재시작 후, 동작 중인 프로세스의 진짜 한계 확인
cat /proc/$(pgrep -f myapp)/limits | grep "open files"
# Max open files   65535   65535   files   ← 이렇게 나와야 성공

재발 방지 체크리스트

  • 에러 로그 errno 24 확인으로 FD 고갈 확정
  • ls /proc/<pid>/fd | wc -l로 실사용 FD 모니터링 (그래프가 우상향이면 누수)
  • ss -tan state close-wait | wc -l 알람 설정 (임계치 초과 시 통보)
  • 한계의 80% 도달 시 알람 (Prometheus process_open_fds / process_max_fds)
  • 코드: 모든 소켓·파일·커넥션 풀 close 보장(try-with-resources/finally)
  • 설정 변경 후 cat /proc/<pid>/limits로 실제 반영 검증

자주 묻는 질문 (FAQ)

Q. limits.conf를 65535로 바꿨는데 여전히 Too many open files가 납니다. A. 십중팔구 서비스를 재시작하지 않았거나, systemd로 띄운 데몬이라 limits.conf가 무시된 경우입니다. systemd 유닛에 LimitNOFILE=65535를 넣고 daemon-reload 후 재시작한 뒤, cat /proc/<pid>/limits로 실제 반영을 확인하세요.

Q. ulimit을 올려도 며칠 뒤 또 터집니다. 왜죠? A. 부하가 아니라 FD 누수일 가능성이 높습니다. ls /proc/<pid>/fd | wc -l이 시간이 갈수록 계속 증가하거나 CLOSE_WAIT 소켓이 쌓이면 애플리케이션이 소켓/파일을 닫지 않는 것입니다. 코드에서 close 누락을 잡아야 근본 해결됩니다.

Q. Docker 컨테이너 안에서 ulimit이 적용되지 않습니다. A. 컨테이너는 호스트 ulimit이 아니라 런타임 설정을 따릅니다. docker run --ulimit nofile=65535:65535 또는 compose의 ulimits:로 지정하고, 컨테이너 내부에서 cat /proc/1/limits로 확인하세요. 쿠버네티스라면 노드 런타임 설정을 점검해야 합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 12일

댓글

불러오는 중...