
서비스 · 프로세스 관리 17 / 50 · Part 2. 시그널·자원·세션
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
트래픽이 몰린 날 웹 서버 로그에 Too many open files가 쏟아지고, 누군가 서버에서 fork bomb을 실행해 SSH 접속조차 안 되는 상황. 두 장애 모두 프로세스 자원 한도(resource limit) 와 관련이 있다. 한도가 너무 낮으면 정상 서비스가 멈추고, 너무 높거나 없으면 한 사용자가 서버 전체를 마비시킬 수 있다.
이번 글에서는 ulimit으로 한도를 확인·변경하고, 한도를 넘겼을 때의 오류를 재현한다. 그리고 실무에서 가장 많이 헷갈리는 부분, limits.conf는 로그인 세션에만 적용되고 systemd 서비스에는 적용되지 않는다 는 점을 실측으로 확인한다.
| ulimit 옵션 | limits.conf 항목 | systemd | 의미 |
|---|---|---|---|
-n | nofile | LimitNOFILE= | 열 수 있는 파일(fd) 수 |
-u | nproc | LimitNPROC= | 사용자(UID)당 프로세스·스레드 수 |
-c | core | LimitCORE= | 코어 덤프 파일 크기 |
-s | stack | LimitSTACK= | 스택 크기 |
-t | cpu | LimitCPU= | CPU 시간(초) |
-v | as | LimitAS= | 가상 메모리 크기 |
| — | — | TasksMax= | 서비스 cgroup의 최대 태스크 수 (18편) |
| 구분 | 의미 | 일반 사용자 |
|---|---|---|
| soft | 실제로 적용되는 값 | hard 이하에서 자유롭게 변경 |
| hard | soft의 상한 | 낮추기만 가능, 올리기는 root만 |
ulimit -n은 soft, ulimit -Hn은 hard를 본다. 실행 중인 프로세스의 한도는 /proc/PID/limits 또는 prlimit --pid PID로 확인하고, prlimit은 root가 실행 중인 프로세스의 한도를 바꾸는 데도 쓴다.
| 경로 | 적용 주체 | 설정 파일 |
|---|---|---|
| SSH·콘솔·su 로그인 | PAM pam_limits.so | /etc/security/limits.conf, limits.d/*.conf |
| systemd 서비스 | systemd | unit 파일의 Limit*= 또는 system.conf의 DefaultLimit*= |
| 셸에서 실행한 명령 | 부모 셸에서 상속 | ulimit 명령 |

open() 호출
→ 커널: 현재 열린 fd 수 >= RLIMIT_NOFILE(soft)?
예 → 실패, errno = EMFILE ("Too many open files")
fork() 호출
→ 커널: 해당 UID 의 프로세스·스레드 수 >= RLIMIT_NPROC(soft)?
예 → 실패, errno = EAGAIN ("Resource temporarily unavailable")
한도는 fork 시 자식에게 복사 된다. 그래서 로그인 셸의 한도는 그 셸에서 실행한 모든 명령에 적용되고, 서비스 메인 프로세스의 한도는 그 서비스의 모든 자식에게 적용된다.
# 1) 현재 한도
ulimit -a | grep -E 'open files|max user processes|core file|stack|cpu time'
ulimit -Sn; ulimit -Hn
grep -E 'Max (open files|processes|core)' /proc/$$/limits
# 2) nofile 초과 재현 (서브셸에서 soft 를 20 으로 낮춤)
bash -c 'ulimit -n 20; python3 -c "
fs=[]
try:
while True: fs.append(open(\"/etc/hostname\"))
except OSError as e: print(len(fs), \"개 열고 실패:\", e)"'
# 3) 일반 사용자는 한 번 낮춘 한도를 다시 올릴 수 없다
bash -c 'ulimit -n 100; ulimit -n 5000'
# 4) nproc 초과 재현
ps -u analyst --no-headers | wc -l
bash -c 'ulimit -u 12; python3 -c "... fork 반복 ..."'
# 5) (root) limits.conf 는 로그인에, 서비스는 별도
echo 'analyst soft nofile 4096' > /etc/security/limits.d/90-analyst.conf
ssh analyst@localhost 'ulimit -Sn' # 새 로그인 세션
prlimit --pid $(pgrep -xo sshd) --nofile # 서비스 프로세스
systemctl show sshd crond -p Id,LimitNOFILE,LimitNOFILESoft,TasksMax



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ ulimit -a | grep -E 'open files|max user processes|core file|stack|cpu time'
core file size (blocks, -c) unlimited
open files (-n) 1024
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 32058
[analyst@rocky9-lab ~]$ ulimit -Sn; ulimit -Hn
1024
20000
[analyst@rocky9-lab ~]$ grep -E 'Max (open files|processes|core)' /proc/$$/limits
Max core file size unlimited unlimited bytes
Max processes 32058 32058 processes
Max open files 1024 20000 files
[analyst@rocky9-lab ~]$ bash -c 'ulimit -n 20; python3 -c "
> fs=[]
> try:
> while True: fs.append(open(\"/etc/hostname\"))
> except OSError as e: print(len(fs), \"개 열고 실패:\", e)"'
17 개 열고 실패: [Errno 24] Too many open files: '/etc/hostname'
[analyst@rocky9-lab ~]$ bash -c 'ulimit -n 100; ulimit -n 5000' ; echo "종료 코드=$?"
bash: line 1: ulimit: open files: cannot modify limit: Operation not permitted
종료 코드=1
[analyst@rocky9-lab ~]$ ps -u analyst --no-headers | wc -l
6
[analyst@rocky9-lab ~]$ bash -c 'ulimit -u 12; python3 -c "import os,time
> n=0
> try:
> while True:
> if os.fork()==0: time.sleep(3); os._exit(0)
> n+=1
> except OSError as e: print(n, \"개 fork 후 실패:\", e)"'
7 개 fork 후 실패: [Errno 11] Resource temporarily unavailable
[analyst@rocky9-lab ~]$ pkill -u analyst -x sleep; true
[root@rocky9-lab ~]# echo 'analyst soft nofile 4096' > /etc/security/limits.d/90-analyst.conf; cat /etc/security/limits.d/90-analyst.conf
analyst soft nofile 4096
[root@rocky9-lab ~]# ssh -i /root/.ssh/lab_key -o LogLevel=ERROR analyst@localhost 'ulimit -Sn'
4096
[root@rocky9-lab ~]# prlimit --pid $(pgrep -xo sshd) --nofile
RESOURCE DESCRIPTION SOFT HARD UNITS
NOFILE max number of open files 1024 20000 files
[root@rocky9-lab ~]# systemctl show sshd crond -p Id,LimitNOFILE,LimitNOFILESoft,TasksMax
TasksMax=51293
LimitNOFILE=524288
LimitNOFILESoft=1024
Id=sshd.service
TasksMax=51293
LimitNOFILE=524288
LimitNOFILESoft=1024
Id=crond.service
[root@rocky9-lab ~]# rm -f /etc/security/limits.d/90-analyst.conf
| 관찰 | 의미 |
|---|---|
open files 1024, hard 20000 | 일반적인 soft 기본값 1024. 필요하면 사용자가 hard까지 올릴 수 있다 |
/proc/$$/limits의 Max open files 1024 20000 | ulimit과 같은 값을 커널에서 직접 읽었다 |
17 개 열고 실패: [Errno 24] Too many open files | 한도 20에서 이미 열려 있던 fd(0·1·2 등 3개)를 빼고 17개만 더 열 수 있었다. errno 24 = EMFILE |
ulimit -n 5000 → cannot modify limit: Operation not permitted | 100으로 낮추면서 hard도 100으로 내려갔고, 일반 사용자는 hard를 다시 올릴 수 없다 (bash의 ulimit -n은 soft·hard를 함께 바꾼다) |
| analyst 프로세스 6개 + fork 7개에서 실패 | nproc은 해당 UID의 전체 프로세스 수 로 계산한다. 이미 있던 6개 + python 자신 + 자식 7개가 한도 12 근처에 도달했다 |
[Errno 11] Resource temporarily unavailable | errno 11 = EAGAIN. fork bomb이 이 한도에서 멈춘다 |
limits.d 추가 후 새 SSH 세션 ulimit -Sn → 4096 | PAM이 로그인 시 limits.conf를 적용했다 |
sshd 서비스 prlimit → 1024 / 20000 | 같은 서버에서도 서비스 프로세스에는 limits.conf가 적용되지 않았다 |
systemctl show → LimitNOFILE=524288, LimitNOFILESoft=1024 | systemd가 서비스에 설정한 값. 서비스 한도는 unit 파일로 관리한다 |
실습 컨테이너는 Docker가 컨테이너 전체의 hard 한도를 20000으로 제한하고 있어, systemd 설정(hard 524288)보다 작은 값이 실제로 적용되었다. 일반 서버에서는
prlimit결과가systemctl show의 값과 일치한다. 설정값과 실제 적용값이 다를 수 있으므로 반드시/proc/PID/limits로 확인 한다.
| 주제 | 내용 |
|---|---|
| fork bomb 방어 | nproc(로그인)과 TasksMax=(서비스·사용자 slice)로 한 사용자가 PID를 고갈시키는 것을 막는다. 공격을 받아도 관리자 SSH 접속 여지가 남는다 |
| 코어 덤프 | core unlimited이면 크래시 시 프로세스 메모리 전체 가 파일로 저장된다. 비밀번호·키가 포함될 수 있어 운영 서버는 core 0 또는 systemd-coredump의 접근 통제를 쓴다 |
| 서비스 가용성 | 웹·DB 서비스의 nofile이 낮으면 동시 접속 증가만으로 서비스 거부가 된다. DDoS 대응 점검 항목이다 |
| 권한 경계 | 일반 사용자는 hard를 올릴 수 없다. 서비스 계정이 탈취되어도 한도 밖으로 자원을 쓰지 못한다 |
[Detection] 웹 로그 급증: "accept4() failed (24: Too many open files)"
↓
[확인] P=$(pgrep -xo nginx); grep 'open files' /proc/$P/limits
ls /proc/$P/fd | wc -l ← 현재 사용량
ss -tn state established '( sport = :443 )' | wc -l
↓
[판단] 정상 트래픽 증가인가? 특정 IP 대량 연결(Slowloris 등)인가?
↓
[Response] 공격이면 출발지 차단·연결 제한 / 정상이면 LimitNOFILE 상향 (drop-in, 28편)
| 점검 명령 | 목적 |
|---|---|
grep -r . /etc/security/limits.conf /etc/security/limits.d/ | 로그인 한도 정책 |
systemctl show SERVICE -p LimitNOFILE,LimitNPROC,TasksMax | 서비스 설정값 |
cat /proc/PID/limits | 실제 적용값 |
journalctl -g 'fork: retry\|Too many open files' | 한도 초과 흔적 |
| 실수 | 결과 | 예방 |
|---|---|---|
| 서비스 한도를 limits.conf에 설정 | 적용되지 않음 (실측) | unit 파일 LimitNOFILE= (drop-in) |
| limits.conf 수정 후 기존 세션에서 확인 | 값이 안 바뀜 | 새 로그인 에서 적용된다 |
ulimit -n을 무심코 낮춤 | 같은 셸에서 되돌릴 수 없음 | ulimit -Sn으로 soft만 변경 |
| 설정값만 보고 적용됐다고 판단 | 상위 한도(컨테이너 등)에 막힐 수 있음 | /proc/PID/limits 확인 |
| nproc을 프로세스 수로만 생각 | 스레드도 포함된다 | 멀티스레드 서비스 계정은 여유 있게 설정 |
[ ] ulimit -a, -Sn, -Hn 과 /proc/PID/limits 를 비교했다
[ ] nofile 을 낮춰 EMFILE(Too many open files)을 재현했다
[ ] 일반 사용자가 hard 를 올릴 수 없는 것을 확인했다
[ ] nproc 을 낮춰 fork 실패(EAGAIN)를 재현했다
[ ] limits.d 설정이 새 SSH 로그인에만 적용되는 것을 확인했다
[ ] 서비스 한도를 systemctl show 와 prlimit 로 확인했다
Limit*=로 설정한다./proc/PID/limits로 확인한다.다음 글 「18. cgroup으로 자원 제어하기」 에서는 프로세스 하나가 아니라 서비스 전체 에 CPU·메모리·프로세스 수 제한을 거는 cgroup을 다룬다. systemd-run으로 CPU 20%, 프로세스 5개로 제한한 임시 서비스를 만들어 실제 제한이 동작하는 것을 확인한다.