ApacheでToo many open filesのエラー

はじめに

先日、僕がサーバー保守を担当しているサーバーの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. ファイルディスクリプタ数の上限値を増やす。
  2. 監視ツールで監視ツールでファイルディスクリプタ数を監視する

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のエラー」問題についての原因調査と実施した対策、監視ツール設定などについて記載しました。

根本的な原因はわかっていないので、監視ツールでファイルディスクリプタ数の推移を注視し、数値が増えてきたときに詳しく調査したいと思います。

タイトルとURLをコピーしました