EMFILE은 디스크가 가득 찬 줄이 아니라 그 프로세스가 동시에 열 수 있는 파일 개수 한도를 이미 다 써서 새 파일을 못 연 줄입니다. ENOSPC 워처와 구분, ulimit, launchd, watch ignored. Node.js, Next.js, 맥, 한국 1인 개발자 기준. 2026년 9월 Node.js 에러·webpack watch 문서.
파일 디스크립터 한도가 찬 줄입니다. 노드는 파일, 소켓, 감시 핸들을 모두 이 한도 안에서 엽니다. 한 칸이 남아 있지 않으면 open이나 watch가 EMFILE을 던집니다.
Node.js 문서도 EMFILE을 "시스템에서 허용하는 파일 디스크립터를 이미 다 썼다"고 적습니다. 맥은 기본 한도가 낮은 편이라, 같은 프로젝트를 리눅스 CI에선 되고 노트북에서만 터지는 경우가 잦습니다.
ENOSPC file watchers는 리눅스 inotify 감시 개수입니다. 디스크 용량도 아니고, 파일 디스크립터도 아닙니다. 워처가 멈추면 ENOSPC 글을 보고, 여기 줄은 한도 숫자부터 보세요.
메시지
한도 종류
먼저 볼 것
EMFILE too many open files
프로세스 파일 디스크립터
ulimit -n, lsof -p PID
ENOSPC file watchers
리눅스 inotify 감시 개수
fs.inotify.max_user_watches
ENOSPC no space left
디스크 용량
df -h
heap out of memory
노드 힙
max-old-space-size
먼저 읽을 것 | 메시지에 watch가 있으면 감시 핸들이 한도를 채운 겁니다. open이면 파일을 한꺼번에 연 쪽입니다. 같은 EMFILE이라도 고치는 칸이 갈립니다.
EMFILE은 디스크가 아니라 그 프로세스가 연 파일 개수 한도다
한도를 어디서 확인하나
소프트 한도와 하드 한도를 같이 봅니다. ulimit -n은 지금 셸의 소프트 한도, ulimit -Hn은 하드 한도입니다. 소프트를 하드보다 높게 올리면 거절됩니다.
프로세스가 실제로 연 개수는 lsof -p PID | wc -l로 셉니다. 넥스트 개발 서버 PID를 넣어 보면, node_modules 아래 파일이 대부분입니다. 감시가 그 폴더까지 따라가면 한도가 금방 찹니다.
리눅스 컨테이너는 호스트 한도와 따로 갑니다. 도커에서 ulimit -n이 1024로 고정된 이미지가 많고, 그 안에서 next dev나 테스트가 한꺼번에 파일을 열면 로컬 맥보다 먼저 죽습니다.
한도와 열린 파일 개수 확인
# 지금 셸 한도
ulimit -n
ulimit -Hn
# 넥스트 개발 서버가 연 파일 개수
lsof -p $(lsof -ti :3000) | wc -l
# 리눅스 프로세스 한도
cat /proc/$(lsof -ti :3000)/limits | grep 'open files'
맥 launchd는 셸 ulimit을 안 물려 준다
터미널에서 ulimit -n 10240을 올려도, 도크나 런처에서 켠 노드는 예전 한도를 씁니다. 맥 GUI 앱과 launchd 작업은 로그인 셸 설정을 물려받지 않습니다.
Node.js 문서는 같은 셸에서 ulimit -n 2048을 먼저 치고 노드를 켜라고 안내합니다. 크론, launchd, VS Code 작업 패널에서 켜는 서버는 그 셸이 아니라서, plist나 서비스 환경에 한도를 따로 적어야 합니다.
리눅스는 PAM limits.conf의 nofile이 세션에 들어갑니다. systemd 서비스는 LimitNOFILE=을 유닛에 넣지 않으면 기본 1024에 머무는 경우가 많습니다. 한국에서 1인으로 맥 개발, 우분투 CI, 버셀 빌드를 섞으면 세 환경의 숫자가 서로 다릅니다.
환경
한도를 올리는 자리
주의
맥 터미널
ulimit -n, ~/.zshrc
GUI 앱에는 안 먹힘
맥 launchd
plist SoftResourceLimits
셸 rc와 별개
리눅스 세션
limits.conf nofile
재로그인 후 적용
systemd
LimitNOFILE=
유닛마다 따로
한도를 올린 셸에서 켠 노드만 그 숫자를 쓴다
감시 폴더를 줄이는 쪽이 먼저다
한도를 올리기 전에 감시 대상을 줄이세요. 웹팩과 넥스트는 node_modules, .git, .next까지 따라가면 파일 핸들이 수천 개로 늘어납니다. ignored를 넣으면 같은 한도에서도 버팁니다.
워치팩 문서는 watchOptions.ignored로 정규식이나 경로를 빼라고 안내합니다. 넥스트는 기본적으로 node_modules를 건너뛰지만, 모노레포에서 워크스페이스 패키지를 심링크하면 다시 따라가는 경우가 있습니다.
파일을 한꺼번에 읽는 스크립트도 같은 줄이 납니다. Promise.all로 천 개 파일을 동시에 readFile하면 한도를 순식간에 채웁니다. 동시에 열 개수를 제한하거나, 스트림으로 읽고 닫는 편이 안전합니다. 설치 트리가 어긋나 감시가 이상해진 경우는 피어 충돌과 자리가 다릅니다.
메시지에 watch가 있으면 감시부터 줄입니다. 그래도 남으면 그 셸에서 ulimit -n을 올리고 서버를 다시 켭니다. 포트가 남아 서버가 안 뜨는 줄은 EADDRINUSE입니다. 모듈을 못 찾는 줄은 Cannot find module입니다.
한도를 10만으로 올리는 건 최후입니다. 핸들이 새는 스크립트를 가리면, 올린 한도도 다시 찹니다. 빌드가 메모리로 죽는 줄은 힙 부족이라 여기 숫자와 무관합니다.
한국에서 맥 한 대로 여러 넥스트 사이트를 띄우는 경우, 프로젝트마다 개발 포트를 나누고 안 쓰는 서버는 Ctrl+C로 내리는 습관이 한도를 가장 빨리 낮춥니다. 워처만 문제면 ENOSPC 글을 같이 보세요.
한 노트북에서 사이트 여러 개를 동시에 켜 두면, 감시 핸들이 사이트 수만큼 쌓입니다. 지금 손보는 프로젝트만 남기고 나머지는 내리는 편이 한도를 올리는 것보다 빠릅니다. 빌드 스크립트가 로그 파일을 닫지 않고 쌓아 두는지도 같이 보세요. 열어 둔 채 방치된 파일이 있으면 개발 서버와 합쳐져 같은 줄이 다시 납니다. 새벽에 크론이 같은 저장소를 한 번 더 열면 낮에 켠 서버와 한도를 나눠 쓰니, 그때만 죽는 증상도 이 줄로 이어집니다. 숫자를 올리기 전에 지금 무엇이 파일을 붙잡고 있는지부터 세는 편이 짧습니다. 한도 숫자만 키워 두면 원인은 남고, 다음 프로젝트에서 같은 빨간 줄이 다시 올라옵니다.