서비스 로그에 Too many open files가 찍혀서 ulimit -n 65536으로 올렸는데, 재시작해도 여전히 죽습니다. 원인은 단순합니다. 셸에서 본 ulimit과 systemd가 데몬에 적용하는 한도는 서로 다른 경로이기 때문입니다. 터미널에서 올린 값은 그 셸과 자식에만 적용되고, systemd가 띄운 서비스에는 전혀 영향이 없습니다.
진단 — 두 곳을 따로 본다
먼저 내 셸의 한도:
로컬 터미널
ulimit -n
# 1024
그리고 실제로 돌고 있는 프로세스의 한도를 확인합니다. 이게 진짜 적용된 값입니다.
로컬 터미널
cat /proc/$(pgrep -f myapp)/limits | grep "open files"
# Max open files 1024 1024 files
systemd 서비스라면 유닛에 설정된 값도 봅니다.
서버 터미널
systemctl show myapp.service -p LimitNOFILE
# LimitNOFILE=1024
셸의 ulimit -n을 65536으로 올려도 위 두 값이 1024 그대로면, 데몬에는 아무것도 반영되지 않은 것입니다.
개념 — 어디서 한도가 정해지나
| 경로 | 적용 대상 | 설정 위치 |
|---|---|---|
ulimit -n (셸) | 로그인 셸과 그 자식 | /etc/security/limits.conf (PAM 경유) |
systemd LimitNOFILE | systemd가 띄운 서비스 | 유닛 파일 [Service] |
limits.conf는 PAM 로그인 세션에만 걸립니다. systemd가 부팅 시 직접 띄운 데몬은 PAM을 거치지 않으므로 limits.conf를 무시하고 유닛의 LimitNOFILE을 따릅니다. 여기서 어긋남이 생깁니다.
해결 — systemd 서비스에 직접 적용
drop-in으로 안전하게 덮어씁니다(원본 유닛은 건드리지 않음).
서버 터미널
sudo systemctl edit myapp.service
INI
[Service]
LimitNOFILE=65536
저장 후 데몬 재로드와 재시작이 둘 다 필요합니다.
서버 터미널
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
체크리스트
로컬 터미널
cat /proc/$(pgrep -f myapp)/limits | grep "open files" # 실제 적용값
systemctl show myapp.service -p LimitNOFILE # 유닛 설정값
systemctl edit myapp.service # LimitNOFILE drop-in
systemctl daemon-reload && systemctl restart myapp # 반영
기억할 한 가지: 데몬의 한도는 ulimit이 아니라 /proc/<pid>/limits로 검증한다. 셸에서 본 숫자는 데몬과 무관할 수 있습니다.
ulimit·limits.conf·systemd 유닛이 어떻게 맞물리는지 직접 만져보는 실습은 리눅스 트랙에서 회원가입 없이 무료로 익힐 수 있습니다.