Linuxサーバー

Linux サーバーの高負荷チューニング:ulimit・sysctl の TCP 最適化と「nf_conntrack: table full」対策

6 ステップ 13 分で読めます 12 回閲覧 0
目次

Web サイトや API へのアクセスが増えたとき、最初に出るエラーは CPU やメモリとは無関係なことがよくあります。Too many open files、nf_conntrack: table full, dropping packet、SYN 状態のまま止まる接続、あふれる listen キューなどです。これらはカーネルやプロセスの制限によるもので、その既定値は小規模なマシン向けであり、数万の接続を処理するサーバーには合っていません。この記事では、どの制限に達しているかを特定する方法と、ファイルディスクリプタの上限(ulimit)の引き上げ、sysctl による TCP スタックの調整、コネクショントラッキングテーブルのサイズ設定、それに合わせた Nginx の設定を解説します。

変更は 1 つずつ行い、その都度効果を測定してください。これらの設定が役立つのは実際に制限に達している場合だけで、遅いアプリケーションが速くなるわけではありません。KVM VPS や専用サーバーではすべて変更できますが、コンテナ(LXC/OpenVZ)内では多くのカーネルパラメーターが読み取り専用です。

ステップ1:どの制限に達しているかを特定

# Kernel messages about dropped packets or full tables
dmesg -T | grep -iE 'conntrack|too many|possible SYN flooding' | tail

# Socket summary and listen queues (Recv-Q close to Send-Q = backlog is full)
ss -s
ss -lnt

# Listen queue overflows and dropped SYNs since boot
nstat -az | grep -iE 'ListenOverflows|ListenDrops|TCPReqQFull'

# Connection tracking usage vs maximum
cat /proc/sys/net/netfilter/nf_conntrack_count /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null

# Open file limit of the shell and of a running service (Nginx worker example)
ulimit -n
grep 'Max open files' /proc/$(pgrep -f 'nginx: worker' | head -1)/limits

ステップ2:ファイルディスクリプタの上限を引き上げる

接続、ログファイル、ソケットはそれぞれファイルディスクリプタを 1 つずつ使います。一般的な既定値であるプロセスあたり 1024 は、高負荷のプロキシやデータベースには少なすぎます。システム全体の最大値、ログインセッション向けのユーザーごとの制限、そして最も重要な systemd サービス自体の制限を引き上げてください。サービスは /etc/security/limits.conf の設定を参照しないためです。

# System-wide maximum
cat > /etc/sysctl.d/90-files.conf <<'EOF2'
fs.file-max = 2097152
fs.nr_open = 2097152
EOF2

# Login sessions (takes effect at the next login)
cat > /etc/security/limits.d/90-nofile.conf <<'EOF2'
*    soft nofile 1048576
*    hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576
EOF2

# A specific service, e.g. Nginx (repeat for mysqld, php-fpm, your app)
mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf <<'EOF2'
[Service]
LimitNOFILE=1048576
EOF2
systemctl daemon-reload && systemctl restart nginx

ステップ3:sysctl で TCP スタックを調整

以下の値は、高負荷の Web サーバー、プロキシ、API サーバーの出発点として妥当な設定です。変更内容を確認したり元に戻したりしやすいよう、専用のファイルにまとめておきます。

cat > /etc/sysctl.d/91-network-tuning.conf <<'EOF2'
# Longer accept and SYN queues for bursts of new connections
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 16384

# More local ports for outgoing connections (proxies, crawlers, API clients)
net.ipv4.ip_local_port_range = 1024 65000
# Reuse TIME_WAIT sockets for new outgoing connections (safe; tcp_tw_recycle no longer exists)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# Keepalive: detect dead peers after about 10 minutes instead of 2 hours
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# Keep SYN cookies on as a basic protection against SYN floods
net.ipv4.tcp_syncookies = 1

# Bigger socket buffers for fast long-distance transfers
net.core.rmem_max = 33554432
net.core.wmem_max = 33554432
net.ipv4.tcp_rmem = 4096 87380 33554432
net.ipv4.tcp_wmem = 4096 65536 33554432

# BBR congestion control (kernel 4.9+)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF2

sysctl --system
sysctl net.core.somaxconn net.ipv4.tcp_congestion_control

BBR の詳細については Linux で BBR を有効にする方法を参照してください。なお、アプリケーションは listen() に独自の backlog 値を渡します。たとえば Nginx で大きなキューを使うには listen 80 backlog=65535; の指定が必要です。

ステップ4:コネクショントラッキングテーブルのサイズを設定

firewalld、ufw、iptables の NAT、Docker を使っている場合、カーネルはすべての接続を conntrack テーブルで追跡します。このテーブルが満杯になると新しいパケットが破棄され、サイトに「ランダムに」アクセスできない状態になります。既定の最大値はメモリ容量によって決まり、65536 や 262144 しかないことも珍しくありません。

# Load the module so the keys exist, and raise the limits
modprobe nf_conntrack
cat > /etc/sysctl.d/92-conntrack.conf <<'EOF2'
net.netfilter.nf_conntrack_max = 1048576
# Forget idle established connections after 2 hours instead of 5 days
net.netfilter.nf_conntrack_tcp_timeout_established = 7200
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
EOF2
sysctl --system

# Hash table size (about max / 4), applied at module load
echo 'options nf_conntrack hashsize=262144' > /etc/modprobe.d/nf_conntrack.conf
echo 262144 > /sys/module/nf_conntrack/parameters/hashsize

# Load the module at boot so the sysctl values are applied
echo nf_conntrack > /etc/modules-load.d/nf_conntrack.conf

conntrack のエントリーは 1 件あたり約 300 バイトのカーネルメモリを使うため、100 万件で約 300 MB になります。NAT を使わない純粋な Web サーバーであれば、notrack ルールでポート 80/443 の追跡自体を止めることもできますが、テーブルを大きくするほうが簡単な解決策です。

ステップ5:Nginx の設定を合わせる

# /etc/nginx/nginx.conf (main context and events block)
worker_processes auto;
worker_rlimit_nofile 1048576;

events {
    worker_connections 65535;
    multi_accept on;
}

http {
    keepalive_timeout 30;
    keepalive_requests 1000;
    # ...
}

# In each server block, let the listen socket use the bigger backlog
#   listen 80 backlog=65535;
#   listen 443 ssl backlog=65535;
nginx -t && systemctl reload nginx
grep 'Max open files' /proc/$(pgrep -f 'nginx: worker' | head -1)/limits

ステップ6:負荷がかかった状態で確認

アクセスのピーク時にステップ1のコマンドを再度実行します。ListenOverflows の増加が止まり、conntrack の件数が最大値を十分に下回ったままで、アプリケーションのログから「too many open files」エラーが消えていれば成功です。CPU、メモリ、ディスク、帯域の継続的な監視については、サーバー監視ガイドを参照してください。

よくある質問

最も重要な設定値はどれですか?

プロキシや API では、多くの場合サービスのファイルディスクリプタ上限と conntrack テーブルです。急にアクセスが集中するサイトでは、listen の backlog(somaxconn と Nginx の backlog)が重要になります。どれに該当するかはステップ1で確認できます。

sysctl で「cannot stat /proc/sys/net/netfilter/nf_conntrack_max」と表示される

conntrack モジュールがまだ読み込まれていません。modprobe nf_conntrack を実行し、ステップ4のとおり /etc/modules-load.d/ に追加してください。

Cloudflare や CDN を使っていても、これらの設定は必要ですか?

CDN を使うとサーバーに届く接続数は減りますが、オリジンには CDN が開く接続や、キャッシュされない API・WebSocket の通信を処理するための十分なファイルディスクリプタと backlog が依然として必要です。

上記の手順で解決しない場合は、サポートチケットを送信して IMIDC の 24 時間 365 日対応テクニカルサポートへご連絡ください。サーバー IP、OS バージョン、表示されているエラーメッセージ、ステップ1のコマンドの出力を添えていただくと、より迅速に調査できます。

この回答はお役に立ちましたか?

関連チュートリアル