infra
Platform

모듈 맵

[Linux] sysctl 커널 파라미터 튜닝으로 대규모 트래픽 처리 성능 높이기

0 / 37 완료

펼치기
0 / 37 완료0%

Linux · 34 / 37

[Linux] sysctl 커널 파라미터 튜닝으로 대규모 트래픽 처리 성능 높이기

sysctl, ulimit, 파일 디스크립터 — 대규모 트래픽에서 서버가 버티게 만드는 OS 튜닝

🚨INCIDENT ALERT
HIGH

트래픽이 늘어난 뒤부터 nginx가 간헐적으로 Too many open files 에러를 내뱉습니다. 코드는 바뀐 게 없는데 서비스만 죽습니다. 커널 튜닝을 해본다고 sysctl -w net.core.somaxconn=65535를 설정했지만, 서버를 재부팅하자 원상복구됐습니다. 그리고 TIME_WAIT 소켓이 수만 개 쌓여 있다는 사실을 이제서야 발견했습니다. OS 기본값은 보수적으로 설정되어 있습니다. 실제 워크로드에 맞게 조정하지 않으면 코드를 아무리 최적화해도 OS 레벨에서 막힙니다.

커널 파라미터 & 성능 튜닝

Nginx가 Too many open files로 죽고, TIME_WAIT 소켓이 수만 개 쌓이고, 연결이 backlog에서 조용히 drop되는 현상 — 이 모든 것은 코드 버그가 아니라 OS 설정 문제입니다. 대규모 트래픽 환경에서 서버가 버티려면 Linux 커널이 기본으로 설정해 놓은 보수적인 값들을 실제 워크로드에 맞게 조정해야 합니다. 이 챕터를 마치면 커널 파라미터의 구조를 이해하고, sysctl과 ulimit을 자유자재로 다루며, 고트래픽 서버의 대표적인 OS 레벨 장애를 직접 해결할 수 있습니다.


1. 커널 파라미터란? /proc/sys/ 파일시스템 구조

이번 챕터에서 배울 것
  • 1/proc/sys/ 가상 파일시스템 구조와 sysctl 파라미터 계층 이해
  • 2sysctl 런타임 조회·변경 및 /etc/sysctl.conf 영구 적용
  • 3파일 디스크립터 개념과 ulimit으로 프로세스 리소스 한도 제어
  • 4/etc/security/limits.conf와 systemd LimitNOFILE로 영구 ulimit 설정
  • 5고트래픽 서버를 위한 TCP 백로그·TIME_WAIT·포트 범위 파라미터 튜닝
  • 6커널 모듈 관리 — lsmod, modprobe, modinfo 활용
실습 환경 준비
현재 커널 파라미터 전체 목록 조회
sysctl -a 2>/dev/null | wc -l && sysctl fs.file-max net.core.somaxconn vm.swappiness
현재 파일 디스크립터 한도 확인
ulimit -a | grep -E 'open files|max user processes' && cat /proc/sys/fs/file-nr
sysctl 설정 파일 위치 확인
ls /etc/sysctl.conf /etc/sysctl.d/ 2>/dev/null
실습 환경 참고

sysctl 값 변경과 limits.conf 수정은 root 권한이 필요합니다. 잘못된 커널 파라미터는 시스템 불안정을 유발할 수 있으므로 반드시 변경 전 기존 값을 기록해 두세요.

💡개념

리눅스 커널 파라미터와 /proc/sys/ 구조

리눅스 커널 파라미터 — /proc/sys/ 카테고리 구조와 sysctl 사용법

실서비스에서 net.core.somaxconn을 128에서 4096으로 올렸더니 갑자기 연결 drop이 사라졌습니다. 그런데 재부팅 후 다시 128로 돌아갔습니다. sysctl -w로 변경하면 런타임에만 적용되고, /etc/sysctl.conf에 써야 영구 적용됩니다. 커널 파라미터는 수백 개이지만, 그 이름들이 /proc/sys/ 디렉토리 구조를 그대로 따르기 때문에 계층 구조를 한 번만 파악해두면 낯선 파라미터 이름도 어느 영역인지 바로 알 수 있습니다.

Linux 커널은 수백 개의 동작 방식을 런타임에 조정할 수 있도록 가상 파일시스템 형태로 파라미터를 노출합니다. 이 파일시스템이 /proc/sys/입니다. sysctl net.core.somaxconn이라고 입력하면 커널이 /proc/sys/net/core/somaxconn 파일의 값을 읽어 반환합니다. 디렉토리 계층 구조를 먼저 파악하면 파라미터 이름이 낯설지 않게 됩니다.

/proc/sys/
├── kernel/         # 커널 전반 — PID 최대값, 호스트명, 패닉 동작 등
│   ├── pid_max
│   ├── hostname
│   └── panic
├── net/            # 네트워크 스택 전체
│   ├── core/       # 소켓 버퍼, 백로그 등 코어 설정
│   │   ├── somaxconn
│   │   ├── rmem_max
│   │   └── wmem_max
│   └── ipv4/       # IPv4 TCP/UDP 동작
│       ├── tcp_tw_reuse
│       ├── ip_local_port_range
│       └── tcp_fin_timeout
├── vm/             # 가상 메모리 관리
│   ├── swappiness
│   ├── dirty_ratio
│   └── overcommit_memory
└── fs/             # 파일시스템 — 파일 핸들 수 등
    ├── file-max
    └── inotify/

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part5/exam_9 && cd /tmp/linux/part5/exam_9

# 현재 커널 파라미터 스냅샷
sysctl -a 2>/dev/null | grep -E "^(net\.|vm\.|kernel\.)" | sort > current_params.txt
echo "파라미터 스냅샷 저장 완료: $(wc -l < current_params.txt)개"

# 적용할 튜닝 설정 파일 초안
cat > 99-custom-tuning.conf << 'EOF'
# /etc/sysctl.d/99-custom-tuning.conf
# 웹 서버 최적화 설정

# TCP 연결 최대 대기열
net.core.somaxconn = 65535

# TIME_WAIT 소켓 재사용 허용
net.ipv4.tcp_tw_reuse = 1

# 스왑 사용 최소화 (SSD 서버)
vm.swappiness = 10

# 파일 디스크립터 최대값
fs.file-max = 1000000
EOF

이제 실습을 진행합니다.

핵심 개념: /proc/sys/ 아래의 모든 파일은 실제 디스크에 존재하지 않습니다. 커널 메모리의 값을 파일처럼 읽고 쓸 수 있도록 가상화한 것입니다. 따라서 cat으로 읽고 echo로 쓸 수 있지만, 재부팅하면 원래대로 돌아갑니다.

로컬 터미널
# 현재 커널 파라미터 직접 읽기
cat /proc/sys/net/core/somaxconn
# 128

# 직접 쓰기 (즉시 적용, 재부팅 시 초기화됨)
echo 65535 > /proc/sys/net/core/somaxconn

/proc/sys/ 경로와 sysctl 키 이름의 관계:

/proc/sys/net/core/somaxconn → sysctl 키: net.core.somaxconn

경로 구분자 /가 점 .으로 바뀝니다. 이 점-표기법이 sysctl 명령에서 사용하는 키 이름입니다.


2. sysctl 명령어: 조회, 임시 변경, 영구 적용

sysctl 기본 사용법 — 조회부터 영구 적용까지

조회

로컬 터미널
# 특정 파라미터 조회
sysctl net.core.somaxconn
# net.core.somaxconn = 128

# 키워드로 관련 파라미터 전체 조회
sysctl -a | grep tcp_tw
# net.ipv4.tcp_tw_reuse = 0

# 전체 파라미터 목록 (출력이 매우 길어 grep과 함께 사용)
sysctl -a | grep net.ipv4 | head -30

# 파일에서 직접 읽기 (스크립트에서 유용)
cat /proc/sys/net/ipv4/tcp_tw_reuse

임시 변경 (재부팅 시 초기화)

로컬 터미널
# 단일 파라미터 변경 — 즉시 적용
sudo sysctl -w net.core.somaxconn=65535

# 여러 파라미터 한 번에 변경
sudo sysctl -w net.core.somaxconn=65535 net.ipv4.tcp_tw_reuse=1

# 변경 후 확인
sysctl net.core.somaxconn
# net.core.somaxconn = 65535

영구 적용 — /etc/sysctl.conf 또는 /etc/sysctl.d/

로컬 터미널
# 방법 1: /etc/sysctl.conf 직접 편집 (레거시 방식, 여전히 널리 사용)
sudo vi /etc/sysctl.conf
INI
# /etc/sysctl.conf 예시
# TCP 연결 backlog 확장
net.core.somaxconn = 65535

# TIME_WAIT 소켓 재사용 허용
net.ipv4.tcp_tw_reuse = 1

# 로컬 포트 범위 확장 (기본: 32768~60999)
net.ipv4.ip_local_port_range = 10000 65535

# 소켓 수신/송신 버퍼 최대값 (바이트, 256MB)
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456

# 스왑 사용 최소화
vm.swappiness = 10
로컬 터미널
# 방법 2: /etc/sysctl.d/ 드롭-인 방식 (권장 — 모듈화, 충돌 방지)
sudo vi /etc/sysctl.d/99-network-tuning.conf
INI
# /etc/sysctl.d/99-network-tuning.conf
# 숫자 접두사(99-)로 적용 순서 제어. 숫자가 클수록 나중에 적용됨.

net.core.somaxconn = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65535
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
로컬 터미널
# 설정 파일 즉시 적용 (재부팅 없이)
sudo sysctl -p                          # /etc/sysctl.conf 적용
sudo sysctl -p /etc/sysctl.d/99-network-tuning.conf  # 특정 파일 적용
sudo sysctl --system                    # /etc/sysctl.d/ 전체 적용 (권장)

적용 우선순위: /etc/sysctl.d/ 파일들은 파일명 알파벳 순으로 적용됩니다. 99-로 시작하는 파일은 거의 마지막에 적용되므로 다른 패키지 설정을 덮어쓸 수 있습니다.

🔍실행 후 확인할 것
  • sysctl fs.file-max 실행 후 변경한 값이 즉시 반영되어 있다
  • sysctl -p 실행 후 /etc/sysctl.conf 에 기록한 값이 '설정' 메시지와 함께 출력된다
  • cat /proc/sys/net/core/somaxconn 값이 변경 전 128(또는 시스템 기본값)에서 설정한 값으로 바뀌어 있다
  • 재부팅 후에도 sysctl -a | grep somaxconn 에서 설정한 값이 유지된다 (영구 설정 검증)

3. 파일 디스크립터(File Descriptor) 개념

💡개념

파일 디스크립터 — 모든 것은 파일이다

파일 디스크립터 — 프로세스 FD 테이블과 ulimit 제한 구조

Nginx 로그에 (24: Too many open files) 에러가 찍히기 시작하면, 서버 메모리나 CPU 문제가 아닙니다. Nginx 프로세스가 열 수 있는 파일 디스크립터 한도에 도달한 겁니다. ulimit -n으로 보면 1024이고, HTTP 연결 하나에 최소 소켓 2개가 필요하니 동시 연결 500개도 안 됩니다. 이 한도는 왜 존재하는지, 어디서 늘려야 하는지(ulimit vs limits.conf vs systemd LimitNOFILE)를 알려면 파일 디스크립터가 무엇인지부터 이해해야 합니다.

Linux의 핵심 철학 중 하나는 **"모든 것은 파일이다(Everything is a file)"**입니다. 일반 파일뿐 아니라 네트워크 소켓, 파이프, 디바이스, 타이머도 모두 파일 디스크립터(FD)를 통해 접근합니다.

파일 디스크립터가 중요한 이유:

프로세스가 열 수 있는 FD 수 = 동시에 처리할 수 있는 연결/파일 수

Nginx가 처리하는 HTTP 연결 1개 = FD 최소 2개 (클라이언트 소켓 + upstream 소켓)
따라서 동시 연결 10,000개 = FD 최소 20,000개 필요

프로세스별 FD 현황 확인:

로컬 터미널
# 특정 프로세스의 열린 FD 수
ls /proc/$(pgrep nginx | head -1)/fd | wc -l

# lsof로 프로세스별 열린 파일 목록
lsof -p $(pgrep nginx | head -1) | wc -l

# 시스템 전체 현재 열린 FD 수 / 최대값
cat /proc/sys/fs/file-nr
# 3456    0    97816
# [현재 열린 수] [해제됐지만 미반환] [최대값]

# 시스템 전체 FD 최대값
cat /proc/sys/fs/file-max
# 97816

기본 FD 3개 — 모든 프로세스에 자동 할당:

FD 번호이름기본 연결 대상
0stdin키보드 입력
1stdout터미널 출력
2stderr터미널 에러 출력

따라서 일반 프로세스는 3번 FD부터 새로 열린 파일이나 소켓을 할당받습니다.

FD 한도 초과 시 발생하는 에러:

nginx: [alert] socket() failed (24: Too many open files)
java.io.IOException: Too many open files
OSError: [Errno 24] Too many open files

에러 코드 24가 EMFILE이며, 이는 "프로세스당 FD 한도 초과"를 의미합니다. 에러 코드 23은 ENFILE로 "시스템 전체 FD 한도 초과"입니다.


4. ulimit: soft/hard limit 이해 및 설정

ulimit 완전 활용 — 프로세스 리소스 한도 제어

ulimit은 현재 쉘과 그 쉘에서 실행되는 모든 프로세스의 리소스 한도를 설정합니다.

soft limit vs hard limit

soft limit: 현재 적용 중인 실제 한도. 프로세스가 스스로 낮출 수도, hard limit까지 높일 수도 있음.
hard limit: soft limit의 상한선. root만 높일 수 있음. 일반 사용자는 낮추는 것만 가능.
로컬 터미널
# 현재 쉘의 모든 ulimit 조회
ulimit -a

# 출력 예시:
# open files                      (-n) 1024     ← 이게 문제의 원인!
# max user processes              (-u) 65535
# stack size              (kbytes, -s) 8192
# virtual memory          (kbytes, -v) unlimited

# soft/hard limit 분리 조회
ulimit -Sn   # soft limit (파일 수)
ulimit -Hn   # hard limit (파일 수)

주요 ulimit 옵션

로컬 터미널
# -n : 열 수 있는 파일(FD) 최대 수
ulimit -n 65536

# -u : 생성할 수 있는 프로세스(스레드) 최대 수
ulimit -u 65535

# -s : 스택 크기 (KB 단위)
ulimit -s 8192

# -m : 최대 메모리 크기 (KB, 많은 시스템에서 무효)
ulimit -m unlimited

# -c : 코어 덤프 파일 크기 (0 = 코어 덤프 비활성화)
ulimit -c unlimited   # 장애 분석을 위해 활성화

# soft/hard 모두 동시에 설정
ulimit -Sn 65536
ulimit -Hn 65536

현재 세션에서 임시 변경

로컬 터미널
# Nginx 실행 전 FD 한도 높이기 (현재 세션에만 적용)
ulimit -n 65536
nginx

# 확인
ulimit -n
# 65536

주의: ulimit 설정은 현재 쉘 세션에만 적용됩니다. 새 터미널을 열면 기본값으로 돌아갑니다.


5. /etc/security/limits.conf 영구 설정

/etc/security/limits.conf로 영구 ulimit 적용

ulimit을 영구적으로 적용하려면 /etc/security/limits.conf 또는 /etc/security/limits.d/ 드롭-인 파일을 수정해야 합니다. 이 파일은 PAM(Pluggable Authentication Modules)이 사용자 로그인 시 읽어 적용합니다.

파일 형식

<도메인>  <타입>  <항목>  <값>
도메인의미
*모든 사용자 (root 제외)
rootroot 사용자
@nginxnginx 그룹의 모든 사용자
www-data특정 사용자
타입의미
softsoft limit 설정
hardhard limit 설정
-soft/hard 동시 설정

실제 설정 예시

로컬 터미널
sudo vi /etc/security/limits.conf
INI
# /etc/security/limits.conf

# 모든 사용자: 파일 디스크립터 65536개 (soft/hard 동시)
*       soft    nofile      65536
*       hard    nofile      65536

# root 사용자: 더 높은 한도
root    soft    nofile      1048576
root    hard    nofile      1048576

# www-data (nginx/apache 사용자): 대용량 동시 연결 처리
www-data    soft    nofile      1048576
www-data    hard    nofile      1048576
www-data    soft    nproc       65535
www-data    hard    nproc       65535

# 모든 사용자: 프로세스 수 제한 (fork 폭탄 방지)
*       soft    nproc       65535
*       hard    nproc       65535

# 스택 크기 제한 (MB 단위, 기본 8MB)
*       soft    stack       8192
*       hard    stack       65536

드롭-인 방식 (권장)

로컬 터미널
# 애플리케이션별 파일로 분리하여 관리
sudo vi /etc/security/limits.d/99-nginx.conf
INI
# /etc/security/limits.d/99-nginx.conf
www-data    soft    nofile      1048576
www-data    hard    nofile      1048576
www-data    soft    nproc       65535
www-data    hard    nproc       65535

적용 확인

로컬 터미널
# 로그아웃 후 재로그인하거나 새 세션에서 확인
su - www-data -s /bin/bash -c 'ulimit -n'
# 1048576

# 특정 프로세스의 실제 한도 확인 (/proc 사용)
cat /proc/$(pgrep nginx | head -1)/limits
# Limit                     Soft Limit           Hard Limit
# Max open files            1048576              1048576

중요: /etc/security/limits.conf 변경은 로그아웃 후 재로그인 또는 서비스 재시작 후에 적용됩니다. 현재 실행 중인 프로세스에는 즉시 영향을 주지 않습니다.


6. 핵심 TCP 튜닝 파라미터

고트래픽 서버를 위한 TCP 파라미터 튜닝

net.core.somaxconn — 연결 backlog 크기

로컬 터미널
# 현재 값 확인
sysctl net.core.somaxconn
# net.core.somaxconn = 128   ← 기본값, 고트래픽에선 너무 작음

# 문제: 트래픽이 급증하면 accept 큐가 가득 차 연결이 drop됨
# 증상: 클라이언트에서 "Connection refused" 에러 발생

# 영구 적용
sudo bash -c 'cat >> /etc/sysctl.d/99-network-tuning.conf << EOF
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
EOF'
sudo sysctl --system

# Nginx에서도 별도로 backlog 지정 필요
# nginx.conf: listen 80 backlog=65535;

net.ipv4.tcp_tw_reuse — TIME_WAIT 소켓 재사용

로컬 터미널
sysctl net.ipv4.tcp_tw_reuse
# net.ipv4.tcp_tw_reuse = 0  ← 기본값

# TIME_WAIT 상태: TCP 연결 종료 후 약 60초간 유지되는 상태
# 문제: 로컬 포트가 TIME_WAIT 소켓에 묶여 새 연결에 사용 불가
# tcp_tw_reuse=1: 안전한 경우에만 TIME_WAIT 소켓을 새 연결에 재사용

echo "net.ipv4.tcp_tw_reuse = 1" | sudo tee -a /etc/sysctl.d/99-network-tuning.conf
sudo sysctl --system

# 주의: tcp_tw_recycle은 Linux 4.12부터 제거됨 — 사용 금지!
# NAT 환경(AWS, GCP 등)에서 tcp_tw_recycle 사용 시 심각한 연결 장애 발생

net.ipv4.ip_local_port_range — 로컬 포트 범위

로컬 터미널
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768	60999   ← 기본값: 약 28,000개의 로컬 포트만 사용 가능

# 문제: Nginx→upstream 연결 등 외부로 나가는 연결이 많을 때 포트 고갈
# 해결: 포트 범위 확장

echo "net.ipv4.ip_local_port_range = 10000 65535" | sudo tee -a /etc/sysctl.d/99-network-tuning.conf
sudo sysctl --system

# 변경 후: 약 55,000개의 로컬 포트 사용 가능

net.core.rmem_max / wmem_max — 소켓 버퍼 크기

로컬 터미널
sysctl net.core.rmem_max net.core.wmem_max
# net.core.rmem_max = 212992     ← 약 200KB (기본값)
# net.core.wmem_max = 212992

# 고대역폭 환경(10Gbps+)에서는 기본 버퍼가 병목이 됨
# 권장값: 256MB (Bandwidth-Delay Product에 맞게 조정)

cat >> /etc/sysctl.d/99-network-tuning.conf << 'EOF'
# 소켓 수신/송신 버퍼 최대값 (256MB)
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456

# TCP 자동 튜닝 버퍼 범위 (최소, 기본, 최대)
net.ipv4.tcp_rmem = 4096 87380 268435456
net.ipv4.tcp_wmem = 4096 65536 268435456
EOF
sudo sysctl --system

vm.swappiness — 스왑 사용 적극성 제어

로컬 터미널
sysctl vm.swappiness
# vm.swappiness = 60   ← 기본값: 메모리 60% 사용 시 스왑 시작

# 웹 서버/DB 서버: 스왑은 응답 지연을 유발함
# 0: 스왑을 절대 안 쓴다는 의미가 아님 (OOM killer 방지용으로 극소량 씀)
# 1: 스왑을 가능한 한 쓰지 않음 (권장)
# 10: 낮은 스왑 사용 (대부분 서버 환경 권장값)
# 100: 적극적으로 스왑 사용 (데스크톱 환경)

echo "vm.swappiness = 10" | sudo tee -a /etc/sysctl.d/99-network-tuning.conf
sudo sysctl --system

# 현재 스왑 사용량 확인
free -h
swapon --show

추가 유용한 파라미터

로컬 터미널
# TIME_WAIT 소켓 최대 개수 (기본: 8192, 고트래픽에서 증가 필요)
net.ipv4.tcp_max_tw_buckets = 1440000

# FIN_WAIT_2 타임아웃 단축 (기본: 60초)
net.ipv4.tcp_fin_timeout = 30

# keepalive 설정 (idle 7200초, 간격 75초, 9번 재시도)
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 9

7. 실무: Nginx 'Too many open files' 에러 해결 전 과정

증상

# /var/log/nginx/error.log
2026/03/26 14:23:07 [alert] 12345#12345: *1048576 socket() failed (24: Too many open files) while connecting to upstream
2026/03/26 14:23:07 [crit] 12345#12345: accept4() failed (24: Too many open files)

Nginx가 더 이상 새 연결을 받지 못하고, 이미 연결된 요청도 upstream 소켓을 열지 못해 502 에러 폭발.

1단계: 현재 상황 진단

로컬 터미널
# Nginx 프로세스의 실제 FD 한도 확인
cat /proc/$(pgrep -o nginx)/limits | grep "Max open files"
# Max open files            1024                 4096

# 현재 Nginx가 사용 중인 FD 수
ls /proc/$(pgrep -o nginx)/fd | wc -l
# 1019   ← 1024 한도에 거의 도달!

# 시스템 전체 FD 사용 현황
cat /proc/sys/fs/file-nr
# 89432   0   97816

# 어떤 파일/소켓이 많이 열렸는지 확인
lsof -p $(pgrep -o nginx) | awk '{print $5}' | sort | uniq -c | sort -rn | head
# 850 IPv4
# 120 REG
# 49  FIFO

2단계: 임시 응급 조치 (서비스 즉시 복구)

로컬 터미널
# 방법 1: Nginx에 직접 fd 한도를 올려서 재시작 (다운타임 발생)
ulimit -n 1048576
sudo systemctl restart nginx

# 방법 2: Nginx worker 프로세스에 직접 prlimit 적용 (재시작 없이)
# (Linux 3.2+, prlimit 명령 사용)
for pid in $(pgrep nginx); do
    sudo prlimit --nofile=1048576:1048576 --pid $pid
done

# 효과 즉시 확인
cat /proc/$(pgrep -o nginx)/limits | grep "Max open files"
# Max open files            1048576              1048576

3단계: 영구 해결

a) /etc/security/limits.d/nginx.conf 설정:

INI
www-data    soft    nofile      1048576
www-data    hard    nofile      1048576

b) systemd 서비스 설정 (권장 — 아래 섹션에서 상세 설명):

INI
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=1048576

c) Nginx 설정 파일에서 worker_rlimit_nofile 설정:

Nginx
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 1048576;    # ← 이 줄 추가

events {
    worker_connections 65536;    # worker_rlimit_nofile보다 작아야 함
    use epoll;
    multi_accept on;
}

d) 시스템 전체 FD 최대값 확장:

로컬 터미널
echo "fs.file-max = 2097152" | sudo tee -a /etc/sysctl.d/99-fs-tuning.conf
sudo sysctl --system

4단계: 검증

서버 터미널
# 재시작 후 실제 한도 확인
sudo systemctl restart nginx
cat /proc/$(pgrep -o nginx)/limits | grep "Max open files"
# Max open files            1048576              1048576

# 로그에 에러가 더 이상 나오지 않는지 모니터링
sudo tail -f /var/log/nginx/error.log

8. 실무: 고트래픽 서버 TIME_WAIT 폭증 문제 해결

증상

로컬 터미널
# ss 또는 netstat으로 확인 시 TIME_WAIT 소켓이 수만 개
ss -s
# TCP:   45231 (estab 2341, closed 41823, orphaned 0, timewait 41823)
#                                                           ↑ 폭증!

# 새 연결 시도 시 로그에 나타나는 에러
# connect: Cannot assign requested address (EADDRNOTAVAIL)
# 로컬 포트가 모두 TIME_WAIT 상태라 새 소켓을 못 만드는 상황

원인 분석

로컬 터미널
# TIME_WAIT 상태 소켓 상세 확인
ss -tan state time-wait | head -20
# CLOSE-WAIT  0   0   10.0.1.5:443   10.0.2.100:34521   timer:(timewait,58sec,0)

# 로컬 포트 고갈 여부 확인
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768	60999   ← 약 28,000개 가용

# 현재 사용 중인 포트 수 확인
ss -tan | grep TIME_WAIT | wc -l
# 27891   ← 로컬 포트 범위의 99% 사용 중!

해결 방법

로컬 터미널
# 1. 로컬 포트 범위 확장 (즉시 가용 포트 증가)
sudo sysctl -w net.ipv4.ip_local_port_range="10000 65535"

# 2. TIME_WAIT 소켓 재사용 허용
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# 3. TIME_WAIT 타임아웃 단축 (기본 FIN_WAIT_2 60초 → 30초)
sudo sysctl -w net.ipv4.tcp_fin_timeout=30

# 4. TIME_WAIT 버킷 최대값 증가
sudo sysctl -w net.ipv4.tcp_max_tw_buckets=1440000

영구 설정 파일:

INI
# /etc/sysctl.d/99-timewait-tuning.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_max_tw_buckets = 1440000
로컬 터미널
sudo sysctl --system

# 적용 후 모니터링
watch -n 2 'ss -s | grep timewait'

근본적 해결: keepalive 활용

가장 좋은 해결책은 TIME_WAIT 자체를 줄이는 것입니다. Nginx에서 upstream keepalive를 활성화하면 매 요청마다 TCP 연결을 새로 만들지 않아 TIME_WAIT 발생이 극적으로 줄어듭니다.

Nginx
upstream backend {
    server 127.0.0.1:8080;
    keepalive 300;      # upstream과의 keepalive 연결 최대 300개 유지
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";   # keepalive를 위해 Connection 헤더 제거
    }
}

9. systemd 서비스별 리소스 제한

systemd LimitNOFILE, LimitNPROC으로 서비스별 리소스 제한

/etc/security/limits.conf는 로그인 세션에 적용되지만, systemd 서비스는 로그인 없이 직접 실행되므로 systemd Unit 파일에서 별도로 리소스 한도를 설정해야 합니다.

systemd 서비스 단위 리소스 제한 항목

systemd 옵션대응 ulimit의미
LimitNOFILE-n파일 디스크립터 최대 수
LimitNPROC-u프로세스/스레드 최대 수
LimitSTACK-s스택 크기 (바이트)
LimitCORE-c코어 덤프 크기
LimitAS-v가상 메모리 크기
LimitMEMLOCK-l메모리 잠금 크기

방법 1: 드롭-인 override 파일 (권장)

원본 서비스 파일을 건드리지 않고 override만 추가하는 방식입니다. 패키지 업데이트 시 원본이 덮어써져도 override는 유지됩니다.

서버 터미널
# systemctl edit 명령으로 override 파일 자동 생성
sudo systemctl edit nginx

에디터가 열리면 다음 내용을 입력합니다:

INI
# 이 파일은 /etc/systemd/system/nginx.service.d/override.conf 로 저장됨
[Service]
LimitNOFILE=1048576
LimitNPROC=65535
서버 터미널
# 저장 후 적용
sudo systemctl daemon-reload
sudo systemctl restart nginx

# 확인
systemctl show nginx | grep -E "LimitNOFILE|LimitNPROC"
# LimitNOFILE=1048576
# LimitNPROC=65535

# 실제 프로세스에 적용됐는지 /proc으로 최종 확인
cat /proc/$(pgrep -o nginx)/limits

방법 2: 서비스 파일 직접 수정

로컬 터미널
# 편집
sudo vi /lib/systemd/system/nginx.service
# 또는 커스텀 위치에 복사 후 편집
sudo cp /lib/systemd/system/nginx.service /etc/systemd/system/nginx.service
sudo vi /etc/systemd/system/nginx.service
INI
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=mixed
PrivateTmp=true

# 리소스 한도 추가
LimitNOFILE=1048576       # FD 최대 1M
LimitNPROC=65535          # 프로세스 65535개
LimitCORE=infinity        # 코어 덤프 제한 없음 (장애 분석용)

[Install]
WantedBy=multi-user.target
서버 터미널
sudo systemctl daemon-reload
sudo systemctl restart nginx

Java 애플리케이션 예시 (스레드가 많은 경우)

INI
# /etc/systemd/system/myapp.service.d/override.conf
[Service]
LimitNOFILE=1048576
LimitNPROC=131072    # Java는 스레드당 1개의 프로세스로 계산됨
LimitSTACK=67108864  # 64MB 스택

10. 커널 모듈: lsmod, modprobe, modinfo

커널 모듈 관리 — lsmod, modprobe, modinfo

Linux 커널은 모듈(Module) 방식으로 기능을 동적으로 추가/제거할 수 있습니다. 드라이버, 파일시스템, 네트워크 프로토콜 등이 모듈로 제공됩니다.

현재 로드된 모듈 확인: lsmod

로컬 터미널
lsmod
# Module                  Size  Used by
# nf_conntrack          172032  4 nf_nat,nf_conntrack_netlink,xt_conntrack,nf_conntrack_ipv4
# br_netfilter           24576  0
# overlay               143360  1
# ip_tables              32768  1 iptables
# ...

# 컬럼 의미:
# Module   : 모듈 이름
# Size     : 메모리 사용량 (바이트)
# Used by  : 이 모듈을 의존하는 다른 모듈 수와 이름

# 특정 모듈 확인
lsmod | grep overlay
# overlay               143360  1

모듈 정보 조회: modinfo

로컬 터미널
# 모듈의 상세 정보 조회
modinfo overlay
# filename:       /lib/modules/5.14.0/kernel/fs/overlayfs/overlay.ko.xz
# description:    Overlay filesystem
# author:         Miklos Szeredi <miklos@szeredi.hu>
# license:        GPL
# depends:
# parm:           redirect_dir:Enable directory redirects (bool)

# 파라미터 목록만 추출
modinfo -p br_netfilter
# nf_call_iptables:If != 0, call ip[6]tables for bridged frames (uint)
# nf_call_arptables:If != 0, call arptables for bridged frames (uint)

모듈 로드/언로드: modprobe

로컬 터미널
# 모듈 로드 (의존 모듈 자동 로드)
sudo modprobe br_netfilter

# 모듈 언로드 (의존 모듈 자동 제거)
sudo modprobe -r br_netfilter

# 파라미터와 함께 로드
sudo modprobe tcp_bbr    # BBR TCP 혼잡 제어 알고리즘 로드

# 로드된 모듈의 의존성 트리 확인
modprobe --show-depends nf_conntrack

부팅 시 자동 로드: /etc/modules-load.d/

로컬 터미널
# 부팅 시 특정 모듈 자동 로드 설정
sudo vi /etc/modules-load.d/custom.conf
INI
# /etc/modules-load.d/custom.conf
# 각 줄에 모듈 이름 하나씩

# Kubernetes/Docker용 네트워크 모듈
br_netfilter
overlay

# BBR TCP 혼잡 제어 (고트래픽 서버 성능 향상)
tcp_bbr

# 네트워크 필터
ip_conntrack
로컬 터미널
# 파일 적용 확인 (재부팅 없이 수동으로 로드)
sudo systemd-modules-load

# 또는 직접 확인
sudo modprobe br_netfilter
lsmod | grep br_netfilter

모듈 블랙리스트 (특정 모듈 로드 방지)

로컬 터미널
# 드라이버 충돌이나 보안 문제로 특정 모듈 블랙리스트 처리
sudo vi /etc/modprobe.d/blacklist-nouveau.conf
INI
# /etc/modprobe.d/blacklist-nouveau.conf
# NVIDIA 오픈소스 드라이버 비활성화 (독점 드라이버 설치 시)
blacklist nouveau
options nouveau modeset=0
로컬 터미널
# initramfs 재생성 후 재부팅 (블랙리스트 완전 적용을 위해)
sudo update-initramfs -u   # Debian/Ubuntu
sudo dracut -f             # RHEL/CentOS/Rocky

11. 종합 실무 시나리오

상황

월간 트래픽 1억 건, 피크 타임 초당 10,000 요청을 처리하는 API 서버를 새로 배포하는 시나리오.

진단 먼저

로컬 터미널
# 현재 기본값 스냅샷 저장 (변경 전 백업)
sysctl -a > /tmp/sysctl-before-tuning.txt

# 현재 네트워크 상태 확인
ss -s
cat /proc/sys/net/core/somaxconn
cat /proc/sys/net/ipv4/ip_local_port_range
ulimit -n

# 시스템 리소스 전체 현황
nproc
free -h
cat /proc/sys/fs/file-max

튜닝 파일 작성

로컬 터미널
sudo tee /etc/sysctl.d/99-api-server-tuning.conf << 'EOF'
# ===== 네트워크 연결 관리 =====
# TCP 연결 backlog 확장 (기본 128 → 65535)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# TIME_WAIT 소켓 재사용 허용
net.ipv4.tcp_tw_reuse = 1

# 로컬 포트 범위 확장 (기본 32768-60999 → 10000-65535)
net.ipv4.ip_local_port_range = 10000 65535

# TIME_WAIT 버킷 증가 (기본 8192 → 1440000)
net.ipv4.tcp_max_tw_buckets = 1440000

# FIN_WAIT_2 타임아웃 단축 (기본 60초 → 30초)
net.ipv4.tcp_fin_timeout = 30

# ===== 소켓 버퍼 =====
# 수신/송신 버퍼 최대값 (256MB)
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456

# TCP 자동 버퍼 튜닝 (최소/기본/최대)
net.ipv4.tcp_rmem = 4096 87380 268435456
net.ipv4.tcp_wmem = 4096 65536 268435456

# ===== keepalive =====
# 유휴 연결 keepalive 주기 (기본 7200초 → 300초)
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 9

# ===== 파일시스템 =====
# 시스템 전체 FD 최대값
fs.file-max = 2097152

# ===== 가상 메모리 =====
# 스왑 최소화 (응답 지연 방지)
vm.swappiness = 10
EOF

sudo sysctl --system

FD 한도 영구 설정

로컬 터미널
sudo tee /etc/security/limits.d/99-api-server.conf << 'EOF'
# API 서버 실행 사용자 (예: apiuser)
apiuser    soft    nofile      1048576
apiuser    hard    nofile      1048576
apiuser    soft    nproc       65535
apiuser    hard    nproc       65535

# root 포함 모든 사용자
*          soft    nofile      65536
*          hard    nofile      65536
EOF

systemd 서비스 설정

로컬 터미널
sudo mkdir -p /etc/systemd/system/api-server.service.d
sudo tee /etc/systemd/system/api-server.service.d/resource-limits.conf << 'EOF'
[Service]
LimitNOFILE=1048576
LimitNPROC=65535
LimitCORE=infinity
EOF

sudo systemctl daemon-reload
sudo systemctl restart api-server

검증

로컬 터미널
# 적용 결과 확인
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse net.ipv4.ip_local_port_range
cat /proc/$(pgrep api-server)/limits

# 부하 테스트 중 실시간 모니터링
watch -n 1 'ss -s; echo "---"; cat /proc/sys/fs/file-nr'

12. 실무 관점: OS 튜닝이 실제로 돈이 되는 이유

💼
실무 맥락
현업 패턴

장애 대응 속도의 차이

OS 레벨 튜닝 지식이 없는 엔지니어는 Too many open files 에러가 나면 "애플리케이션 버그인가?" 또는 "서버를 더 늘려야 하나?"를 먼저 생각합니다. OS 튜닝을 아는 엔지니어는 30초 안에 원인을 찾고 5분 안에 서비스를 복구합니다.

# 진단 루틴 (1분 안에 끝낼 수 있어야 함)
cat /proc/$(pgrep 서비스명 | head -1)/limits    # FD 한도 확인
ss -s                                            # 소켓 상태 한눈에
sysctl net.core.somaxconn                        # backlog 확인
cat /proc/sys/fs/file-nr                         # 시스템 FD 현황

인프라 비용 절감

AWS EC2 m5.xlarge (4코어/16GB) 기준:

튜닝 전튜닝 후절감
동시 연결 2,000개 처리 (FD 한도 막힘)동시 연결 50,000개 처리서버 25대 → 1대
TIME_WAIT 포트 고갈로 초당 200req포트 확장 후 초당 5,000req서버 25대 → 1대

실제로 OS 기본값 그대로 쓰는 것과 잘 튜닝된 서버의 처리량은 수십 배 차이가 날 수 있습니다.

면접에서 자주 나오는 질문

Q: Nginx에서 동시 접속자 수를 높이려면 어떻게 하나요?
A: (단순) worker_connections만 늘리면 됩니다.
A: (심화) worker_connections, worker_rlimit_nofile, net.core.somaxconn,
         /etc/security/limits.conf, LimitNOFILE 를 함께 조정해야 합니다.
         각 레이어가 독립적으로 병목이 될 수 있기 때문입니다.

Q: TIME_WAIT가 많으면 왜 문제가 되나요?
A: 로컬 포트를 점유하고 있어 새 연결 시 포트 고갈이 발생할 수 있습니다.
   해결 방법은 포트 범위 확장, tcp_tw_reuse 활성화, keepalive 설정입니다.

자동화: 서버 프로비저닝 스크립트에 OS 튜닝 포함

로컬 터미널
#!/bin/bash
# server-tuning.sh — 신규 서버 배포 시 자동 실행

set -euo pipefail

echo "=== OS 튜닝 적용 시작 ==="

# sysctl 설정
cat > /etc/sysctl.d/99-production-tuning.conf << 'EOF'
net.core.somaxconn = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_fin_timeout = 30
net.core.rmem_max = 268435456
net.core.wmem_max = 268435456
fs.file-max = 2097152
vm.swappiness = 10
EOF

sysctl --system

# ulimit 설정
cat > /etc/security/limits.d/99-production.conf << 'EOF'
*    soft    nofile    1048576
*    hard    nofile    1048576
*    soft    nproc     65535
*    hard    nproc     65535
EOF

echo "=== OS 튜닝 완료 ==="
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse

이런 스크립트를 Ansible playbook, Terraform user_data, 또는 AWS Systems Manager에 통합하면 모든 서버가 동일한 OS 설정으로 프로비저닝됩니다.


13. 커널 모듈과 BBR TCP 혼잡 제어 — 보너스 튜닝

💼
실무 맥락
현업 패턴

Google이 개발한 BBR(Bottleneck Bandwidth and Round-trip propagation time) 알고리즘은 기존 CUBIC 대비 고지연/고손실 네트워크에서 처리량을 극적으로 향상시킵니다. Linux 4.9+에 포함되어 있으며, 커널 모듈 로드와 sysctl 설정만으로 활성화할 수 있습니다.

로컬 터미널
# 현재 혼잡 제어 알고리즘 확인
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = cubic  ← 기본값

# 사용 가능한 알고리즘 목록
sysctl net.ipv4.tcp_available_congestion_control
# net.ipv4.tcp_available_congestion_control = reno cubic

# BBR 모듈 로드
sudo modprobe tcp_bbr

# 로드됐는지 확인
lsmod | grep bbr
# tcp_bbr                20480  0

# BBR 활성화
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
sudo sysctl -w net.core.default_qdisc=fq   # BBR과 함께 권장되는 큐 규칙

# 영구 적용
cat >> /etc/sysctl.d/99-bbr.conf << 'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF

# 모듈 부팅 시 자동 로드
echo "tcp_bbr" | sudo tee /etc/modules-load.d/bbr.conf

sudo sysctl --system

# 최종 확인
sysctl net.ipv4.tcp_congestion_control
# net.ipv4.tcp_congestion_control = bbr

BBR이 특히 효과적인 환경:

  • 클라이언트와 서버 간 RTT가 100ms 이상인 경우
  • 전송 중 패킷 손실이 있는 환경 (모바일, 위성)
  • CDN 원본 서버와 엣지 서버 간 통신

정리: 커널 파라미터 튜닝 핵심 요약

목적설정 항목권장값
연결 backlog 확장net.core.somaxconn65535
TIME_WAIT 재사용net.ipv4.tcp_tw_reuse1
로컬 포트 확장net.ipv4.ip_local_port_range10000 65535
소켓 버퍼 확장net.core.rmem_max / wmem_max268435456
스왑 최소화vm.swappiness10
FD 한도 (시스템)fs.file-max2097152
FD 한도 (프로세스)ulimit -n / LimitNOFILE1048576

설정 우선순위 (낮은 번호가 우선 적용):

1. 커널 기본값 (부팅 시)
2. /etc/sysctl.d/*.conf (알파벳 순)
3. /etc/sysctl.conf
4. /etc/security/limits.conf (PAM 로그인 시)
5. /etc/security/limits.d/*.conf
6. systemd Unit의 Limit* (서비스 시작 시)
7. 애플리케이션 자체 설정 (nginx worker_rlimit_nofile 등)

각 레이어는 독립적으로 병목이 될 수 있습니다. 한 곳만 올려도 다른 레이어가 막혀 있으면 효과가 없습니다. 모든 레이어를 일관성 있게 설정하는 것이 OS 튜닝의 핵심입니다.

다음 모듈에서는 Prometheus와 Node Exporter를 연동해 커널 튜닝 전후의 성능 지표를 시계열로 수집하고 Grafana 대시보드로 시각화하는 방법을 다룹니다.

지식 확인

퀴즈 — 5문제

Q1

sysctl 설정을 재부팅 후에도 영구적으로 유지하려면 어느 파일에 작성해야 합니까?

Q2

현재 실행 중인 시스템에서 /etc/sysctl.conf의 설정값을 즉시 적용하는 명령은 무엇입니까?

Q3

IPv4 패킷 포워딩을 활성화하려면 어떤 커널 파라미터를 1로 설정해야 합니까?

Q4

vm.overcommit_memory=2 로 설정하면 어떤 동작이 일어납니까?

Q5

시스템 전체 최대 파일 디스크립터 수를 조회하는 커널 파라미터는 무엇입니까?

0 / 5 답변

🧪 실습으로 확인하기

새 서버 인수인계 — 처음 30분

초급

낯선 Linux 서버를 인수받았을 때 OS, 서비스, 로그를 빠르게 파악하는 루틴을 직접 수행한다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

linux고급 · 80
[Linux] Ansible과 스크립트로 여러 대의 리눅스 서버 한 번에 구축하기
Linux 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점