logrotateを使う
logrotateはその名の通り、Linuxで汎用的なログのローテーションツールです。
似た名前でApacheに付属のrotatelogsというツールもあるので、違いについても少しだけ触れます。


ローテーション実装の流派
ログファイルのローテーションには、大きく二つの種類があります。 例えば、error_log0からerror_log6まで7世代のローテーションをする場合、
方式1: ローテーションのたび、error_log0から1,2,3,4,5,6,0,1…と最新のログを書き込むファイルが変わっていく方式
方式2: ローテーションのたび、error_log5⇒6、4⇒5に、3⇒4…と後ろにログをスライドしていき、最新のログは常にerror_log0ファイルに書き込む方式
logrotateは「方式2」を採用しています。 他にJavaのjava.util.logging.FileHandlerのlimitとcountパラメータを使ったローテーションもこの系統です。
一方「方式1」の例がApacheに付属のrotatelogsです。


logrotateのrotatelogsに対する比較
メリットとしては以下が挙げられます。

  • Linuxではシステムログ(/var/log/messagesやsecure)とアプリケーション(ApacheやTomcat) のログローテーションを同一の仕組みで管理できる。
  • Apacheに特化したrotatelogsに対して、Tomcat等様々なアプリケーションに適用でき、実績も多い。
  • 最新の書き込むログファイル名が常に固定である(方式2)のため、Amazon CloudWatch等外部のログ監視ツールと相性が良い。
  • リアルタイムではなく予め決めた時刻にしかローテーションしない代わり、 ログスイッチの時刻が予測できるため、ログスイッチの「瞬間」に関する誤動作や障害に遭遇しにくい。
一方で次のデメリットがあります。
  • アプリケーションのログ設定(Apacheのhttpd.confやTomcatのlogging.properties) とは別の場所(/etc/logrotate.d)に設定ファイルが分散する。
  • このページで後述する通り、SELinux環境でアクセス制限を通過するための設定が相当難しく、 各種のLinux管理コマンドの知識が必要。
  • Apacheのログファイルを管理したいだけなら、内蔵の仕組みで完結するrotatelogsに対して、 外部コマンドの実行や設定が必要な分管理が手間。


パッケージ経由で導入したApacheのローテーション設定
Apache(httpd)をパッケージ経由でLinuxに導入した場合、多くのディストリビューションでは自動的に /etc/logrotate.d/httpd というファイルが作られ、こんな感じで設定済になっています。

/var/log/httpd/*log {
    missingok
    notifempty
    sharedscripts
    delaycompress
    postrotate
        /bin/systemctl reload httpd.service > /dev/null 2>/dev/null || true
    endscript
}


Apacheのログにlogrotateを適用する
次に、自分でソース配布からインストールしたApacheに対する設定をしていきます。
Apacheのログといえば、ほとんどの場合 logsディレクトリ下のaccess_logとerror_logの二つを対象にすると思います。 多くのLinuxディストリビューションでは、logrotateは標準で導入され、設定ファイルは /etc/logrotate.dの下に書いて配置すれば動く前準備までは済んでいる状態になっています。

ここでは、/usr/local/apache2にインストールしたApache2.4のログに対する設定を例として、 /etc/logrotate.d/apache2 というファイルに次のように記述してみます。

/usr/local/apache2/logs/*log {
    daily
    rotate 14
    missingok
    notifempty
    nodateext
    compress
    delaycompress
    sharedscripts
    postrotate
        /usr/bin/killall -USR1 httpd > /dev/null 2>&1 || true
    endscript
}

最初は今すぐローテーションの動作を確認したいと思って、dailyをhourlyなどに変更したいと思うかもしれません。
もちろんそれでも良いのですが、間隔や条件に関わらず強制的にログローテーションを発生させるコマンドもあるので、 最終的にdailyで運用したいならdailyで良いと思います。
なお、Apacheはそのままだとローテーション後も古いログに書き続けるため、 新しいログに書くよう変更するのにリロードが必須です。
この例ではApacheをsystemdサービスに登録していない想定で、 USR1シグナルを使ってリロードしていますが、systemd登録済ならば次のようになると思います。

/bin/systemctl reload apache2 > /dev/null 2>/dev/null || true

なお、RHEL等でSELinux環境下の場合、リロード操作にapachectl graceful等のapachectlコマンドを使うとエラーになることがあります。 詳しくは後述します。


主な設定の意味
パラメータ意味
daily毎日一度実行する
rotate {N}{N}世代を保持する
missingokログファイルがなくてもエラーにしない
notifemptyログファイルが空ならローテーションしない
dateext古いログファイル名を error_log-{YYYYMMDD}の形式にする
nodateext古いログファイル名を error_log.1、.2…の形式にする
compressローテーションしたファイルをgzip圧縮する
delaycompress1世代目のファイルは圧縮せず、2世代目以前を圧縮する
prerotateローテーション前に実行する処理をendscriptまでの間に記述
postrotateローテーション後に実行する処理をendscriptまでの間に記述
sharedscripts複数のログファイルをローテーションした場合にも、prerotateやpostrotate内に記述した処理は一度だけ実行する
copytruncateログファイルを改名ではなく、コピー+既存ファイルサイズを0に切り詰める方式で行う
create {perm} {user} {group}ローテーション後に指定した所有者、グループ、パーミッションで明示的に空のファイルを作成する
su {user} {group}ローテーションを指定したユーザーとグループで実行する


試験実行

# 設定ファイルの構文チェックと動作確認(実際にはローテーションしない)
sudo logrotate -d /etc/logrotate.conf

# 特定の設定ファイルのみをテスト(実際にはローテーションしない)
sudo logrotate -d /etc/logrotate.d/apache2

# 通常手動実行(条件を満たせばローテーションされる)
sudo logrotate /etc/logrotate.conf

# 強制手動実行(条件に関係なくローテーションされる)
sudo logrotate -v -f /etc/logrotate.d/apache2
ls -l /usr/local/apache2/logs

-dを付けるとデバッグモードとなり、実行する処理をシミュレートし、エラーがあれば教えてくれます。
-vを付けるとverboseの意味で詳しい処理内容が出力されます。
-fを付けると、条件を見たさなくても強制的にローテーションの処理が行われます。


自動実行機構について
/etc/logrotate.d/apache2 を正しい文法で書き、その中にdailyと書けば、翌日から1日置きに自動的にローテーションを行ってくれるのは、 どういう機構で実現しているのでしょうか?
多くのLinuxディストリビューションでは、crondというデーモンが動いていて、 このデーモンが1日1回 /etc/cron.d/daily/logrotate というファイルを実行するので、 その中から /etc/logrotate.d/apache2 の設定も読まれてローテーションが掛かる仕組みになっています。 ところが、RHEL9やその一族であるRocky Linux9などでは、初期状態では /etc/cron.d/daily/logrotate というファイルはありません。いったいなぜ?
RHELやRocky Linuxでは、cronの代わりにsystemd-timerという仕組みで自動実行を実現しています。 systemd-timerにlogrotate.timerが設定されているかどうかは、次のコマンドで分かります。

systemctl status logrotate.timer

● logrotate.timer - Daily rotation of log files
     Loaded: loaded (/usr/lib/systemd/system/logrotate.timer; enabled; preset: enabled)
     Active: active (waiting) since Sat 2024-08-03 19:54:04 JST; 11 months 22 days ago
      Until: Sat 2024-08-03 19:54:04 JST; 11 months 22 days ago
    Trigger: Sun 2025-07-27 00:00:00 JST; 3h 34min left
   Triggers: ● logrotate.service
       Docs: man:logrotate(8)
             man:logrotate.conf(5)

このようにactiveと出てくれば実行が設定されています。inactiveだと設定がされていないことになります。
設定内容は、/usr/lib/systemd/system にあるlogrotate.service と logrotate.timer という二つのファイルで確認できます。 logrotate.timerのタイマー設定に応じて、logrotate.service で定義した処理が実行されます。 この後者のファイルに

[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.conf

とコマンドラインが書かれています。
さらに、以下のコマンドで前者のタイマー設定を見てみます。

systemctl cat logrotate.timer

# /usr/lib/systemd/system/logrotate.timer
[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)

[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true

[Install]
WantedBy=timers.target

と「OnCalendar=daily」と出てくれば、日次のローテーションが有効になっている意味です。 dailyは、00:00を指します。先のsystemctl statusでも、 「Trigger: (中略) 00:00:00 JST」と「次回の実行は翌日の00:00」となっていましたね。
もし、0時ではなく5時に実行したければ、systemdの設定を次のように変更します。

##OnCalendar=daily
OnCalendar=*-*-* 05:00:00

また、00:00にマシンが停止していた場合はどうなるでしょう? 「Persistent=true」の設定により、マシンが起動した直後に「遅延実行」されます。 もし時刻にマシンが停止していた場合は起動後も実行しない方が良ければ、 systemdの設定で上記を「Persistent=false」に変更すればOKです。


ローテーション履歴の保存場所
/var/lib/logrotate/logrotate.status というファイルに、各ログの最後のローテーション実行日時が記録されています。
これを見ると、/var/log/secureや/var/log/messagesも同じ仕組みでローテーションされていることが分かりますね。
なお、上記の「sudo logrotate /etc/logrotate.conf」でローテーションを「通常手動実行」すると、 /var/lib/logrotate/logrotate.status の日時は更新されますが、 「sudo logrotate -f /etc/logrotate.d/apache2」で「強制手動実行」した場合は更新されません。
この使い分けによって、翌日0時のローテーションが行われるかどうかが変わってきます。


ローテーションされないぞ?(落とし穴多数)
設定が終わって、いざ実行…と真夜中0:00を待機しても…ローテーションが行われない!?
しかも /var/lib/logrotate/logrotate.status の日付は最新に更新されています。いったい、なぜ?
初心者がはまった落とし穴の内、まず、RHELやRocky Linuxの一族に限らず起きうるものから挙げていきましょう。

落とし穴1. logrotate.statusが変わってもローテーションされたとは限らない
まず最初。logrotate.statusに記録される日付は、最後にlogrotateが実行された日付だというだけで、 そのログファイルが実際にローテーションされる条件を満たしていたかどうかは別の話です。 つまり、そのログファイルがローテーションされてもされなくても、ファイルの日付は最新になってしまいます。

落とし穴2. ログファイルが更新されないとローテーション対象にならない
前回ローテーション後にログが追記されていない場合、ローテーション対象になりません。
つまり、前回logrotateが実行されたときにローテーションの条件を満たさずに対象外となったログファイルに対して、 設定の側だけを変えて次回ローテーションの条件を満たすようにしたとしても、 ログファイルに前回以降全く追記がされていないとしたら、いくら待ってもローテーション対象にはなりません。

落とし穴3. 手動実行したら2日待たないといけない
dailyの場合、ある日の日中にlogrotateを手動実行したら、翌日の0:00に自動でローテーションされるでしょうか?
答はされません。翌日の0:00だとまだ24時間経っていないです。 手動実行したら、その翌々日の0:00から自動実行の対象になります。 したがって問題解決を焦って毎日毎日手動実行していると、いつまで経っても自動実行できたかの確認ができません。

次に、ここからはRHELやRocky Linux固有の落とし穴になります。

落とし穴4. hourlyと書くだけでは毎時実行されない
問題解決を焦ってdailyをhourlyに変えて1時間後、様子を見たら…あれ、ローテーションされてない。
logrotateはrotatelogsと異なり、条件を満たすと即座にローテーションが掛かるわけではなく、 logrotateが実行されないと処理が行われません。その実行間隔はlogrotate.timerで設定します。 つまりそちらが「OnCalendar=daily」だと実行もやっぱりdailyになります。 logrotate.timerの設定をhourlyにすればhourlyで実行されます。

これでも解決しない場合、次のコマンドでエラーが出ているか確認しましょう。

journalctl -u logrotate

落とし穴5. Read-only file system のエラー
Read-only file system というエラーは、ログファイルが/usrなどに置かれている場合によく出ます。 logrotate.serviceに

ProtectSystem=full

という行があれば、これはlogrotate実行時に他のディレクトリへの読み書きを遮断する意味の設定になります。 その対象に/usrも含まれているため、これを「除外」するために次のように変更する必要があります。

ProtectSystem=full
ReadWritePaths=/usr/local/apache2/logs

なお、複数のパスの指定が必要な場合は空白を挟んで列挙すれば良いようです(未試験)。
ちなみにlogrotate.serviceに限りませんが、RHEL系の通常の運用として、 /usr/lib/systemd/systemのファイルは直接編集せず、/etc/systemd/systemにコピーしてそちらを編集します。 従って運用に即した手順としてはこうなります。

sudo cp -i /usr/lib/systemd/system/logrotate.service /etc/systemd/system/.
sudo nano /etc/systemd/system/logrotate.service
------
ProtectSystem=full
ReadWritePaths=/usr/local/apache2/logs
------
sudo systemctl daemon-reload
sudo systemctl restart logrotate.service

落とし穴6. Permission denied のエラー
Permission deniedのエラーが出ている場合、多くの場合SELinuxでやられていると思います。

sudo ausearch -m avc

を実行して、「avc: denied { read } 」みたいな行が出ていれば当たりです。
実は、ログファイルのタイプ(ラベル)が var_log_t という物でないとsystemd.timerが処理できないことがあります。 ls -lZコマンドなどでログファイルを表示して、usr_tとかuser_home_tとかだと多分 Permission denied になります。

sudo semanage fcontext -a -t var_log_t "/usr/local/apache2/logs(/.*)?"
sudo semanage fcontext -a -t var_log_t /usr/local/apache2/logs
sudo restorecon -Rv /usr/local/apache2/logs
ls -lZ /usr/local/apache2/logs

というコマンドで var_log_t に変われば良いですが、変わらない場合…
実は、落とし穴の中に落とし穴があります。つまり落とし穴 in 落とし穴です。 SELinuxのsemanageとかrestoreconといったコマンドは、物理的なファイルシステム上のパスだけを処理対象にするため、 指定したパスにシンボリックリンクが含まれると黙って無視されてしまうことがあります。 具体的には、

/usr/local/apache2-2.4.xx <--- /usr/local/apache2

というリンクが張られている場合、「/usr/local/apache2/logs」ではだめで、次のように指定しないといけません。

sudo semanage fcontext -a -t var_log_t "/usr/local/apache2-2.4.xx/logs(/.*)?"
sudo semanage fcontext -a -t var_log_t /usr/local/apache2-2.4.xx/logs
sudo restorecon -Rv /usr/local/apache2-2.4.xx/logs
ls -lZ /usr/local/apache2/logs


ローテーションはされるが…
落とし穴7. 古いログファイルの形式が思ったのと違う…
設定ファイルで記述を省略した場合、その設定項目は/etc/logrotate.confの内容を引き継ぎます。
多くのディストリビューションでは、/etc/logrotate.confに「dateext」と書かれています。
つまり、設定にdateextもnodateextも書かない場合、dateextになってしまいます。
error_log.1、.2…の形式にしたい場合は必ず「nodateext」を書く必要があります。

落とし穴8. Apacheのログファイルが古い方(error_log.1)に書かれてしまう
error_logはサイズ0で作られたのに、最新のログはなぜか古いerror_log.1に書かれてしまう…
おぬしもしや、RHEL系のOSで、systemd reloadやkillでUSR1シグナルを送る方法ではなく、 apachectl gracefulやapachectl restartでリロードしておりませんかな?
例によってSELinuxの制限で止められているかを確認して、

sudo ausearch -m avc

次のようなログが出ていたらアウトです。

type=AVC msg=audit(1753887604.802:160599): avc:  denied  { read } for  pid=2318095 comm="httpd" name="libphp.so" dev="sda2"
 ino=23360088 scontext=system_u:system_r:logrotate_t:s0 tcontext=system_u:object_r:httpd_modules_t:s0 tclass=file permissive=0

SELinux的な説明だと、logrotateプロセス(logrotate_tコンテキスト)からApacheを再起動する際に、 Apacheのモジュール(httpd_modules_tコンテキスト)へのアクセスが拒否されています。
この厄介君を回避したければ、最初の例のように

postrotate
    /usr/bin/killall -USR1 httpd > /dev/null 2>&1 || true
endscript

とします。このコマンドは既に起動済みのプロセスにシグナルを送るだけなので、 apachectlプロセスがhttpdプロセスを起動する過程がないため、この制限を受けないで処理されます。
ちなみに、あるプロセスが開いているファイルの一覧を確認するには「lsof」というコマンドが便利です (入ってない場合、dnf等でインストールする必要があります)。 最も簡単には

sudo lsof -p {プロセスID}

のようにプロセスを指定して実行しますが、

sudo lsof -u tomcat
sudo lsof -u root | grep apache2 | grep log

このように-uオプションを使うと、その実行ユーザーのプロセスを全て対象にして表示してくれます。


Tomcatのログにlogrotateを適用する
Tomcatの方はApacheよりもはるかに手順が複雑になります。理由は

  • Tomcatのログは catalina.out、catalina***.log、localhost***.log、manager***.log、host-manager***.log、localhost_access_log***.txt の6種類と数が多いこと
  • それらのログの設定個所が複数に分かれていること
  • その幾つかは、デフォルトでTomcatがYYYY-MM-DDの日付を付けてせっかくローテーションをしてくれているのを、 敢えてやめさせる必要があること
の3点があります。では、順にいきましょう。

[1] catalina*.log等のローテーションを止める
まず、$CATALINA_HOME/conf/logging.propertiesの各FileHandlerに

1catalina.org.apache.juli.AsyncFileHandler.rotatable = false
2localhost.org.apache.juli.AsyncFileHandler.rotatable = false
3manager.org.apache.juli.AsyncFileHandler.rotatable = false
4host-manager.org.apache.juli.AsyncFileHandler.rotatable = false

を追記して、catalina***.log、localhost***.logなどのファイル名にYYYY-MM-DDがつくのを止めます。
またmanager.log、host-manager.logの中身を見る機会はあまりないと思うので、コメントにしてもいいかもしれません。

[2] localhost_access_log***.txtのローテーションも止める
これは $CATALINA_HOME/conf/server.xmlで変更します(4行目)

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b"
               rotatable="false" />

[3] /etc/logrotate.d/tomcatを作る

/opt/tomcat11/logs/catalina.out
/opt/tomcat11/logs/catalina.log
/opt/tomcat11/logs/localhost.log
/opt/tomcat11/logs/localhost_access_log.txt
{
    copytruncate
    daily
    rotate 14
    missingok
    notifempty
    nodateext
    compress
    delaycompress
    create 0644 tomcat11 tomcat
    su tomcat11 tomcat
}

Tomcatプロセスは自力でファイルハンドルを閉じないため、多くの場合copytruncateが必要です。 またApacheの場合はデフォルトでroot自身がログを書き込むのに対して、 Tomcatは実行ユーザーがログも書く(そしてほとんどの場合、Tomcatはrootでは実行しない) ため、ログファイルの所有者とパーミッションをcreateで指定することが多いでしょう。 さらに書き込む場所によってはlogrotateをrootで実行するとエラーになることがあるので、 ローテーションを実行するユーザーもsuで指定するのが安全とされています。

Misc. Topics Top

(first uploaded 2025/07/26 last updated 2025/08/01, URANO398)