Published on

리눅스 Too many open files - ulimit·fd 해결

Authors

서버에서 갑자기 Too many open files 에러가 터지면 대부분은 “파일을 너무 많이 열었다”로 끝나지만, 실제 원인은 훨씬 다양합니다. 파일 디스크립터(FD)는 파일뿐 아니라 소켓, 파이프, 이벤트 디바이스, inotify, epoll 내부 리소스까지 포괄하는 커널 핸들이라서, 웹 서버·DB·프록시·배치 워커 어디서든 터질 수 있습니다.

이 글에서는 에러를 “증상”이 아니라 한도(soft/hard)·적용 범위(세션/서비스/컨테이너)·커널 전역 제한으로 분해해 진단하고, 재발 방지까지 연결되는 해결책을 단계별로 정리합니다.

1) 에러 메시지가 의미하는 것

대표적인 형태는 다음과 같습니다.

  • 애플리케이션 로그: OSError: [Errno 24] Too many open files
  • Nginx: socket() failed (24: Too many open files)
  • Java: java.io.FileNotFoundException: (Too many open files)
  • Go: accept4: too many open files

이때 “열린 파일 수”는 보통 다음 중 하나의 한도에 걸립니다.

  1. 프로세스 단위 한도: RLIMIT_NOFILE (soft/hard)
  2. systemd 서비스 단위 한도: LimitNOFILE
  3. 커널 전역 파일 핸들 한도: fs.file-max
  4. (컨테이너) 컨테이너 런타임이 전달한 rlimit

핵심은 “ulimit -n을 올렸는데도 왜 계속 터지지?”가 자주 발생한다는 점입니다. 대개는 현재 셸 세션만 올린 것이거나, systemd가 더 낮은 값으로 서비스를 띄우는 것이 원인입니다.

2) 빠른 진단 체크리스트

2.1 현재 프로세스의 FD 사용량 확인

문제가 나는 PID를 알고 있다면 가장 먼저 확인합니다.

pid=1234
ls -l /proc/$pid/fd | wc -l

FD 타입별로 감을 잡고 싶으면:

ls -l /proc/$pid/fd | awk '{print $NF}' | head

더 정확하게는 lsof가 편합니다.

sudo lsof -p 1234 | wc -l
sudo lsof -p 1234 | head

특정 파일/소켓이 폭증하는지 보려면:

sudo lsof -p 1234 | awk '{print $5}' | sort | uniq -c | sort -nr | head

2.2 프로세스의 rlimit 확인(soft/hard)

cat /proc/1234/limits | grep -i "open files"

출력 예시는 보통 이런 형태입니다.

  • Max open files 1024 4096 files

여기서 앞이 soft, 뒤가 hard입니다. 애플리케이션이 soft를 기준으로 동작하는 경우가 많습니다.

2.3 시스템 전역 파일 핸들 사용량 확인

전역 한도에 근접하면 전체 시스템이 불안정해질 수 있습니다.

cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr

file-nr는 보통 할당됨 사용중 최대치 형태로 보입니다. 배포판마다 표기가 다를 수 있지만 “사용 중”이 file-max에 근접하면 전역 한도 문제입니다.

3) 원인 패턴 6가지(현업에서 자주 만남)

3.1 커넥션 누수(소켓 FD 누수)

HTTP 클라이언트, DB 커넥션 풀, gRPC 채널을 닫지 않거나, 타임아웃이 없어 연결이 쌓이면 소켓 FD가 증가합니다.

  • 증상: ESTABLISHED 또는 CLOSE_WAIT가 계속 증가
  • 확인:
ss -s
ss -tanp | grep 1234 | head

CLOSE_WAIT가 많으면 애플리케이션이 close를 제대로 호출하지 않는 경우가 흔합니다.

3.2 로그 파일 핸들 누수(로테이션/핸들 재오픈 문제)

로그 로테이션 후에도 프로세스가 기존 파일 핸들을 잡고 있으면 디스크는 계속 차고, FD도 유지됩니다.

  • 확인:
sudo lsof -p 1234 | grep deleted | head

(deleted)가 뜨면 “지워졌는데 프로세스가 잡고 있는 파일”입니다.

3.3 inotify/watch 한도 초과(Too many open files처럼 보이는 케이스)

파일 감시를 많이 쓰는 워처(webpack, nodemon, fluent-bit 일부 설정 등)에서 inotify 한도 때문에 유사한 에러가 납니다.

  • 관련 커널 파라미터:
    • fs.inotify.max_user_watches
    • fs.inotify.max_user_instances

확인:

sysctl fs.inotify.max_user_watches
sysctl fs.inotify.max_user_instances

3.4 systemd 서비스 LimitNOFILE이 낮음

셸에서 ulimit -n을 올려도, systemd가 서비스를 시작할 때 별도의 제한을 적용하면 무용지물입니다.

확인:

systemctl show your-service.service -p LimitNOFILE

3.5 컨테이너의 rlimit 전달 문제

컨테이너 내부에서 ulimit -n을 보면 높게 나오는데도 실제 프로세스 limit이 낮거나, 런타임/오케스트레이터가 제한을 강제하는 경우가 있습니다.

  • 컨테이너 내부에서 반드시 /proc/1/limits 또는 대상 PID의 limits를 확인하세요.
cat /proc/1/limits | grep -i "open files"

3.6 커널 전역 fs.file-max 부족

특정 프로세스 limit을 올렸는데도 시스템 전체에서 FD를 많이 쓰면 전역 한도에 걸립니다. 대규모 프록시/메시/노드 에이전트가 함께 돌 때 종종 발생합니다.

4) 해결 1단계: 임시 조치(즉시 복구)

장애 대응 중이라면 “원인 해결”과 “서비스 복구”를 분리해야 합니다.

4.1 현재 셸에서만 올리기(임시)

ulimit -n
ulimit -n 65535
ulimit -n

주의: 이건 현재 셸과 그 하위 프로세스에만 적용됩니다. systemd로 뜬 서비스에는 영향이 없습니다.

4.2 프로세스 재시작이 가장 빠른 완화책

FD 누수라면 재시작으로 급한 불은 끌 수 있습니다. 다만 재발 방지를 위해 아래 “영구 설정”과 “누수 원인 제거”까지 이어가야 합니다.

5) 해결 2단계: 영구 설정(서비스/시스템 레벨)

5.1 systemd 서비스에 LimitNOFILE 설정(권장)

가장 재현성 있고 안전한 방법입니다. 드롭인으로 추가합니다.

sudo systemctl edit your-service.service

에디터에 다음을 추가:

[Service]
LimitNOFILE=65535

적용:

sudo systemctl daemon-reload
sudo systemctl restart your-service.service
systemctl show your-service.service -p LimitNOFILE

서비스가 실제로 받은 값은 대상 PID로 재확인합니다.

pid=$(systemctl show -p MainPID --value your-service.service)
cat /proc/$pid/limits | grep -i "open files"

5.2 PAM limits로 사용자 세션 기본값 올리기

대화형 로그인/SSH 세션 기반으로 돌리는 프로세스라면 limits.conf를 사용합니다.

/etc/security/limits.conf 또는 /etc/security/limits.d/99-nofile.conf에 추가:

* soft nofile 65535
* hard nofile 65535

특정 사용자만:

appuser soft nofile 65535
appuser hard nofile 65535

주의: PAM 적용은 로그인 세션부터 반영되므로, 기존 세션에는 적용되지 않습니다.

5.3 커널 전역 fs.file-max 조정

전역 한도가 병목이면 sysctl로 조정합니다.

sysctl fs.file-max
sudo sysctl -w fs.file-max=2097152

영구 적용은 /etc/sysctl.conf 또는 /etc/sysctl.d/99-fd.conf:

fs.file-max=2097152

적용:

sudo sysctl --system

주의: 무작정 크게 올리면 커널 메모리 사용량이 증가할 수 있습니다. “필요한 만큼”만 올리고, FD 증가 원인을 반드시 함께 잡으세요.

6) 누수/폭증을 잡는 실전 트러블슈팅

6.1 어떤 FD가 늘어나는지 분류하기

FD 심볼릭 링크를 타입별로 대략 분류할 수 있습니다.

pid=1234
ls -l /proc/$pid/fd | awk '{print $NF}' \
  | sed -e 's/\[.*\]/[...]/g' \
  | cut -d: -f1 \
  | sort | uniq -c | sort -nr | head
  • socket가 많다: 커넥션/풀/타임아웃/백엔드 장애 의심
  • pipe가 많다: 서브프로세스 파이프 누수, 로깅 파이프, 워커 관리 이슈
  • 실제 파일 경로가 많다: 파일 핸들 누수, 로테이션, 캐시 파일 관리 이슈

6.2 네트워크 커넥션 폭증 시 같이 봐야 할 것

  • 애플리케이션 타임아웃(Connect/Read/Write)
  • Keep-Alive 설정
  • 커넥션 풀 최대치
  • 백엔드 장애 시 재시도 폭주

특히 타임아웃이 없으면 FD뿐 아니라 워커가 묶이면서 연쇄 장애가 납니다. 이 흐름은 워커 타임아웃 이슈와도 연결됩니다. 워커가 묶이는 패턴은 Gunicorn Uvicorn Worker timeout 재현과 해결에서도 함께 참고할 만합니다.

6.3 로그 로테이션 이슈 해결 포인트

  • logrotate에서 copytruncate를 쓰면 애플리케이션이 파일을 재오픈하지 않아도 되지만, 대용량에서는 비용이 큽니다.
  • 가능하면 애플리케이션에 SIGHUP 등으로 로그 파일 재오픈을 지원하게 하거나, 로깅을 stdout로 보내고 수집기로 넘기는 구조가 안정적입니다.

삭제된 파일을 계속 잡고 있는지 점검:

sudo lsof -p 1234 | grep deleted | wc -l

7) 서비스별 권장 값과 주의사항

  • 일반 웹/API 서버: nofile 32768~65535에서 시작, 트래픽/커넥션 모델에 따라 상향
  • 프록시/게이트웨이(Nginx/Envoy): 동시 커넥션 목표치 기반으로 계산 후 상향
  • 배치 워커: 파일 I/O 동시성, 외부 커넥션 수(HTTP/DB) 합산해서 산정

중요한 점은 “높게 올리면 해결”이 아니라, FD가 늘어나는 속도정상 상태에서의 평형치를 관측해 “정상 범위”를 만드는 것입니다.

8) 재발 방지: 모니터링과 알람

8.1 프로세스 FD 사용률 알람

프로세스의 Max open files 대비 현재 사용량을 퍼센트로 보고, 70%/85% 같은 임계치로 알람을 거는 것이 실무적으로 유효합니다.

간단한 스크립트 예시:

pid=1234
used=$(ls /proc/$pid/fd 2>/dev/null | wc -l)
limit=$(awk '/Max open files/ {print $4}' /proc/$pid/limits)
echo "used=$used limit=$limit" 

8.2 시스템 전역 file-nr 추적

cat /proc/sys/fs/file-nr

여기서 “사용 중”이 꾸준히 증가하면 특정 프로세스만의 문제가 아닐 수 있습니다.

9) 정리: 올바른 해결 순서

  1. 대상 PID의 FD 사용량과 /proc/PID/limits진짜 한도 확인
  2. lsof, ss로 FD가 무엇으로 채워지는지 분류
  3. 임시 복구는 ulimit 또는 재시작으로 처리하되
  4. 영구 설정은 systemd LimitNOFILE부터 적용(서비스 단위로 안전하게)
  5. 전역 병목이면 fs.file-max를 조정
  6. 마지막으로 누수/폭증 원인을 제거하고 모니터링으로 재발 방지

FD 이슈는 단독으로 끝나지 않고, 타임아웃/재시도/워커 고갈 같은 성능 문제로 이어지는 경우가 많습니다. 장애가 “FD 한도”로 보이더라도, 그 뒤에 있는 커넥션 관리 문제까지 같이 잡아야 진짜로 끝납니다.