【検証】Linuxのulimit設定が反映されない!? 理由を「起動経路」から解き明かす


Linuxサーバーの運用で「/etc/security/limits.conf を書き換えたのに実は反映されていなかった…」という経験はありませんか? 「確かにulimit -nで確認していたはずなのに、なぜ!?」
このような状態は、すぐにではなく時間がたって忘れたころに問題として現れます。。

今回、実際に詳細な比較検証を通じて「なぜ limits.conf だけでは不十分だったのか」そして「どうすれば全経路で確実に設定を反映できるのか」を3つのステップで解明しました。
これを機にあいまいな理解を整理して同じ失敗が起きないようにしておきましょう。

検証の準備:使用したファイル一覧

今回の検証では、以下のファイルを作成・編集して挙動を確認しました。(rootユーザー)

・/usr/local/bin/sample_app.sh: 現在の ulimit 値をログ出力するスクリプト
・/etc/security/limits.conf: 制限値を定義するファイル
・/etc/cron.d/test_cron: system cronの設定ファイル
・crontab -e:ユーザー用cronの設定
・/etc/systemd/system/service_manual_app.service: 手動で起動コマンド(systemctl start)を叩いて検証するためのサービスファイル
・/etc/systemd/system/service_crontab_app.service: ユーザー用cron(crontab)から起動される検証用サービスファイル
・/etc/systemd/system/service_crond_app.service: システム用cron(/etc/cron.d/)から起動される検証用サービスファイル

コマンド実行+ログ出力用スクリプトの内容

すべての実行経路で、このスクリプトを呼び出して ulimit 値をログファイルに記録します。

今回は以下の6つのパターンで検証します。

<手動:直接実行>
/usr/local/bin/sample_app.sh
<手動:サービス起動>
systemctl start service_manual_app
<crontab起動:直接実行>
* * * * * /usr/local/bin/sample_app.sh CronTab
<crontab起動:サービス起動>
* * * * * /usr/bin/systemctl start service_crontab_app.service
<cron.d起動:直接実行>
* * * * * root /usr/local/bin/sample_app.sh CronD
<cron.d起動:サービス起動>
* * * * * root /usr/bin/systemctl start service_crond_app.service

【STEP 1】「limits.conf」だけを設定した場合の挙動

まずは、多くの方が最初に行う「limits.conf の修正」のみを実施し、どこまで設定が反映されるかを確認します。

設定内容

/etc/security/limits.conf に以下の設定を追記。

検証結果(第1フェーズ)

それぞれ実行したところ、以下の結果になりました。

実行経路(ログ出力内容) 実行方法の詳細 ulimit 結果 判定
Manual SSHログイン後、sample_app.shを手動で直接実行 65535 〇 反映
CronTab crontab(ユーザー用cron)から直接実行 65535 〇 反映
CronD /etc/cron.d/(システム用cron)から直接実行 1024 × デフォルト値
Service-via-Manual 手動で systemctl start を実行しサービスを起動 1024 × デフォルト値
Service-via-CronTab crontab から systemctl start を呼び出し 1024 × デフォルト値
Service-via-CronD cron.d から systemctl start を呼び出し 1024 × デフォルト値

手動実行とcrontab 以外はシステムデフォルト値(1024)のままになっています。

【実測ログ】

つまり、「cron.dから起動」または「サービス起動」の場合は、limits.confは反映されないということになります。

なぜこうなるのか?:PAM(Pluggable Authentication Modules)の仕組み

この差を生んでいるのは、Linuxの認証、アカウント、セッション、パスワード変更などを一元管理するフレームワークである PAM(Pluggable Authentication Modules) の仕組みです。

PAMは、ユーザーのログインやセッション開始時に、認証だけでなく「環境のセットアップ(リソース制限の適用など)」を動的に行う機能を持っています。/etc/security/limits.conf の設定値は、PAMモジュールのひとつである pam_limits.so が読み込むことでプロセスに反映されます。

    • 反映される経路: SSHログインや crontabは、実行時にPAMのセッションスタック(/etc/pam.d/sshd や /etc/pam.d/crond)を通過するため、limits.conf が読み込まれます。
    • 反映されない経路: systemd(サービス)や /etc/cron.d は、OS起動時やサービスマネージャーのプロセスから直接フォークされるため、PAMによるセッション初期化プロセスをスキップすることがあります。その結果、limits.conf は参照されず、親プロセスの制限値がそのまま引き継がれます。

【STEP 2】個別サービスの「LimitNOFILE」を有効化する

次に、反映されなかった「サービス起動」への対策として、各サービス定義ファイルに直接制限値を記述します。

※LimitNOFILE は、Linuxにおいて「1つのプロセスが同時に開くことができるファイルの最大数(ファイルディスクリプタ数)」を制限するための設定項目です。 主に systemd のユニットファイル(.service ファイル)内で、特定のサービスやデーモンに対して制限をかける際に使用されます。

設定内容

各ユニットファイル(service_manual_app.service 等)に LimitNOFILE を追記。

検証結果(第2フェーズ)

実行したところ、以下の結果になりました。

実行経路(ログ出力内容) ulimit 結果 採用された設定
Manual 65535 limits.conf
CronTab 65535 limits.conf
Service-via-Manual 1048576 LimitNOFILE (反映)
Service-via-CronTab 1048576 LimitNOFILE (反映)
Service-via-CronD 1048576 LimitNOFILE (反映)
CronD 1024 × 依然デフォルト値

サービス経由の起動はすべて、指定した 1048576 へと反映されました。

【実測ログ】

つまり、サービス起動の場合は、各ユニットファイルに LimitNOFILE を追記しておく必要があるということになります。

判明したこと

systemctl を介して起動するものは、呼び出し元がどこであっても、サービス定義内の LimitNOFILE が絶対的な基準として適用されます。

しかし、依然として 「cron.d から直接プログラムを叩く(CronD)」 パターンだけが 1024 のまま取り残されています。

【STEP 3】「cron.d」直接実行をどう救うか?

最後に、残っている cron.d 直接実行への対策です。 これには「コマンドラインに ulimit を書く」方法と、「親プロセスである crond 自体の制限を引き上げる(systemctl edit)」方法の2通りが考えられますが、今回はより直接的な 「コマンドラインへの追記」 で検証しました。

設定内容

/etc/cron.d/test_cron の記述を、コマンド実行直前に ulimit を指定する形に変更。

検証結果(最終フェーズ)

実行したところ、以下の結果になりました。

実行経路(ログ出力内容) ulimit 結果 判定
CronD 65535 〇 改善 (反映)
Service-via-CronD 1048576 〇 反映済み
Service-via-CronTab 1048576 〇 反映済み
CronTab 65535 〇 反映済み

ようやく、すべての経路でデフォルト値(1024)からの脱却に成功しました。

【実測ログ】

まとめ:最終的な「反映パターン」を整理

今回の検証で明らかになった最終的な結果です。

      • 手動(直接実行):limits.confが反映される
      • 手動(サービス起動): limits.confは反映されない。LimitNOFILEでの指定が必要
      • crontab起動(直接実行):limits.confが反映される
      • crontab起動(サービス起動):limits.confは反映されない。LimitNOFILEでの指定が必要
      • cron.d起動(直接実行):limits.confは反映されない。ulimitコマンドの併用やcrond 自身の制限変更が必要
      • cron.d起動(サービス起動):limits.confは反映されない。LimitNOFILEでの指定が必要

※補足:cronの@reboot指定も、この「起動経路」の仕組みに従います。そのため、記述箇所が crontab -e か /etc/cron.d/ かによって挙動が決まります。

以上の結果を踏まえて、今後意識しておきたいポイント

      • 「limits.conf を書いたから安心」は禁物
        特にシステムサービスや cron.d はPAMによる制限適用の対象外となるケースが多いと認識しておく。
      • サービスには「LimitNOFILE」をセットで考える
        systemd で管理するサービスに制限をかける場合は、ユニットファイルに直接 LimitNOFILE を記載する。
      • 確認は「本番と同じ起動経路」で
        ログイン後の ulimit -n は、あくまで「確認に使用したシェル環境」における結果です。プログラムが実際に起動される経路(cronやsystemdなど)で個別にログを出力させ、設定が意図通りに反映されているかを確実にチェックする。

設定漏れを防ぐチェックリスト

      • [ ] そのプログラムは systemctl で起動されるか?
        → 起動されるなら、必要に応じて LimitNOFILE を設定する。
      • [ ] cronは cron.d に書いていないか?
        → 書いているなら limits.conf は反映されない可能性が高い。
      • [ ] 手動確認だけで終わっていないか?
        → ログインシェル経由以外の経路(実際の運用環境)で必ず確認する。

「そのプロセスがどの経路を通って起動されるか」を見極め、設定値を「どこに書くか」を正しく決めることが、トラブルを未然に防ぐ鍵になります!

※本検証は RHEL 9.7 環境で実施したものです。Linuxのディストリビューション(CentOS, Ubuntu, Debian等)やそのバージョン、あるいはシステム全体のPAM設定、systemdの仕様変更、セキュリティポリシー(SELinux等)といった個別の環境要因により、挙動や結果が異なる場合があります。実際に設定を行う際は、ご自身の環境において、意図した通りに制限値が反映されているかを必ず事前にご確認ください。

 
 
 
エコモットでは、ともに未来の常識を創る仲間を募集しています。
IoTに少しでも興味がある方はぜひ下記の採用ページをご覧ください!