こんにちは、インフラエンジニアのryuです。
Webサーバーやアプリケーションのログに Too many open files と出て、新しい接続を受け付けなくなる。再起動するとしばらくは直るものの、数時間後や数日後にまた同じエラーが出る。Linuxのサーバーを運用していると、こうした場面に出会うことがあります。
このエラーは、プロセスが開けるファイルの数の上限に達したことを表しています。検索すると「ulimit -n を増やせば直る」という情報がよく出てきますが、サービスとして動いているプロセスには、シェルで ulimit を変えても効きません。また、上限に当たる理由が「ファイルを閉じ忘れて増え続けている」ことなら、上限を上げても時間稼ぎにしかなりません。
この記事では、どの上限に当たっているのかを確かめ、上限が低いのか、使いすぎているのかを見分けたうえで、systemd の LimitNOFILE= で直すまでの順番を紹介します。対象は systemd で動く一般的なLinuxディストリビューション(RHEL系・Ubuntuなど)で、確認には root 権限を使います。
Too many open filesは、何の数が足りなくなったエラーなのか?¶
Linuxでは、プロセスがファイルを開くと、カーネルからファイルディスクリプタという番号が渡されます。プロセスはこの番号を使って読み書きをします。ここで大事なのは、数えられるのは普通のファイルだけではないことです。
ネットワークのソケット、パイプ、ログファイル、共有ライブラリ以外で開いているデバイスなども、すべてファイルディスクリプタを1つずつ使います。Webサーバーなら、つながっているクライアントの数だけソケットが開くので、アクセスが増えるとそれに比例して使う数も増えます。
このエラーには、似た2つの種類があります。Linuxの open(2) のマニュアルでは、次のように区別されています。
| エラーの表示 | エラー番号 | 当たっている上限 | 主に確かめる場所 |
|---|---|---|---|
Too many open files |
EMFILE |
プロセスごとの上限(RLIMIT_NOFILE) |
/proc/<PID>/limits |
Too many open files in system |
ENFILE |
システム全体の上限 | /proc/sys/fs/file-nr と file-max |
ほとんどの場合に出るのは、上の段のプロセスごとの上限です。下の段のシステム全体の上限に当たることはまれですが、表示がよく似ているので、エラー文の最後に in system が付いているかどうかを最初に見ておきます。
まず、プロセスの上限と今の使用数を確かめる¶
上限を変える前に、問題のプロセスが今いくつまで開けて、実際にいくつ開いているのかを確かめます。この2つの数字がそろうと、次に何をするべきかがはっきりします。
/proc/PID/limitsで、そのプロセスの上限を見る¶
まず、対象のプロセスのPIDを調べます。systemd のサービスなら、systemctl show でメインのプロセスのPIDを取り出せます。
systemctl show -p MainPID nginx
PIDが分かったら、/proc/<PID>/limits を見ます。ここには、そのプロセスに今かかっている上限がそのまま出ます。
cat /proc/1234/limits | grep -E 'Limit|open files'
出力の形は次のとおりです(数値は説明用の例で、実際の値は環境によって変わります)。
Limit Soft Limit Hard Limit Units
Max open files 1024 524288 files
Soft Limit が実際に効いている上限で、Hard Limit は、プロセス自身がソフトリミットを引き上げられる天井です。エラーが出るのはソフトリミットに達したときなので、まず左側の数字を見ます。prlimit --pid 1234 --nofile でも同じ値を確認できます。
ここでよくある勘違いが、ログインしたシェルで ulimit -n を打って、その値を見てしまうことです。ulimit -n が表示するのは、そのシェルの上限です。systemd が起動したサービスは、ログインしたシェルとは別の経路で上限が決まるので、数字が違っていることは珍しくありません。サービスの上限は、必ず /proc/<PID>/limits で見るようにします。
/proc/PID/fdで、今いくつ開いているかを数える¶
次に、今開いているファイルディスクリプタの数を数えます。/proc/<PID>/fd の下には、開いているファイルディスクリプタが1つずつシンボリックリンクとして並んでいます。
ls /proc/1234/fd | wc -l
この数がソフトリミットに近いか、同じになっていれば、プロセスごとの上限に当たっていると判断できます。逆に、エラーが出ていた時刻からかなり時間がたっていて、今は数が少ないこともあります。その場合は、後で説明するように時間をおいて何度か数え、増え方を見ます。
なお、Nginxや Apache のように、メインのプロセスとは別にワーカーのプロセスを持つサービスもあります。上限はプロセスごとにかかるので、実際に接続を受けているワーカー側のPIDも ps で確かめて、同じように数えておきます。プロセスの親子関係の見方はpsコマンドの使い方で解説しています。
lsofで、何を開いているかの内訳を見る¶
数が分かったら、何を開いているのかを見ます。lsof -p を使うと、開いているファイルの一覧が種類つきで表示されます。
lsof -p 1234 | awk 'NR>1 {print $5}' | sort | uniq -c | sort -rn
5番目の列は TYPE で、普通のファイルは REG、IPv4やIPv6のソケットは IPv4・IPv6、パイプは FIFO のように表示されます。この集計で、ソケットが大半なのか、普通のファイルが大半なのかが分かります。
ソケットが大半なら、ネットワークの接続がどういう状態で残っているかを ss で確かめます。特に、相手が閉じたのに自分の側が閉じていない CLOSE_WAIT が増え続けている場合は、アプリケーションが接続を閉じ忘れている可能性が高くなります。TCPの状態の数え方はTIME_WAITとCLOSE_WAITをssで見分ける記事にまとめています。
普通のファイルが大半なら、同じファイル名が何度も並んでいないかを見ます。同じログファイルや一時ファイルを何度も開き直して閉じていない場合、同じパスがずらりと並ぶので見つけやすいです。
上限が低いのか、使いすぎているのかを見分ける¶
ここまでの数字がそろったら、原因を2つのどちらかに分けます。どちらかによって、直し方がまったく変わるからです。
1つ目は、上限が仕事の量に対して低すぎる場合です。同時接続が多いサーバーで、ソフトリミットが 1024 のままになっている、というのが典型的な例です。このときは、アクセスが多い時間帯にだけ使用数が上限まで上がり、アクセスが減れば使用数も下がります。
2つ目は、ファイルディスクリプタの漏れです。開いたものを閉じないまま処理を続けるので、アクセスの量に関係なく使用数が少しずつ増え、最後は上限に当たります。再起動すると直り、しばらくするとまた起きる、という症状はこちらでよく見られます。
見分けるには、同じプロセスの使用数を時間をおいて記録するのがいちばん確実です。
while true; do
echo "$(date '+%F %T') $(ls /proc/1234/fd | wc -l)"
sleep 60
done
アクセスが少ない夜間も数が下がらず、右肩上がりに増え続けるなら漏れを疑います。この場合は上限を上げても、エラーが出るまでの時間が延びるだけです。lsof の内訳で増えている種類を突き止め、アプリケーションの開発者やベンダーに情報を渡して、閉じ忘れを直してもらう必要があります。
漏れが疑われる場合でも、直るまでのあいだサービスを止めないために、一時的に上限を上げることはあります。そのときは、上限を上げたことと、根本の原因が残っていることを、作業の記録に残しておきましょう。
systemdのサービスは、LimitNOFILEで上限を変える¶
上限が低いことが原因と分かったら、上限を上げます。systemd で動くサービスの場合、上限はユニットファイルの LimitNOFILE= で決まります。
systemdが渡す既定の上限を知っておく¶
systemd 240 からは、systemd が起動するプロセスに渡す上限の既定値が、ソフトリミット 1024、ハードリミット 524288(512K)になっています。systemd の更新履歴によると、ハードリミットはそれまでカーネルの既定値の 4096 だったものを大きく引き上げましたが、ソフトリミットは互換性のために 1024 のまま残されました。
ソフトリミットを据え置いた理由は、古くからある select() というシステムコールにあります。select() は 1024 以上の番号のファイルディスクリプタを扱えないので、全体のソフトリミットを上げると、select() を使っているプログラムが思わぬところで壊れるおそれがあるためです。
そのため systemd のマニュアルでは、select() を使わないアプリケーションは、自分でソフトリミットをハードリミットまで引き上げることを想定しています。実際、Nginxの worker_rlimit_nofile のように、アプリケーション側の設定で上限を上げられるソフトウェアもあります。サービスの上限を変える前に、アプリケーションに同じ役割の設定がないかを公式のドキュメントで確かめておくと安全です。
systemctl editで上限を上書きする¶
ユニットファイルで上限を変えるときは、パッケージに入っている元のファイルを直接書き換えず、systemctl edit で上書き用の設定(ドロップイン)を作ります。元のファイルは、パッケージの更新で上書きされることがあるからです。
sudo systemctl edit nginx
開いたエディタに、次のように書きます。
[Service]
LimitNOFILE=65536
値を1つだけ書くと、ソフトリミットとハードリミットの両方がその値になります。別々に指定したいときは LimitNOFILE=65536:524288 のように、コロンで区切って「ソフト:ハード」の順に書きます。
保存したら、設定を読み込み直してサービスを再起動します。上限はプロセスが起動するときに決まるので、reload ではなく restart が必要です。
sudo systemctl daemon-reload
sudo systemctl restart nginx
systemctl show -p LimitNOFILE -p LimitNOFILESoft nginx
最後に、もう一度 /proc/<PID>/limits を見て、新しいPIDのプロセスに上限が反映されたことを確かめます。systemctl show の表示が変わっていても、プロセスを起動し直していなければ、動いているプロセスの上限は古いままです。設定を変えたのに効かないときの調べ方は、systemdのサービスが起動しないときの記事も参考になります。
limits.confを書き換えても、サービスに効かない理由¶
/etc/security/limits.conf に nofile を書けば上限が上がる、という情報もよく見かけます。この設定は、PAM を通るログインのセッションに上限をかける仕組みです。SSHでログインしたユーザーのシェルや、そこから起動したプログラムには効きます。
一方、systemd が起動するシステムのサービスは、ログインのセッションを通りません。systemd のマニュアルでは、ユニットに上限を書かなかった場合は systemd-system.conf の DefaultLimitNOFILE= などの既定値が使われると説明されています。サービスに効かせたいなら、ユニットの LimitNOFILE= を使うのが確実です。
システム全体の上限に当たったときの確かめ方¶
エラー文が Too many open files in system だった場合は、システム全体の上限を見ます。カーネルのドキュメントによると、/proc/sys/fs/file-nr には、割り当て済みのファイルハンドルの数、割り当て済みで使われていない数、最大数の3つが並んでいます。
cat /proc/sys/fs/file-nr
cat /proc/sys/fs/file-max
1つ目の数が3つ目の最大数に近ければ、システム全体の上限に当たっています。上限に当たったときは、カーネルのログに VFS: file-max limit <number> reached というメッセージが記録されるので、journalctl -k で探すこともできます。
ただし、最近の systemd を使うディストリビューションでは、起動時に fs.file-max と fs.nr_open をとても大きな値に引き上げる動きがあり、システム全体の上限に当たる場面は少なくなっています。こちらに当たっている場合は、特定のプロセスが極端に多くのファイルを開いていることが多いので、上限を上げる前に、どのプロセスが使っているかを lsof で調べるのが先です。
うまくいかないときの確認ポイント¶
ここまでの手順で直らないときに、よく見落とす点を表にまとめます。
| 状況 | 確認すること |
|---|---|
ulimit -n は大きいのに、サービスでエラーが出る |
シェルの値を見ていないか。/proc/<PID>/limits でサービスの値を見る |
LimitNOFILE= を書いたのに値が変わらない |
daemon-reload と restart をしたか。ドロップインが別のファイルで上書きされていないか(systemctl cat で確認) |
| 上限は上がったのに、まだエラーが出る | ワーカーなど別のプロセスの上限が古いままではないか。アプリケーション側の上限設定がないか |
| 上限を上げても、数日でまた起きる | 使用数が増え続けていないか。lsof の内訳で増えている種類を探す |
| ディスクの空きが減らない | 削除済みなのに開かれたままのファイルがないか |
最後の行は、ファイルディスクリプタの話と関係が深いものです。プロセスが開いたままのファイルは、削除しても領域が解放されません。lsof で deleted と表示されるファイルを探す手順は、ディスクが100%なのにduで数えると空いているときの記事で解説しています。
エラーが出た時刻と内容を正確に押さえるには、アプリケーションのログとシステムのログを突き合わせるのが基本です。ログの探し方に慣れていない方は、ログの読み方入門から読んでみてください。
InfraAcademyで、Linuxのプロセスとsystemdを学び直す¶
Too many open files の切り分けには、プロセスとPID、/proc の見方、systemd のユニット、ネットワークの接続状態といった知識が少しずつ必要になります。どれか1つがあいまいだと、どこを見ればいいかで迷いやすくなります。
InfraAcademy のLinuxロードマップでは、プロセス管理や systemd、ログの扱いといったLinuxの基礎を順番に学べるようにしています。現場で出会ったエラーをきっかけに、土台から整理し直したい方はのぞいてみてください。
まとめ¶
Too many open files が出たら、まずエラー文に in system が付いているかを見て、プロセスごとの上限なのか、システム全体の上限なのかを分けます。プロセスごとの上限なら、/proc/<PID>/limits で上限を、/proc/<PID>/fd と lsof で使用数と内訳を確かめます。
使用数がアクセスに合わせて上下するなら上限が低いので、systemd の LimitNOFILE= をドロップインで設定し、サービスを再起動します。使用数が増え続けるなら漏れを疑い、上限を上げるだけで終わらせないことが大切です。
次にこのエラーに出会ったら、ulimit -n を打つ前に cat /proc/<PID>/limits を1回実行してみてください。それだけで、どこから調べればいいかがかなり見えてくるはずです。



