Published on

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

Authors

서버를 운영하다 보면 어느 날 갑자기 애플리케이션 로그에 Too many open files가 찍히고, Nginx가 502/504를 뿜거나(업스트림 연결 실패), DB 커넥션이 꼬이고, 심하면 SSH 접속까지 불안정해지는 상황을 겪습니다. 이 에러는 단순히 “파일을 너무 많이 열었다”가 아니라, 프로세스가 사용할 수 있는 파일 디스크립터(File Descriptor, FD) 한도를 초과했음을 의미합니다.

이 글에서는 FD 한도의 계층(커널 전역/사용자/프로세스), ulimit의 함정, systemd 서비스 단위에서의 영구 적용, 그리고 Nginx 튜닝까지 한 번에 정리합니다. (컨테이너/K8s 환경이라면 유사한 증상이 CrashLoopBackOff로 이어질 수 있으니 필요 시 K8s Pod CrashLoopBackOff 원인 7가지와 해결도 함께 참고하세요.)

1) 증상: 무엇이 “open file”인가?

리눅스에서 FD는 실제 파일뿐 아니라 아래 리소스도 포함합니다.

  • TCP/UDP 소켓(대부분의 서버 장애는 이 케이스)
  • 파이프/이벤트FD/epoll FD
  • 로그 파일 핸들
  • UNIX domain socket

따라서 “파일을 안 열었는데요?”라는 반응이 흔합니다. 웹서버/애플리케이션은 요청을 처리하는 동안 소켓을 열고 닫으며, Keep-Alive, 업스트림 커넥션 풀, WebSocket 등으로 FD 사용량이 기하급수적으로 늘 수 있습니다.

2) 원인 구조: FD 한도는 3단으로 걸린다

Too many open files를 해결하려면 “어디에서 막히는지”를 먼저 구분해야 합니다.

2.1 커널 전역 한도: fs.file-max

시스템 전체에서 동시에 열 수 있는 FD 총량입니다.

cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr
# file-nr: 할당된 FD / 사용 중 / 최대치
  • file-max가 낮으면 서버 전체가 동시에 무너질 수 있습니다.
  • file-nr에서 사용 중 FD가 최대치에 근접하면 전역 튜닝이 필요합니다.

영구 설정:

sudo tee /etc/sysctl.d/99-fd.conf <<'EOF'
fs.file-max = 2097152
EOF
sudo sysctl --system

2.2 사용자/세션 한도: PAM limits (ulimit의 기반)

로그인 세션/서비스 실행 컨텍스트에 적용되는 한도입니다.

ulimit -n
ulimit -a

여기서 흔한 함정은:

  • 터미널에서 ulimit -n 65535로 올렸는데 systemd로 실행되는 서비스에는 적용되지 않음
  • /etc/security/limits.conf를 바꿨는데 PAM이 적용되는 로그인 세션에만 반영

2.3 프로세스 한도: RLIMIT_NOFILE

프로세스별 “열 수 있는 FD 최대 개수”입니다. 실제로 에러를 발생시키는 주범.

확인:

pidof nginx
cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep -i "open files"

3) 진단 체크리스트: 지금 어디서 막혔나?

장애 시에는 “증상 → 근거” 순서로 빠르게 좁히는 게 중요합니다.

3.1 어떤 프로세스가 FD를 많이 쓰나?

# 프로세스별 열린 FD 개수(상위 10개)
for pid in /proc/[0-9]*; do
  p=${pid#/proc/}
  fdcount=$(ls -1 $pid/fd 2>/dev/null | wc -l)
  cmd=$(tr -d '\0' < $pid/cmdline 2>/dev/null | cut -c1-80)
  echo "$fdcount $p $cmd"
done | sort -nr | head

3.2 특정 프로세스의 FD 구성은?

예: Nginx 워커가 소켓을 과도하게 잡고 있는지 확인

PID=$(pgrep -n nginx)
ls -l /proc/$PID/fd | head
sudo lsof -p $PID | head
sudo lsof -p $PID | awk '{print $5}' | sort | uniq -c | sort -nr | head

3.3 소켓이 TIME_WAIT로 쌓여 FD를 잡아먹는가?

FD 자체는 TIME_WAIT 상태에서도 일정 기간 점유됩니다(특히 폭주 시).

ss -s
ss -tan state time-wait | wc -l
ss -tan state established | wc -l

애플리케이션이 커넥션을 과도하게 만들거나, 업스트림/클라이언트 Keep-Alive 정책이 비정상일 수 있습니다.

4) 해결 1: ulimit로 즉시 완화(하지만 영구 해결 아님)

장애 중 급한 불을 끄려면 해당 프로세스를 실행하는 쉘/세션에서 ulimit -n을 올린 뒤 재시작할 수 있습니다.

ulimit -n 65535
# 예: 임시로 직접 실행하는 경우
./your-server-binary

하지만 대부분의 서버는 systemd로 떠 있으므로, 이 방식은 재부팅/재시작 시 원복되고, 서비스에는 적용되지 않을 가능성이 큽니다.

5) 해결 2: systemd에서 LimitNOFILE로 영구 적용(가장 중요)

현대 리눅스 배포판에서 서비스 FD 한도는 systemd 유닛 설정이 최종 결정권자인 경우가 많습니다.

5.1 서비스 단위 override 권장

예: nginx.service 또는 앱 서비스에 대해 override 파일을 만듭니다.

sudo systemctl edit nginx

에디터가 열리면:

[Service]
LimitNOFILE=262144

적용:

sudo systemctl daemon-reload
sudo systemctl restart nginx

# 확인
systemctl show nginx -p LimitNOFILE
cat /proc/$(pgrep -n nginx)/limits | grep -i "open files"

5.2 systemd 전역 기본값을 올릴 때(신중)

여러 서비스에 공통으로 적용하려면:

  • /etc/systemd/system.conf (시스템)
  • /etc/systemd/user.conf (유저)

예:

DefaultLimitNOFILE=262144

다만 전역 상향은 모든 서비스의 FD 사용 여지를 늘리므로, 전역 fs.file-max 및 메모리/리소스 영향을 함께 고려해야 합니다.

6) 해결 3: PAM limits.conf(로그인/비-systemd 환경)

SSH 로그인 후 실행하는 배치나, 특정 환경에서 PAM 기반 제한이 중요한 경우가 있습니다.

/etc/security/limits.conf 또는 /etc/security/limits.d/*.conf에 설정:

* soft nofile 65535
* hard nofile 65535

# 특정 사용자만
deploy soft nofile 131072
deploy hard nofile 131072

적용 확인:

ulimit -n

주의: 배포판/설정에 따라 /etc/pam.d/sshd, /etc/pam.d/loginpam_limits.so가 포함되어 있어야 적용됩니다.

7) Nginx에서의 실전 튜닝 포인트

FD 한도만 올리면 끝나는 경우도 있지만, Nginx는 설정이 FD 사용량과 직결됩니다.

7.1 worker_rlimit_nofile

Nginx 워커 프로세스가 사용할 수 있는 FD 상한을 지정합니다.

# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 200000;

events {
  worker_connections 8192;
  multi_accept on;
}
  • worker_connections는 “워커당 동시 연결”의 상한입니다.
  • 이 값은 FD를 직접 소모하므로, worker_rlimit_nofile 및 systemd LimitNOFILE보다 작으면 병목이 됩니다.

대략적인 감으로는:

  • 동시 접속 목표가 50k이고 워커가 8개라면 워커당 6~10k 수준부터 검토
  • 업스트림 연결(프록시), 캐시, 로그 등 추가 FD도 감안해 여유를 둡니다.

7.2 access_log가 과도한 FD/IO를 유발하는 경우

로그 파일 핸들이 FD를 점유하고, 디스크 IO 지연이 워커를 묶어 FD 회전을 느리게 만들 수 있습니다.

  • 장애 시 일시적으로 access_log를 줄이거나 버퍼링 고려
access_log /var/log/nginx/access.log combined buffer=512k flush=1s;

7.3 upstream keepalive와 커넥션 폭주

업스트림으로 keepalive를 켜면 성능이 좋아지지만, 업스트림 소켓 FD를 더 오래 잡습니다.

upstream app {
  server 127.0.0.1:8080;
  keepalive 256;
}

server {
  location / {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://app;
  }
}
  • keepalive를 무작정 크게 잡으면 업스트림 FD가 늘어납니다.
  • 애플리케이션 워커 수/DB 풀과 함께 균형을 맞추세요.

7.4 OS 소켓 백로그와 함께 보기

연결 폭주 시 accept 대기열도 함께 튜닝 포인트가 됩니다.

sysctl net.core.somaxconn

Nginx:

listen 443 ssl backlog=4096;

(단, 이는 Too many open files의 직접 해결책은 아니고, 폭주 상황에서의 부수적 안정화입니다.)

8) 컨테이너/Kubernetes 환경에서의 추가 함정

컨테이너에서 ulimit -n이 기대대로 안 오르는 경우가 많습니다.

  • systemd가 없는 컨테이너: 런타임(Docker) 기본 ulimit, 또는 엔트리포인트에서 설정 필요
  • Kubernetes: 노드/컨테이너 런타임의 ulimit 정책 영향

증상이 반복되어 프로세스가 죽고 재시작되면 CrashLoopBackOff로 보일 수 있습니다. 이때는 애플리케이션 로그에서 EMFILE(Too many open files) 여부를 먼저 확인하고, 노드 레벨 제한과 런타임 제한을 함께 점검하세요. 장애가 CrashLoopBackOff로 확장될 때의 디버깅 흐름은 Kubernetes CrashLoopBackOff 원인별 로그·Probe·리소스 디버깅도 도움이 됩니다.

9) 재발 방지: “한도 상향”만 하지 말고 누수를 잡아라

FD 한도를 올리는 것은 치료가 아니라 버퍼를 늘리는 처방입니다. 근본 원인은 아래 중 하나인 경우가 많습니다.

  • 커넥션/소켓 누수(코드에서 close 누락)
  • Keep-Alive 과다(클라이언트/업스트림)
  • 과도한 동시성(스레드/워커/비동기 태스크 폭주)
  • 로그/파일 핸들 로테이션 문제
  • 외부 장애로 인해 재시도/타임아웃이 누적되어 소켓이 풀리지 않음

9.1 프로세스 FD 추이를 관측(간단 스크립트)

PID=$(pgrep -n nginx)
watch -n 1 "echo -n 'fd='; ls /proc/$PID/fd 2>/dev/null | wc -l; date"
  • 요청이 없는데 FD가 계속 증가하면 누수 의심
  • 트래픽과 함께 증가했다가 정상적으로 내려오면 “용량 문제”일 가능성이 큼

9.2 알림/모니터링 포인트

  • 프로세스별 FD 사용량
  • file-nr(시스템 전체)
  • Nginx active connections, upstream connection 상태

DB 연결 고갈과 함께 나타난다면(예: 커넥션 풀 과다/데드락으로 대기 증가), 트랜잭션 병목도 같이 봐야 합니다. DB 이슈가 동시성 폭주를 만들면 FD도 함께 터질 수 있으니 필요하면 MySQL·PostgreSQL 데드락 분석과 트랜잭션·인덱스 튜닝도 연결해서 점검하세요.

10) 빠른 결론: 실무에서 가장 안전한 적용 순서

  1. 진단: 어떤 프로세스가 FD를 많이 쓰는지, /proc/<pid>/limits로 현재 상한 확인
  2. systemd: 해당 서비스에 LimitNOFILE override 적용(가장 확실)
  3. Nginx: worker_rlimit_nofile, worker_connections, upstream keepalive 균형 조정
  4. 커널 전역: fs.file-max가 병목이면 sysctl로 상향
  5. 재발 방지: FD 증가 패턴 관측 + 누수/재시도 폭주/타임아웃 설계 점검

Too many open files는 설정 한두 줄로 “일단” 사라질 수 있지만, 트래픽 패턴과 연결 정책이 그대로면 언젠가 더 큰 값에서 다시 터집니다. FD를 용량 계획(capacity planning) 대상으로 보고, systemd·Nginx·애플리케이션을 한 세트로 튜닝하는 것이 가장 비용 대비 효과가 큽니다.