はじめに
先日、僕がサーバー保守を担当しているサーバーのWebサイトが、Webサイト監視サービスSite24x7でサイトダウンを検知しました。
このエラーの原因調査と対策について記載します。
サーバーのOSは、AlmaLinux 9 です。
緊急対処
Site24x7が検知したエラー内容は「403 Forbidden」でした。
サーバー自体は正常に稼働しており、CPU、メモリリソース等の負荷も問題ありませんでした。
Apacheのエラーログを確認すると、数週間前から、以下のような「Too many open files」のエラーメッセージが出力されていました。
[Thu Oct 02 10:08:46.681482 2025] [core:error] [pid 3478456:tid 3478586] (24)Too many open files: AH02179: apr_socket_accept: (client socket) [Thu Oct 02 10:08:46.849702 2025] [core:error] [pid 3478268:tid 3478398] (24)Too many open files: AH02179: apr_socket_accept: (client socket) [Thu Oct 02 10:08:47.734264 2025] [core:error] [pid 3478589:tid 3478719] (24)Too many open files: AH02179: apr_socket_accept: (client socket) [Thu Oct 02 10:08:48.854724 2025] [core:error] [pid 3478720:tid 3478850] (24)Too many open files: AH02179: apr_socket_accept: (client socket)
また、Perl/CGI製の監視ツール画面が表示されない(真っ白)現象が発生しました。
このサイトはMovable Typeを採用しており、MTの管理画面が表示されないとまずいので、緊急対処として、Apacheを(reloadではなく)restartで再起動しました。
その後、Perl/CGI製の監視ツール画面は正しく表示されるようになり、「403 Forbidden」や「Too many open files」のエラーはなくなり、問題は解消されました。
調査と原因
「Too many open files」は、
「Apacheプロセスがオープンできるファイルディスクリプタ数の上限に達したので、これ以上処理できません。」
ということですね。
ファイル・ディスクリプタの制限と変更については、以下の記事がとても詳しく、わかりやすいです。
(参考)
・Linux ファイル・ディスクリプタ制限のチューニング – Oji-Cloud
https://oji-cloud.net/2022/03/16/post-6951/
これを参考に調査しますが、残念ながら緊急対処としてApacheプロセスを再起動してしまったため、「Too many open files」エラーが発生している状況での調査はできず、本当の原因はわかりませんでした。
※再起動する前に、プロセスの状況などを調査すべきでした。反省点です。
このサーバーでは、Movable TypeのWebサイトが稼働しています。
たとえば、Movable Typeで大量のページを再構築するときにファイルをオープンし、何らかの不具合でクローズせずに異常終了してしまうと、ファイルディスクリプタ数が大量にオープンしたままとなってしまうような状況が発生し得るかな?
とは思いましたが、推測にすぎません。
以下、問題が解消された状態での調査となります。
プロセスがオープンしているファイルディスクリプタは、/proc/<プロセスID>/fd 以下で確認できます。
# ps -ef | grep httpd root 33111 1 0 11月06 ? 00:00:23 /usr/sbin/httpd -DFOREGROUND apache 248069 33111 0 05:58 ? 00:00:00 /usr/sbin/httpd -DFOREGROUND apache 248070 33111 0 05:58 ? 00:00:10 /usr/sbin/httpd -DFOREGROUND apache 248071 33111 0 05:58 ? 00:00:10 /usr/sbin/httpd -DFOREGROUND root 257632 255578 0 12:02 pts/1 00:00:00 grep --color=auto httpd # ls -l /proc/248069/fd ... -wx------ 1 root root 64 11月 12 05:59 44 -> /var/log/httpd/ssl_example.com-access_log lrwx------ 1 root root 64 11月 12 05:59 46 -> socket:[225399619] lrwx------ 1 root root 64 11月 12 05:59 47 -> socket:[225784627] lrwx------ 1 root root 64 11月 12 05:59 48 -> socket:[225805231] lrwx------ 1 root root 64 11月 12 05:59 49 -> socket:[226349472] lr-x------ 1 root root 64 11月 12 05:59 5 -> pipe:[228570930] lrwx------ 1 root root 64 11月 12 05:59 50 -> socket:[226368677] lrwx------ 1 root root 64 11月 12 05:59 51 -> socket:[226776265] lrwx------ 1 root root 64 11月 12 05:59 52 -> socket:[226888672] lrwx------ 1 root root 64 11月 12 05:59 53 -> socket:[226909672] lrwx------ 1 root root 64 11月 12 05:59 54 -> socket:[227327665] lrwx------ 1 root root 64 11月 12 05:59 55 -> socket:[227440903] lrwx------ 1 root root 64 11月 12 05:59 56 -> socket:[227460163] lrwx------ 1 root root 64 11月 12 05:59 57 -> socket:[227877373] lrwx------ 1 root root 64 11月 12 05:59 58 -> socket:[227992089] lrwx------ 1 root root 64 11月 12 05:59 59 -> socket:[228011997] l-wx------ 1 root root 64 11月 12 05:59 6 -> pipe:[228570930] lrwx------ 1 root root 64 11月 12 05:59 60 -> socket:[228265756] lrwx------ 1 root root 64 11月 12 05:59 61 -> socket:[228303655] lrwx------ 1 root root 64 11月 12 05:59 62 -> socket:[228550509] l-wx------ 1 root root 64 11月 12 05:59 7 -> /var/log/httpd/ssl_example.com-error_log ...
ソケットがけっこうあるんだな、とは思いますが、(問題が解消されたあとなので)特におかしな箇所はなさそうです。
プロセスが開いているファイルディスクリプタ数を調べるため、/proc/<プロセスID>/fd 以下をカウントします。
Apacheプロセスは複数存在するので、for文を使ってみます。
# for pid in $(pgrep httpd); do echo "PID $pid: $(ls /proc/$pid/fd | wc -l)"; done PID 33111: 64 PID 248069: 62 PID 248070: 69 PID 248071: 89
一方、プロセスの各種上限値は、/proc/<プロセスID>/limits で確認できます。
ファイルディスクリプタ数の上限値は、Max open files が該当します。
# cat /proc/2789/limits Limit Soft Limit Hard Limit Units ... Max open files 1024 524288 files ...
ファイルディスクリプタ数の上限値は1024で、現在開いている数と比べると、まだまだ余裕があります。
対策と設定手順
発生した問題と現状を踏まえ、当該サーバーでは、以下の対策を実施しました。
- ファイルディスクリプタ数の上限値を増やす。
- 監視ツールで監視ツールでファイルディスクリプタ数を監視する
1. ファイルディスクリプタ数の上限値を増やす
ファイルディスクリプタ数の上限をデフォルトの 1024 から 2048 に増やしてみます。
(もっと増やしてもいいのかも。。)
OSを再起動しても設定がクリアされないよう、ApacheのSystemdで設定します。
# mkdir /etc/systemd/system/httpd.service.d/ # vim /etc/systemd/system/httpd.service.d/limits.conf
[Service] LimitNOFILE=2048
Systemdに反映します。
# systemctl daemon-reload
Apacheを再起動して反映します。
# systemctl restart httpd
# systemctl status httpd
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
Drop-In: /etc/systemd/system/httpd.service.d
mqlimits.conf, php-fpm.conf
Active: active (running) since Wed 2025-11-12 12:35:39 JST; 3min 31s ago
Docs: man:httpd.service(8)
Main PID: 258540 (httpd)
Status: "Total requests: 174; Idle/Busy workers 100/0;Requests/sec: 0.833; Bytes served/sec: 85KB/sec"
Tasks: 262 (limit: 17355)
Memory: 32.6M
CPU: 331ms
CGroup: /system.slice/httpd.service
tq258540 /usr/sbin/httpd -DFOREGROUND
tq258541 /usr/sbin/httpd -DFOREGROUND
tq258542 /usr/sbin/httpd -DFOREGROUND
mq258543 /usr/sbin/httpd -DFOREGROUND
...
現在のファイルディスクリプタ数の上限値を確認します。
# cat /proc/258543/limits | grep open Max open files 2048 2048 files
↑2048となったことが確認できました。
当該サーバーは本番稼働中なので、OSの再起動はできなかったのですが、別途用意した検証サーバーで、OSを再起動したときに上限値がキープされることを確認しました。
2. 監視ツールでファイルディスクリプタ数を監視する
ふだんのサーバー監視で使用している監視ツールMRTG, Nagiosの監視項目に、
「Apacheプロセスがオープンしているファイルディスクリプタ数(の最大値)」
を追加します。
Apacheは複数のプロセスが起動しているため、複数プロセスの中での最大値を取得します。
※MRTG, Nagiosは古いツールですが、RDBを使用せず軽量でほとんどサーバーに負荷をかけないため、低スペックのサーバー監視に向いていると考えていて、お気に入りです。
※Nagiosで pnp4nagios や nagiosgraph を設定して、グラフ表示機能を使用していれば、MRTGの設定は不要ですね。
以下、MRTG, Nagiosは設定完了、稼働済みであることを前提としています。
2-1. MRTGの監視項目を追加
「Apacheプロセスが開いているファイルディスクリプタ数の最大値」をMRTGでグラフ化します。
値を取得するシェルスクリプトを作成し、MRTGはそのスクリプトをSNMP経由で実行することでグラフ描画します。
・シェルスクリプトを作成
# vim /root/bin/httpd_max_open_files_snmp.sh
#/bin/bash
#
# MRTGで使用する、複数のApacheプロセスの中で、開いている
# ファイルディスクリプタ数の最大値を出力するスクリプト
# httpd_max_open_files_snmp.sh
#
#
PROC_NAME=httpd
maxfd=0
maxpid=0
for pid in $(pgrep $PROC_NAME); do
fdcount=$(ls /proc/$pid/fd 2>/dev/null | wc -l)
if [ $fdcount -gt $maxfd ]; then
maxfd=$fdcount
maxpid=$pid
fi
done
#echo "最大ファイルディスクリプタ数: $maxfd (PID: $maxpid)"
echo $maxfd
exit 0
# EOF
実行権限を付与します。
# chmod 700 /root/bin/httpd_max_open_files_snmp.sh
動作確認のため、シェルスクリプトを実行し、ファイルディスクリプタ数を確認するコマンドの実行結果と比較して、正しく最大値が出力されることを確認します。
# /root/bin/httpd_max_open_files_snmp.sh 51 # for pid in $(pgrep httpd); do echo "PID $pid: $(ls /proc/$pid/fd | wc -l)"; done PID 258540: 47 PID 258541: 45 PID 258542: 51 PID 258543: 51
↑同じ 51 なのでOKです。
(Webアクセスによって頻繁に変動するので、何度か確認します。)
・SNMPの設定
作成したシェルスクリプトを実行するexecディレクティブを追加します。
# vim /etc/snmp/snmpd.conf ... exec httpd_max_open_files_snmp /bin/bash /root/bin/httpd_max_open_files_snmp.sh
snmpdを再起動して、反映します。
# systemctl restart snmpd
snmpwalkコマンドで、値が取得できることを確認します。
# snmpwalk -v2c -c hec_admin hec-cloud3 .1.3.6.1.4.1.2021.8.1.101.5 UCD-SNMP-MIB::extOutput.5 = STRING: 51
※今回は、snmpd.confで、execディレクティブとしては5番目のエントリーとしたので、末尾は5として取得しています。1番目なら1など、適宜変更します。
・MRTGの設定
# vim /etc/mrtg/mrtg.cfg
MRTGのエントリーを追加します。
<hostname>と<snmp_community_name>は、適宜置き換えます。
グラフの最大値は 2050 としています。
### Apache Max OpenFiles Target[<hostname>_httpd-max-open-files]: .1.3.6.1.4.1.2021.8.1.101.5&.1.3.6.1.4.1.2021.8.1.101.5:<snmp_community_name>@<hostname>::5 MaxBytes[<hostname>_httpd-max-open-files]: 2050 WithPeak[<hostname>_httpd-max-open-files]: wmy Unscaled[<hostname>_httpd-max-open-files]: dwmy Options[<hostname>_httpd-max-open-files]: gauge, growright, integer, nolegend, nopercent YLegend[<hostname>_httpd-max-open-files]: Count ShortLegend[<hostname>_httpd-max-open-files]: Count LegendI[<hostname>_httpd-max-open-files]: Count LegendO[<hostname>_httpd-max-open-files]: Count Legend1[<hostname>_httpd-max-open-files]: Apache Max OpenFiles Legend2[<hostname>_httpd-max-open-files]: Apache Max OpenFiles Legend3[<hostname>_httpd-max-open-files]: Apache Max OpenFiles Peak Legend4[<hostname>_httpd-max-open-files]: Apache Max OpenFiles Peak Title[<hostname>_httpd-max-open-files]: <hostname> Apache Max OpenFiles PageTop[<hostname>_httpd-max-open-files]: <H1><hostname> Apache Max OpenFiles</H1>
MRTGインデックスページを更新します。
# indexmaker /etc/mrtg/mrtg.cfg > /var/www/mrtg/index.html
MRTGページを確認します。
グラフは、2,3時間経過してからのほうが確認しやすいでしょう。
2-2. Nagiosの監視項目を追加
「Apacheプロセスが開いているファイルディスクリプタ数の最大値」をチェックし、しきい値を超えていれば、WaringまたはCriticalを通知するよう設定します。
Nagiosプラグインを作成し、Nagiosのコマンド定義とサーバー監視定義に追加します。
・Nagiosプラグインを作成
ちょっと工夫して、Apacheだけではなく、
「任意のプロセスが開いているファイルディスクリプタ数の最大値」
をチェックするプラグインスクリプトを作成します。
これは、Perplexity Pro(AI)に作成してもらったものをベースに、少しカスタマイズしました。
引数として、プロセス名、Warningしきい値、Criticalしきい値を与えるようにしています。
Nagiosプラグインは、引数として -w や -c など、ハイフンを使用するのが一般的ですが、自分しか使わない想定なので、横着して(笑)、ハイフンは省略しました。
# vim /usr/lib64/nagios/plugins/check_proc_max_open_files.sh
#!/bin/bash
# Nagiosプラグイン用 プロセス最大ファイルディスクリプタ数チェック
# base code by Perplexity Pro
# 引数の数チェック
if [ $# -ne 3 ];then
echo "Usage: ./check_proc_max_fd <process_name> <warn_threshold> <crit_threshold>";
exit 3
fi
# しきい値(警告・クリティカル)
#WARN_THRESHOLD=5000
WARN_THRESHOLD=$2
#CRIT_THRESHOLD=7000
CRIT_THRESHOLD=$3
# Apacheプロセス名(環境に応じてhttpdやapache2など変更)
#PROC_NAME="httpd"
PROC_NAME=$1
maxfd=0
maxpid=0
for pid in $(pgrep $PROC_NAME); do
# エラー出力は捨てる(プロセス終了等に備え)
fdcount=$(ls /proc/$pid/fd 2>/dev/null | wc -l)
if [ "$fdcount" -gt "$maxfd" ]; then
maxfd=$fdcount
maxpid=$pid
fi
done
# Nagios出力判定
if [ "$maxfd" -ge "$CRIT_THRESHOLD" ]; then
echo "CRITICAL - Max open files for $PROC_NAME PID $maxpid: $maxfd | max_open_files=$maxfd;$WARN_THRESHOLD;$CRIT_THRESHOLD;0;"
exit 2
elif [ "$maxfd" -ge "$WARN_THRESHOLD" ]; then
echo "WARNING - Max open files for $PROC_NAME PID $maxpid: $maxfd | max_open_files=$maxfd;$WARN_THRESHOLD;$CRIT_THRESHOLD;0;"
exit 1
else
echo "OK - Max open files for $PROC_NAME PID $maxpid: $maxfd | max_open_files=$maxfd;$WARN_THRESHOLD;$CRIT_THRESHOLD;0;"
exit 0
fi
# EOF
実行権限を付与します。
# chmod 755 /usr/lib64/nagios/plugins/check_proc_max_open_files.sh
動作確認のため、Nagiosプラグインスクリプトを実行し、ファイルディスクリプタ数を確認するコマンドの実行結果と比較して、正しく最大値が出力されることや、Warning、Criticalのしきい値を変更して、正しく、Warning、Criticalが判定されることを確認します。
# /usr/lib64/nagios/plugins/check_proc_max_open_files.sh Usage: ./check_proc_max_fd <process_name> <warn_threshold> <crit_threshold> # for pid in $(pgrep httpd); do echo "PID $pid: $(ls /proc/$pid/fd | wc -l)"; done PID 258540: 47 PID 258541: 45 PID 258542: 51 PID 258543: 51 # /usr/lib64/nagios/plugins/check_proc_max_open_files.sh httpd 1000 2000 OK - Max open files for httpd PID 258542: 51 | max_open_files=51;1000;2000;0; # /usr/lib64/nagios/plugins/check_proc_max_open_files.sh httpd 40 100 WARNING - Max open files for httpd PID 258542: 51 | max_open_files=51;40;100;0; # /usr/lib64/nagios/plugins/check_proc_max_open_files.sh httpd 30 45 CRITICAL - Max open files for httpd PID 258542: 51 | max_open_files=51;30;45;0;
↑問題なさそうです。
・sudo設定の追加
このサーバーでは、nagios.cfg で、Nagiosサービスの実行ユーザーは nagios ユーザーとしています。
# view /etc/nagios/nagios.cfg ... # NAGIOS USER # This determines the effective user that Nagios should run as. # You can either supply a username or a UID. nagios_user=nagios ...
一方、今回のNagiosプラグインスクリプトは、プロセスの情報を取得するため、root権限で実行する必要があります
nagios ユーザーがパスワードなしでroot権限でスクリプトを実行できるよう、sudo設定に追記します。
# visudo -- 末尾に追記 # Nagios Plugin nagios ALL=(ALL) NOPASSWD: /usr/lib64/nagios/plugins/check_proc_max_open_files.sh --
・Nagiosのコマンド定義を追加
check_proc_max_open_files コマンドを追加します。
# vim /etc/nagios/objects/commands.cfg
# 'check_proc_max_open_files' command definition
define command{
command_name check_proc_max_open_files
command_line /usr/bin/sudo $USER1$/check_proc_max_open_files.sh $ARG1$ $ARG2$ $ARG3$
}
・Nagiosのサーバー監視定義を追加
check_proc_max_open_files コマンドを使用して httpd プロセスのファイルディスクリプタ数(の最大値)を監視し、Warningしきい値は 900、Criticalしきい値は 1800 とします。
# vim /etc/nagios/servers/<hostname>.cfg
define service{
use hec-service
host_name <hostname>
service_description Apache Max OpenFiles
check_command check_proc_max_open_files!httpd!900!1800
}
・Nagiosを再起動して反映
# nagios -v /etc/nagios/nagios.cfg # systemctl restart nagios
Nagios画面にアクセスして確認します。
これで、Warningしきい値を超えたときに、Nagiosの contact で設定した宛先に、アラートメールで通知されます。
(余談)Apache mod_cgidのしくみ
この問題について調査する過程で、まつもとりーさんの以下の記事を見つけました。
・ApacheでCGIを使う場合にpreforkを使った方が良い状況とそのチューニングについて – 人間とウェブの未来
https://hb.matsumoto-r.jp/entry/2014/09/11/025533
mod_cgidはApache起動時にシングルスレッドのhttpdプロセスを起動させておき、CGIにリクエストがあった場合はリクエストを受けたhttpdプロセスとmod_cgid専用のシングルスレッドのhttpdプロセスがプロセス間通信を行い、実際のCGI実行はmod_cgid専用のプロセスで実行されます。通常このプロセスは一つです。
なんと、mod_cgidは、CGI処理専用のhttpdプロセスを1つ用意して、シングルスレッドで処理するんですね。
知らなかった。。
試してみると、Apacheを起動する際、mod_cgid をロードして起動したときは、ロードせずに起動したときときに比べてhttpdプロセスが1つ多く起動しています。
これは勉強になりました。
今回ファイルディスクリプタ数が上限に達したプロセスは、このCGI処理専用のhttpdプロセスだったのかもしれません。
Movable TypeのCGI処理については、以前PSGIサーバーの一種である Starman を導入したこともあるのですが、Movable Typeのアップグレード時にフリーズしてしまう問題が発生したため、mod_cgidに戻したことがありました。
preforkにするか、PSGIサーバーを導入するかなど、今後検討したいと思います。
おわりに
サーバーで発生した「ApacheでToo many open filesのエラー」問題についての原因調査と実施した対策、監視ツール設定などについて記載しました。
根本的な原因はわかっていないので、監視ツールでファイルディスクリプタ数の推移を注視し、数値が増えてきたときに詳しく調査したいと思います。


