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이 공통 신호입니다.
# 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. 현재 한계 진단: 몇 개까지 열 수 있고, 몇 개 쓰고 있나
먼저 한계값과 실사용량을 비교합니다.
# 현재 셸의 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 -lls /proc/<pid>/fd | wc -l 결과가 한계값에 바짝 붙어 있으면 범인을 잡은 겁니다. 한발 더 들어가 무엇이 FD를 먹고 있는지 타입별로 집계합니다.
lsof -p <pid> | awk '{print $5}' | sort | uniq -c | sort -rn
# 예) 48000 IPv4 ← 소켓이 대부분이면 커넥션/소켓 누수 의심
# 1200 REG ← 일반 파일이 비정상적으로 많으면 파일 미close3. 원인 분리: 진짜 부하 vs FD 누수
설정을 올리기 전에 반드시 원인을 가르세요. 누수라면 한계만 올려도 시간이 지나면 또 터집니다.
| 패턴 | 판단 | 조치 |
|---|---|---|
| FD 수가 한계 근처에서 안정적 | 진짜 부하 | 한계 상향 |
| 시간이 갈수록 계속 증가 후 터짐 | FD 누수 | 코드 수정 + 한계 상향 |
누수 의심 시 CLOSE_WAIT 소켓 누적을 확인합니다. 이 수치가 계속 쌓이면 애플리케이션이 소켓을 닫지 않고 있다는 강력한 증거입니다.
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) 세션 임시 (테스트/즉시 확인용)
ulimit -n 65535 # 현재 셸과 자식 프로세스에만 적용, 재로그인 시 사라짐(2) 영구 — /etc/security/limits.conf
# /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로 띄운 데몬에 적용되지 않습니다. 유닛 파일에 직접 박아야 합니다.
# /etc/systemd/system/myapp.service
[Service]
LimitNOFILE=65535systemctl daemon-reload
systemctl restart myapp배포판마다 systemd 기본 LimitNOFILE 값이 다르니(과거엔 작게, 최근 배포판은 크게) 반드시 명시하는 걸 권장합니다.
(4) Docker / Compose
호스트 ulimit이 아니라 컨테이너 런타임 설정이 적용됩니다.
docker run --ulimit nofile=65535:65535 myimage# docker-compose.yml
services:
app:
ulimits:
nofile:
soft: 65535
hard: 65535쿠버네티스에서는 노드 런타임(containerd/CRI-O) 또는 Pod의 securityContext/런타임 설정을 따릅니다. 호스트에서 아무리 올려도 컨테이너 안에서는 안 먹을 수 있으니 컨테이너 내부에서 cat /proc/1/limits로 확인하세요.
(5) 커널 전역 한계 — fs.file-max
프로세스 한계를 아무리 올려도 시스템 전체 상한을 넘을 수 없습니다.
# /etc/sysctl.conf
fs.file-max = 2097152sysctl -p
cat /proc/sys/fs/file-nr # 현재 사용/할당/최대 확인5. 적용 후 검증 — 가장 흔한 함정
limits.conf만 고치고 서비스를 재시작하지 않으면 절대 반영되지 않습니다. 셸에서 ulimit -n이 바뀌어 보여도, 이미 떠 있는 프로세스는 옛 한계를 그대로 들고 있습니다. 반드시 실제 프로세스 기준으로 확인하세요.
# 서비스 재시작 후, 동작 중인 프로세스의 진짜 한계 확인
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로 확인하세요. 쿠버네티스라면 노드 런타임 설정을 점검해야 합니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...