홈/인프라/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 실전
목차
  1. 새벽 3시, 서비스가 커넥션을 못 받기 시작했다면
  2. 1. 증상부터 정확히 식별하기 (스택별 에러 로그 4종)
  3. 2. 현재 한계 진단: 몇 개까지 열 수 있고, 몇 개 쓰고 있나
  4. 3. 원인 분리: 진짜 부하 vs FD 누수
  5. 4. 단계별 복붙 설정
  6. 5. 적용 후 검증 — 가장 흔한 함정
  7. 자주 묻는 질문 (FAQ)

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가 동반되면 새 연결 자체를 못 받는 상태입니다.

로그 원문으로 어느 계층에서 걸렸는지도 바로 가를 수 있습니다.

에러 원문걸린 계층먼저 확인할 명령
bash: cannot create temp file for here-document: Too many open files로그인 셸(세션 ulimit)ulimit -Sn
accept4() failed (24: Too many open files) (Nginx error.log)Nginx 워커 프로세스cat /proc/$(pgrep -o nginx)/limits | grep "open files", nginx -T | grep -E 'worker_(rlimit_nofile|connections)'
java.io.IOException / java.net.SocketException: Too many open filesJVM 프로세스cat /proc/$(pgrep -f java)/limits | grep "open files", ls /proc/$(pgrep -f java)/fd | wc -l
Error: EMFILE: too many open files (Node.js)Node 프로세스cat /proc/$(pgrep -f node)/limits | grep "open files"
Too many open files in system (ENFILE, errno 23)커널 전체(fs.file-max)cat /proc/sys/fs/file-nr

셸에서 명령조차 실행되지 않으면 세션 계층, 서비스 로그에만 찍히고 셸은 멀쩡하면 서비스(프로세스) 계층입니다.

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   # 참고: 헤더 줄과 cwd·txt·mem(메모리 매핑) 항목까지 세므로 실제 FD보다 많게 나옴

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

유닛 파일을 직접 고치는 대신 drop-in으로 추가하면 패키지 업데이트에도 설정이 남습니다.

Bash
sudo systemctl edit myapp.service       # 편집기에 [Service] / LimitNOFILE=65535 입력
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p LimitNOFILE -p LimitNOFILESoft   # systemd가 적용한 최종값

systemd 240 이상의 기본값은 soft 1024, hard 524288입니다(DefaultLimitNOFILE=1024:524288). hard가 커도 soft가 1024라서, 프로그램이 스스로 soft를 올리지 않으면 1024에서 막힙니다. 그러니 서비스에는 LimitNOFILE을 명시하는 걸 권장합니다. 여전히 1024로 나오면 daemon-reload를 빠뜨렸거나 drop-in이 다른 유닛에 들어간 것입니다.

(3-1) Nginx — worker_rlimit_nofile과 worker_connections

Nginx 워커는 worker_rlimit_nofile로 자신의 FD 한계를 직접 올릴 수 있습니다. worker_connections가 실제로 열 수 있는 FD보다 크면 accept4() failed (24: Too many open files)가 납니다.

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

events {
    worker_connections 16384;
}

worker_connections는 클라이언트 연결뿐 아니라 업스트림(프록시) 연결까지 모두 포함한 워커당 동시 연결 수입니다. 프록시 요청 하나는 클라이언트·업스트림 연결 두 개를 쓰므로 실제로 받을 수 있는 클라이언트 수는 그만큼 줄어듭니다. worker_rlimit_nofile은 worker_connections보다 크게 잡아 로그·캐시 파일 같은 다른 FD를 위한 여유를 둡니다. nginx 문서

Bash
sudo nginx -t && sudo systemctl reload nginx
cat /proc/$(pgrep -o nginx)/limits | grep "open files"   # 마스터 프로세스 확인

(4) Docker / Compose

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

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

쿠버네티스에는 Pod 스펙으로 nofile을 지정하는 필드가 없고(securityContext에도 없음), 노드의 컨테이너 런타임(containerd/CRI-O)과 그 서비스의 한계 설정을 따릅니다. 호스트에서 아무리 올려도 컨테이너 안에서는 안 먹을 수 있으니 컨테이너 내부에서 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로 확인하세요. 쿠버네티스라면 노드 런타임 설정을 점검해야 합니다.

확인 정보
✦ ✦ ✦
출처·검증 안내

글마다 출처와 확인 범위가 다릅니다. 아래에 확인일·확인 범위·출처가 기록된 글만 그 범위에서 확인된 것입니다. 기록이 없는 글은 적용 전 사용 중인 버전의 공식 문서로 직접 확인하고, 오류는 이메일로 알려주세요. 편집 정책 · 이메일로 제보

이 글에는 사람이 확인한 날짜·범위 기록이 아직 없습니다.

발행 · ·업데이트 ·

댓글

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