はじめに — accept4() failed (24: Too many open files)
症状はこのように現れます。
2026/07/25 14:02:11 [alert] 14022#14022: accept4() failed (24: Too many open files)
2026/07/25 14:02:11 [alert] 14022#14022: accept4() failed (24: Too many open files)
あるいは Java ならこうです。
java.net.SocketException: Too many open files
at java.base/sun.nio.ch.Net.accept(Native Method)
Caused by: java.io.IOException: Too many open files
ここでほとんどの人が同じことをします。ulimit -n 65536 を実行し、サービスを再起動し、それでも同じエラーを見ます。その理由は 上限が一つではなく三つあり、シェルで変更した値がサービスには適用されないからです。
errno 24 は EMFILE です。文言を正確に読む必要があります。
errno 24 23
# EMFILE 24 Too many open files
# ENFILE 23 Too many open files in system
in system が付いていれば別の問題です。この違い一つで診断の半分が分かれます。
上限は一つではなく三つあります
三つがそれぞれ独立して存在し、どれか一つに引っかかるだけでファイルを開けなくなります。
| 上限 | 範囲 | 超過時の errno | 確認 | 設定場所 |
|---|---|---|---|---|
| RLIMIT_NOFILE soft | プロセス 1 つ | EMFILE (24) | /proc/PID/limits | ulimit -n、limits.conf、systemd LimitNOFILE |
| RLIMIT_NOFILE hard | プロセス 1 つ | 設定拒否 | /proc/PID/limits | 同上、非特権では下げることのみ可能 |
| fs.nr_open | カーネル全体 | EPERM/EINVAL | sysctl fs.nr_open | /etc/sysctl.d/ |
| fs.file-max | システム全体 | ENFILE (23) | cat /proc/sys/fs/file-nr | /etc/sysctl.d/ |
関係を整理するとこうなります。
fs.file-max : システム全体が同時に開けるファイル数 (すべてのプロセスの合計)
└─ fs.nr_open : 1 つのプロセスの RLIMIT_NOFILE が超えられない天井
└─ RLIMIT_NOFILE hard : 管理者が定めたそのプロセスの上限
└─ RLIMIT_NOFILE soft : 実際に適用される値。プロセスが hard まで自分で上げられる
現在の状態を一度に確認します。
sysctl fs.file-max fs.nr_open
# fs.file-max = 6553600
# fs.nr_open = 1048576
cat /proc/sys/fs/file-nr
# 13984 0 6553600
file-nr の三つの数字はそれぞれ、割り当て済みのファイルハンドル数、割り当て済みだが未使用の数 (最近のカーネルでは常に 0)、最大値です。一つ目が三つ目に近づけばシステム全体の問題であり、そのときに出るのが ENFILE です。上の例では 13984 対 655 万なので余裕は圧倒的です。ほとんどの現代的な Linux では fs.file-max はボトルネックではありません。カーネルがメモリサイズに比例して自動計算するからです。それでも多くのチューニング文書はこの値から上げろと言います。上げても無害ですが症状はそのままです。
fs.nr_open は違います。デフォルト値の 1048576 を超える ulimit -n は設定自体が失敗します。
ulimit -n 2000000
# bash: ulimit: open files: cannot modify limit: Operation not permitted
sudo sysctl -w fs.nr_open=2097152
# fs.nr_open = 2097152
ulimit -n 2000000 # これで成功します
実際に適用された値の確認 — ulimit ではなく /proc/PID/limits
ここが核心です。ulimit -n はいま開いているこのシェルの値を表示します。問題が起きているプロセスの値ではありません。この二つはしょっちゅう食い違います。
ulimit -n
# 1048576
pgrep -x nginx
# 14022
# 14025
sudo grep 'Max open files' /proc/14025/limits
# Max open files 1024 524288 files
シェルは 100 万なのに nginx ワーカーのソフト上限は 1024 です。この差こそが問題の正体です。今後この症状に出会ったら ulimit -n はいっさい見ずに、まずこのコマンドを実行してください。
# プロセス名でまとめて
for pid in $(pgrep -x nginx); do
printf '%6s %s\n' "$pid" "$(awk '/Max open files/ {print $4, $5}' /proc/"$pid"/limits)"
done
# 14022 1024 524288
# 14025 1024 524288
いま何個使っているかも合わせて見ます。
# 実際に開いている fd の数 (最も正確)
sudo ls /proc/14025/fd | wc -l
# 1021
# 使用率を 1 行で
for pid in $(pgrep -x nginx); do
used=$(sudo ls /proc/"$pid"/fd 2>/dev/null | wc -l)
soft=$(awk '/Max open files/ {print $4}' /proc/"$pid"/limits)
printf '%6s %6s / %-8s (%d%%)\n' "$pid" "$used" "$soft" $((used * 100 / soft))
done
# 14022 1021 / 1024 (99%)
# 14025 1019 / 1024 (99%)
lsof -p PID | wc -l を使うと数字はもっと大きく出ます。lsof はカレントワーキングディレクトリ、ルートディレクトリ、実行ファイル、メモリマップされたライブラリまで一緒に数えますが、これらは RLIMIT_NOFILE に含まれません。上限と比較するときは /proc/PID/fd の個数を使ってください。
systemd が ulimit 設定を無視する理由
いちばんよくある落とし穴です。次の設定を入れて再起動したのに何も変わらなかった経験があるなら、この節が答えです。
cat /etc/security/limits.d/99-nofile.conf
# * soft nofile 1048576
# * hard nofile 1048576
このファイルは PAM モジュールの pam_limits が読みます。そして pam_limits はログインセッションでのみ動作します。SSH 接続、su、login がこれに当たります。systemd が起動時に立ち上げるシステムサービスはログインセッションではないので、このファイルをそもそも読みません。
サービスの上限を決めるのは systemd です。優先順位はユニットの LimitNOFILE、なければ /etc/systemd/system.conf の DefaultLimitNOFILE、それもなければ systemd の組み込みデフォルト値です。
# ユニットに適用された値 (ハードとソフトが別々に出ます)
systemctl show -p LimitNOFILE -p LimitNOFILESoft nginx.service
# LimitNOFILE=524288
# LimitNOFILESoft=1024
# グローバルなデフォルト値
systemctl show -p DefaultLimitNOFILE -p DefaultLimitNOFILESoft
# DefaultLimitNOFILE=524288
# DefaultLimitNOFILESoft=1024
ここで systemd 240 以降のデフォルト動作を知っておく必要があります。systemd はハード上限を 524288 と大きく取っておきながら、ソフト上限は 1024 と低いままに保ちます。理由は互換性です。select() は fd 番号 1024 以上を扱えず、一部の古いプログラムは起動時に 0 からソフト上限まですべての fd を閉じるループを回るため、その上限が 100 万だと起動に数秒かかります。そこで「必要なプログラムが自分で上げて使え」というのが systemd の設計意図です。
実際に現代的なランタイムの多くがそうしています。Go はランタイムが起動時にソフトをハードまで上げるため、同じマシンで Go サービスは平気なのに他のサービスだけ EMFILE が出るという状況が生まれます。
正しい設定方法です。
sudo systemctl edit nginx.service
# 開いたエディタに以下を書きます
# [Service]
# LimitNOFILE=65536
sudo systemctl daemon-reload
sudo systemctl restart nginx
二つ強調しておきます。systemctl reload では rlimit は変わりません。プロセス生成時点で適用される値なので、必ず restart でなければなりません。そして LimitNOFILE=65536 のように値を一つだけ書くと、ソフトとハードの両方がその値になります。別々に与えたいときは LimitNOFILE=65536:524288 の形式を使います。
適用を確認します。
sudo grep 'Max open files' /proc/$(pgrep -x nginx | head -1)/limits
# Max open files 65536 65536 files
アプリケーション自身の設定がさらにある場合もあります。nginx は worker_rlimit_nofile というディレクティブを別に持っており、この値は systemd が与えたハード上限を超えられません。
# /etc/nginx/nginx.conf
worker_rlimit_nofile 65536;
events {
worker_connections 32768;
}
worker_connections が worker_rlimit_nofile より大きくても意味がありません。接続 1 つがクライアントソケット 1 つとアップストリームソケット 1 つを使いうるので、リバースプロキシなら worker_rlimit_nofile を worker_connections の 2 倍以上に取るのが安全です。
再起動なしで急場をしのぐ方法もあります。
sudo prlimit --pid 14025 --nofile=65536:65536
sudo prlimit --pid 14025 --nofile
# RESOURCE DESCRIPTION SOFT HARD UNITS
# NOFILE max number of open files 65536 65536 files
prlimit は実行中のプロセスの上限を変更します。ただしアプリケーションが起動時に上限を読んで内部データ構造のサイズを決めていた場合 (nginx のコネクションプールがそうです) は効果がないことがあります。本当の解決はユニットファイルの修正と再起動です。
コンテナのルールはまた別です。コンテナ内プロセスの RLIMIT_NOFILE は OCI ランタイム仕様の process.rlimits から来ており、その値はコンテナランタイムのデーモンが決めます。シェルの ulimit も、ホストの limits.conf も関与しません。
# コンテナの中で確認するのが唯一確実な方法です
docker exec api-server cat /proc/1/limits | grep 'Max open files'
# Max open files 1048576 1048576 files
# コンテナごとに指定
docker run --ulimit nofile=65536:65536 myimage
# デーモン全体のデフォルト値
cat /etc/docker/daemon.json
# {
# "default-ulimits": { "nofile": { "Name": "nofile", "Soft": 65536, "Hard": 65536 } }
# }
Kubernetes の Pod スペックには ulimit フィールドがありません。Pod 単位で調整するには、コンテナのエントリポイントで ulimit -n を使って下げるか (ハード以下にのみ可能)、ノードの containerd 設定を変えるか、コンテナランタイムのユニットの LimitNOFILE を調整する必要があります。ランタイムのユニットが LimitNOFILE=infinity になっているディストリビューションが多く、その場合コンテナは非常に大きな値を受け継ぎます。潤沢で良さそうに見えますが、先に述べた「fd を全部閉じる」ループを回るプログラムでは起動に数十秒かかる原因になります。
何が fd を消費しているのか — ソケット・ファイル・パイプに細分する
上限を上げる前に何が占めているのかを見なければなりません。/proc/PID/fd のシンボリックリンク先が種類を教えてくれます。
sudo ls -l /proc/14025/fd | awk '{print $NF}' \
| sed -E 's#^(socket|pipe|anon_inode):.*#\1#; s#^/.*#regular-file#' \
| sort | uniq -c | sort -rn
# 61204 socket
# 3891 regular-file
# 412 anon_inode
# 8 pipe
# 3 /dev/null
ソケットが 6 万個ならネットワーク側です。どんなソケットなのかをさらに掘ります。
# 状態ごとのソケット数
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
# 38210 ESTAB
# 21044 CLOSE-WAIT
# 2118 TIME-WAIT
# 412 LISTEN
CLOSE-WAIT が 2 万個あります。これが fd リークの決定的な証拠です。CLOSE-WAIT は相手が FIN を送って接続を閉じたのに、こちら側のアプリケーションが close() を呼んでいない状態です。カーネルはアプリケーションが閉じてくれるのを待って無期限にこの状態で残ります。タイムアウトはありません。TIME-WAIT と混同しないでください。TIME-WAIT はカーネルが勝手に片付けますし、fd も消費しません。
どの相手との接続なのかを見ると原因コードが絞り込まれます。
sudo ss -tanp state close-wait | head -5
# Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# 1 0 10.0.3.14:44120 10.0.9.31:6379 users:(("java",pid=21874,fd=1042))
# 1 0 10.0.3.14:44122 10.0.9.31:6379 users:(("java",pid=21874,fd=1043))
# 相手アドレスごとの集計
sudo ss -tan state close-wait | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn
# 20981 10.0.9.31
# 63 10.0.9.42
すべて同じ相手 (Redis) です。Redis クライアントプールが接続を返していないということであり、ここでコードレビューの対象が決まります。
ファイル側が犯人なら、どのファイルなのかを数えます。
sudo ls -l /proc/21874/fd | awk '$NF ~ /^\// {print $NF}' \
| sed -E 's#/[^/]+$##' | sort | uniq -c | sort -rn | head -5
# 3204 /var/lib/app/uploads/tmp
# 512 /data/kafka/logs/orders-0
# 88 /usr/lib/x86_64-linux-gnu
/var/lib/app/uploads/tmp に 3204 個が開いています。一時ファイルを作って閉じないコードパスがあるということです。
inotify と epoll は別枠の上限です
anon_inode 系は RLIMIT_NOFILE も消費しますが、それ自体の上限が別にあります。そのため fd に十分な余裕があるのに失敗することが起きます。
inotify の上限は三つあります。
sysctl fs.inotify
# fs.inotify.max_queued_events = 16384
# fs.inotify.max_user_instances = 128
# fs.inotify.max_user_watches = 65536
max_user_instances はユーザーごとに作れる inotify インスタンス (inotify_init) の数で、超えると EMFILE です。ulimit -n とまったく同じ文言のエラーが出ますが原因は別物です。max_user_watches はユーザーごとに監視できるパスの数で、超えると ENOSPC、つまり "No space left on device" が出ます。ディスクとは無関係なのにディスクエラーのように見えます。
どちらの上限もユーザー単位であることが重要です。同じ UID で動くコンテナ数十個が一つの予算を分け合います。ノードに Pod を増やしたら急に監視ベースのツールが死ぬという典型的なパターンはここから出ます。
現在の使用量はこう数えます。
# インスタンスを握っているプロセス
sudo find /proc/[0-9]*/fd -lname 'anon_inode:*inotify*' 2>/dev/null \
| cut -d/ -f3 | sort | uniq -c | sort -rn | head -5
# 42 9931
# 8 3311
# 2 1042
# 実際の watch 数 (fdinfo の inotify 行数がそのまま watch 数です)
sudo grep -c '^inotify' /proc/9931/fdinfo/* 2>/dev/null | grep -v ':0$' | head
# /proc/9931/fdinfo/18:31204
# /proc/9931/fdinfo/22:8102
cat /etc/sysctl.d/60-inotify.conf
# fs.inotify.max_user_watches = 524288
# fs.inotify.max_user_instances = 1024
sudo sysctl --system
epoll にも別の上限があります。
cat /proc/sys/fs/epoll/max_user_watches
# 3618421
この値はユーザーごとに登録できる epoll 監視項目の数で、カーネルが低位メモリのおよそ 4% を基準に自動算定します。項目一つがカーネルメモリを数十バイト使うため、無制限ではないのです。実務でこの上限にぶつかることはまれですが、数百万コネクションを扱うプロキシなら確認対象です。
整理するとこうなります。
| リソース | 上限 | 単位 | 超過時の errno |
|---|---|---|---|
| 通常の fd | RLIMIT_NOFILE | プロセス | EMFILE (24) |
| システム全体のファイル | fs.file-max | システム | ENFILE (23) |
| inotify インスタンス | fs.inotify.max_user_instances | ユーザー | EMFILE (24) |
| inotify watch | fs.inotify.max_user_watches | ユーザー | ENOSPC (28) |
| epoll watch | fs.epoll.max_user_watches | ユーザー | ENOSPC (28) |
真の原因はたいていリーク — 確認方法と応急処置
上限を上げて解決するケースは二つだけです。実際に同時接続がそれだけ必要な場合、そしてデフォルト値が非常識に低い場合です。残りはすべてリークであり、リークに対して上限を上げるのは障害の時点を数時間後ろにずらすだけの行為です。
リークの定義は単純です。fd 使用量が負荷と無関係に単調増加するならリークです。負荷とともに上下するなら正常な容量の問題です。これを見分けるには数分間のサンプリングで十分です。
PID=21874
while sleep 60; do
printf '%s fd=%-7s estab=%-7s close_wait=%s\n' \
"$(date +%H:%M:%S)" \
"$(sudo ls /proc/$PID/fd | wc -l)" \
"$(ss -tan state established | wc -l)" \
"$(ss -tan state close-wait | wc -l)"
done
# 14:02:11 fd=18204 estab=8102 close_wait=9931
# 14:03:11 fd=19388 estab=8044 close_wait=11102
# 14:04:11 fd=20511 estab=7981 close_wait=12290
# 14:05:11 fd=21702 estab=8120 close_wait=13401
接続数 (estab) は 8000 付近で平らなのに fd は毎分 1200 ずつ上がります。そして増加分が close_wait の増加分と正確に一致しています。ソケットリークが確定し、前節の ss -tanp で相手まで特定できていれば、調査すべきコードの範囲はごく狭いものになります。
どのコードパスが開いているのかまで捕まえるにはトレースが必要です。
# 開かれるファイルとコールスタック (bcc ツール)
sudo opensnoop-bpfcc -p 21874
# fd を開くシステムコールの頻度
sudo bpftrace -e 'tracepoint:syscalls:sys_exit_openat /pid == 21874 && args->ret > 0/ { @[ustack] = count(); }'
# strace は負荷が大きいので短時間だけ
sudo timeout 5 strace -f -e trace=openat,socket,close -p 21874 -c
# % time seconds usecs/call calls errors syscall
# ------ ----------- ----------- --------- --------- ----------------
# 61.02 0.184203 18 10233 socket
# 38.44 0.116041 21 5488 openat
# 0.54 0.001632 4 402 close
socket が 10233 回なのに close は 402 回です。比率だけ見ても答えは出ます。
応急処置の選択肢は三つあり、順序があります。
# 1) プロセス再起動 — 最も確実だがサービス停止
sudo systemctl restart myapp
# 2) prlimit で上限だけ上げて時間を稼ぐ (再起動なし)
sudo prlimit --pid 21874 --nofile=200000:200000
# 3) 特定の fd を強制的に閉じる — 最後の手段、アプリケーションが壊れる可能性あり
sudo gdb -p 21874 -batch -ex 'call (int)close(1042)'
3 番はアプリケーションがその fd をまだ有効だと信じている状態で閉じるので、データ破損やクラッシュを覚悟しなければなりません。本番で最後に残った選択肢のときだけ使ってください。
おわりに — 三つの上限と一つの真の原因
一つだけ覚えるならこれです。ulimit -n はいま開いているこのシェルの値であり、サービスとは何の関係もありません。見るべきものは常に /proc/PID/limits です。
診断の順序を圧縮するとこうなります。
- エラーの文言を正確に読みます。
in systemが付いていればfs.file-max、そうでなければプロセスの上限です。 /proc/PID/limitsで実際の適用値を、ls /proc/PID/fd | wc -lで実使用量を見ます。この二つだけで上限の問題かどうかが即座に判定できます。- systemd サービスなら limits.conf ではなく
systemctl editでLimitNOFILEを設定しrestartします。reloadでは変わりません。 - コンテナはランタイムのデーモンが値を決めます。コンテナの中で
/proc/1/limitsを直接確認してください。 - fd の構成をソケット・ファイル・パイプに分けて見ます。
CLOSE-WAITの多発はリークの確定証拠です。 - 使用量が負荷と無関係に単調増加するなら上限を上げても無駄です。上限の引き上げは調査する時間を稼ぐ措置であって解決ではありません。
- "Too many open files" が出ているのに fd に余裕があるなら inotify インスタンスの上限を確認してください。文言が同じです。
현재 단락 (1/144)
症状はこのように現れます。