- 技術 💻
logrotateの設定方法|コピペ用テンプレートとディレクティブ一覧
logrotateとは、大きくなり続けるログファイルを「日付やサイズの区切りで別名に移し、古いものを圧縮・削除する」Linux標準のツールです。 設定は /etc/logrotate.d/ に1ファイル置くだけで、多くのディストリビューションでは1日1回、cron または systemd timer から自動で呼ばれます。
この記事では、ディレクティブの早見表と、そのまま貼れる設定テンプレート(nginx・Node.js・Java・Python・独自ログ)をまとめました。さらに、Ubuntu 24.04 の logrotate 3.21.0 を使って、-d(ドライラン)・-f(強制実行)・-s(状態ファイル指定)を実際に動かし、ログを書き続けるプロセスに対して copytruncate と create+postrotate を試したときに「何行欠けたか」を数えた実測結果を載せています。よく出るエラー(parent directory has insecure permissions など)も実際に再現して、直し方と一緒に示します。
logrotateディレクティブの早見表
設定ファイルの中で使う主なディレクティブ(命令)です。迷ったら「daily / rotate / missingok / notifempty / compress / delaycompress / create」の7つから始めると、ほとんどのログに対応できます。
| ディレクティブ | 書き方の例 | 意味 | 使いどころ |
|---|---|---|---|
| daily / weekly / monthly / yearly | daily |
日・週・月・年ごとにローテート | 時間で区切る基本設定 |
| hourly | hourly |
1時間ごと(logrotate自体を毎時実行する必要あり) | 大量に出るログ |
| rotate | rotate 14 |
残す世代数。超えた分は削除 | 保存期間=間隔×世代数 |
| size | size 100M |
サイズが超えたらローテート(時間指定より優先) | サイズだけで区切りたい |
| maxsize | maxsize 500M |
時間が来ていなくても、このサイズを超えたらローテート | dailyと併用で急増に備える |
| minsize | minsize 1M |
時間が来て、かつこのサイズ以上ならローテート | 小さいログを細切れにしない |
| compress | compress |
古い世代をgzip圧縮 | ディスク節約 |
| delaycompress | delaycompress |
直前の世代(.1)だけ圧縮を次回に回す | 書き込み中のプロセスがある時 |
| missingok | missingok |
ログが無くてもエラーにしない | ほぼ常に付ける |
| notifempty | notifempty |
空のログはローテートしない | 空ファイルの世代を増やさない |
| create | create 0640 www-data adm |
移動後に同名の新しい空ファイルを作る(権限・所有者) | 標準の方式 |
| copytruncate | copytruncate |
コピーしてから元ファイルを0バイトに切り詰める | ログを開き直せないアプリ |
| dateext | dateext |
世代番号ではなく日付を付ける(app.log-20261001) | 日付で探したい |
| dateformat | dateformat -%Y%m%d-%H%M%S |
dateextの書式。1日に複数回ならHMSを付ける | hourly・size併用時 |
| postrotate 〜 endscript | 下のテンプレート参照 | ローテート後に実行するシェル | シグナルでログを開き直させる |
| prerotate 〜 endscript | 同上 | ローテート前に実行するシェル | 事前処理 |
| sharedscripts | sharedscripts |
ワイルドカードで複数ログが対象でも、スクリプトを1回だけ実行 | *.log 指定時 |
| su | su appuser appgroup |
指定ユーザー・グループの権限でローテート | 親ディレクトリがroot以外書き込み可の時 |
| olddir | olddir /var/log/app/archive |
古い世代を別ディレクトリへ | ログ置き場を整理 |
| createolddir | createolddir 0750 root root |
olddirが無ければ作る | olddirとセット |
| maxage | maxage 30 |
ローテート済みで指定日数より古いものを削除 | 世代数と日数の両方で管理 |
| include | include /etc/logrotate.d |
別ファイル・ディレクトリを読み込む | logrotate.confで使用 |
保存期間は「間隔 × rotate」で決まります。daily + rotate 14 なら約2週間、weekly + rotate 4 なら約1か月分が残ります。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「logrotateの設定方法|コピペ用テンプレートとディレクティブ一覧」
logrotateをインストールして既定の設定を確認する
インストールとバージョン確認
Ubuntu / Debian では apt で入ります(多くの環境では最初から入っています)。
sudo apt-get install -y logrotate
logrotate --version
実行結果(Ubuntu 24.04.4 LTS):
logrotate 3.21.0
...
Default compress command: /bin/gzip
Default state file path: /var/lib/logrotate/status
「Default state file path」が状態ファイルの場所です。logrotate は「各ログを最後にいつローテートしたか」をここに記録し、次回の判定に使います。
設定ファイルの構成
| ファイル | 役割 |
|---|---|
/etc/logrotate.conf |
全体の既定値。最後に include /etc/logrotate.d で個別設定を読む |
/etc/logrotate.d/* |
ログごとの設定。自分で追加するのはここ |
/var/lib/logrotate/status |
状態ファイル(最終ローテート日時) |
Ubuntu 24.04 の /etc/logrotate.conf(コメントと空行を除く):
weekly
su root adm
rotate 4
create
include /etc/logrotate.d
個別ファイルに書かなかった項目は、この既定値(weekly・rotate 4・create)が使われます。 個別設定で daily を書けば、そのログだけ daily になります。
設定ファイルの書き方(基本形)
/var/log/myapp/app.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
create 0640 appuser appgroup
}
1行目に対象のパス(ワイルドカード・複数可)、{ } の中にディレクティブを1行1つ書きます。設定ファイルは root 所有・644 にします(緩い権限だと読み飛ばされます。後述)。
-d・-f・-sで動作確認する手順
本番のログで試す前に、作業用ディレクトリのダミーログで動きを確かめるのがおすすめです。ここでは /tmp/t89lr を作業場所として使いました。
準備:ダミーログと設定ファイル
mkdir -p /tmp/t89lr/logs && cd /tmp/t89lr
for i in $(seq 1 200); do echo "2026-10-01T10:00:00 INFO order id=$i created"; done > logs/app.log
cat > app.conf <<'EOF'
/tmp/t89lr/logs/app.log {
daily
rotate 3
missingok
notifempty
compress
delaycompress
create 0640 root root
}
EOF
-s で状態ファイルを作業場所に指定すると、システムの /var/lib/logrotate/status を書き換えずに試せます。
-d(ドライラン):何も変更せずに判定だけ見る
logrotate -d -s /tmp/t89lr/status /tmp/t89lr/app.conf
実行結果:
warning: logrotate in debug mode does nothing except printing debug messages! Consider using verbose mode (-v) instead if this is not what you want.
reading config file /tmp/t89lr/app.conf
Reading state from file: /tmp/t89lr/status
state file /tmp/t89lr/status does not exist
...
rotating pattern: /tmp/t89lr/logs/app.log after 1 days (3 rotations)
empty log files are not rotated, old logs are removed
considering log /tmp/t89lr/logs/app.log
Creating new state
Now: 2026-10-01 22:52
Last rotated at 2026-10-01 22:00
log does not need rotating (log has already been rotated)
ここがつまずきやすいポイントです。状態ファイルにまだ記録が無いログは、初回は「今ローテートした」扱いで記録だけされ、実際にはローテートされません。daily を入れた当日に何も起きないのはこのためです。続けて -v(詳細表示つきの通常実行)で実行すると、状態ファイルに "/tmp/t89lr/logs/app.log" 2026-10-1-22:0:0 と記録が作られ、翌日以降の実行から動き始めます。
-f(強制):時間を待たずにローテートする
-f は「時間やサイズの条件を無視して今すぐローテート」します。5回続けて実行し、ファイルの変化を ls -l で追いました。
for n in 1 2 3 4 5; do
echo "== force $n =="
echo "line from round $n" >> logs/app.log
logrotate -f -s /tmp/t89lr/status /tmp/t89lr/app.conf
ls -l logs | tail -n +2
done
実行結果(5回目は4回目と同じ並びのため省略):
== force 1 ==
-rw-r----- 1 root root 0 Oct 1 22:52 app.log
-rw-r--r-- 1 root root 9110 Oct 1 22:52 app.log.1
== force 2 ==
-rw-r----- 1 root root 0 Oct 1 22:52 app.log
-rw-r----- 1 root root 18 Oct 1 22:52 app.log.1
-rw-r--r-- 1 root root 575 Oct 1 22:52 app.log.2.gz
== force 3 ==
-rw-r----- 1 root root 0 Oct 1 22:52 app.log
-rw-r----- 1 root root 18 Oct 1 22:52 app.log.1
-rw-r----- 1 root root 38 Oct 1 22:52 app.log.2.gz
-rw-r--r-- 1 root root 575 Oct 1 22:52 app.log.3.gz
== force 4 ==
-rw-r----- 1 root root 0 Oct 1 22:52 app.log
-rw-r----- 1 root root 18 Oct 1 22:52 app.log.1
-rw-r----- 1 root root 38 Oct 1 22:52 app.log.2.gz
-rw-r----- 1 root root 38 Oct 1 22:52 app.log.3.gz
結果から読み取れること:
| 観察 | 理由 |
|---|---|
毎回 app.log が0バイトで新しく作られ、権限が -rw-r----- |
create 0640 root root |
.1 は圧縮されず、.2 以降が .gz |
delaycompress(直前の世代だけ圧縮を1回遅らせる) |
| 4回目以降、9110バイトの最初のログ(575バイトの.gz)が消えた | rotate 3 で4世代目が削除された |
-d と -f を組み合わせる:強制した場合の手順を確認
-d -f にすると「強制したら何をするか」を変更なしで表示できます。postrotate を含む設定で試すと、次のようにリネームの順番とスクリプトの中身まで出ます(抜粋)。本番に入れる前の最終チェックに便利です。
rotating pattern: /tmp/t89lr/dd/app.log forced from command line (3 rotations)
...
renaming /tmp/t89lr/dd/app.log to /tmp/t89lr/dd/app.log.1
creating new /tmp/t89lr/dd/app.log mode = 0640 uid = 0 gid = 0
running postrotate script
running script with args /tmp/t89lr/dd/app.log /tmp/t89lr/dd/app.log.1: "
echo reopen
"
size:サイズで区切る
size 1k を指定し、2048バイトのログと6バイトのログを置いて -v で実行しました。
considering log /tmp/t89lr/sz/big.log
log needs rotating
considering log /tmp/t89lr/sz/small.log
log does not need rotating (log size is below the 'size' threshold)
size を書くと時間間隔は見ずにサイズだけで判定します。「毎日、ただし大きくなったら途中でも」としたい場合は daily + maxsize を使います。なお、logrotate 自体が1日1回しか動かない環境では、サイズ判定も1日1回です。
| やりたいこと | 書き方 |
|---|---|
| サイズだけで区切る | size 100M |
| 毎日。ただし500MBを超えたらその時点で | daily + maxsize 500M(logrotateを毎時実行) |
| 毎日。ただし1MB未満なら見送る | daily + minsize 1M |
copytruncateとcreate+postrotateの違いを実測
ローテートには大きく2つの方式があります。
| 方式 | 動き | アプリ側に必要なこと |
|---|---|---|
| create(既定) | 元ファイルを app.log.1 にリネーム → 新しい app.log を作成 |
ローテート後にログを開き直す(シグナル等で) |
| copytruncate | 元ファイルを app.log.1 にコピー → 元ファイルを0バイトに切り詰め |
何もしなくてよい |
Linux では、プロセスはファイルを「名前」ではなく開いたときの実体(inode)で持ち続けます。create方式でリネームしただけだと、アプリは app.log.1 に書き続けます。 そこで postrotate でシグナルを送り、ログを開き直してもらいます。copytruncate は同じファイルを使い続けるので開き直し不要ですが、コピーと切り詰めの間に書かれた行は失われます。
検証用のアプリと集計スクリプト
連番の行を高速で書き続け、SIGHUP を受けたらログを開き直すダミーアプリです(Python 3.11)。
# writer.py: 連番の行を書き続けるダミーアプリ(SIGHUPでログを開き直す)
import os, signal, sys, time
path = sys.argv[1]
mode = sys.argv[2] if len(sys.argv) > 2 else "a" # "a"=追記(O_APPEND) / "w"=書き込み位置を自分で持つ
f = open(path, mode, buffering=1)
def reopen(signum, frame):
global f
f.close()
f = open(path, "a", buffering=1)
signal.signal(signal.SIGHUP, reopen)
with open(path + ".pid", "w") as p:
p.write(str(os.getpid()))
i = 0
while True:
i += 1
f.write(f"seq={i:07d} order created\n")
if i % 50 == 0:
time.sleep(0.001)
ローテート済みのファイルと現行ファイルをすべて読み、連番の欠けとNULバイト(\0)を数えるスクリプトです。
# check.py: 連番の欠け・重複・NULバイトを数える
import glob, gzip, re, sys
base = sys.argv[1]
seqs, nul = [], 0
for fn in sorted(glob.glob(base + "*")):
if fn.endswith(".pid"): continue
data = gzip.open(fn).read() if fn.endswith(".gz") else open(fn, "rb").read()
nul += data.count(b"\x00")
seqs += [int(m) for m in re.findall(rb"seq=(\d{7})", data)]
s = sorted(set(seqs))
missing = s[-1] - s[0] + 1 - len(s)
print(f"files={len(glob.glob(base+'*'))-1} lines={len(seqs)} first={s[0]} last={s[-1]} missing={missing} dup={len(seqs)-len(s)} nul_bytes={nul}")
3つの設定を用意しました。ct.conf は下の create 行を copytruncate に替えて postrotate〜endscript を消したもの、nos.conf は postrotate〜endscript を消したものです(パスはそれぞれ ct/・nos/)。
# cr.conf(create + postrotate でSIGHUP)
/tmp/t89lr/cr/app.log {
rotate 10
create 0644 root root
missingok
notifempty
postrotate
kill -HUP "$(cat /tmp/t89lr/cr/app.log.pid)"
endscript
}
それぞれ、アプリを起動して1秒ごとに3回 logrotate -f を実行しました。
for c in ct cr nos; do
python3 writer.py /tmp/t89lr/$c/app.log a &
sleep 1
for k in 1 2 3; do logrotate -f -s /tmp/t89lr/st_$c /tmp/t89lr/$c.conf; sleep 1; done
kill %1; wait
echo "== $c =="; ls -l $c | tail -n +2; python3 check.py /tmp/t89lr/$c/app.log
done
実行結果(pidファイルの行は省略):
== ct ==
-rw-r--r-- 1 root root 1030900 Oct 1 22:52 app.log
-rw-r--r-- 1 root root 1020500 Oct 1 22:52 app.log.1
-rw-r--r-- 1 root root 1037400 Oct 1 22:52 app.log.2
-rw-r--r-- 1 root root 1071200 Oct 1 22:52 app.log.3
files=4 lines=160000 first=1 last=163600 missing=3600 dup=0 nul_bytes=0
== cr ==
-rw-r--r-- 1 root root 949000 Oct 1 22:52 app.log
-rw-r--r-- 1 root root 982800 Oct 1 22:52 app.log.1
-rw-r--r-- 1 root root 1127100 Oct 1 22:52 app.log.2
-rw-r--r-- 1 root root 1014000 Oct 1 22:52 app.log.3
files=4 lines=156650 first=1 last=156650 missing=0 dup=0 nul_bytes=0
== nos ==
-rw-r--r-- 1 root root 0 Oct 1 22:52 app.log
-rw-r--r-- 1 root root 3749200 Oct 1 22:52 app.log.1
files=2 lines=144200 first=1 last=144200 missing=0 dup=0 nul_bytes=0
| 方式 | 欠けた行 | 結果のファイル | 読み取れること |
|---|---|---|---|
| copytruncate | 3,600行(3回のローテートで) | 世代ごとにきれいに分かれる | 書き込みが多いほど、コピー〜切り詰めの間の行が失われる |
| create + postrotate(SIGHUP) | 0行 | 世代ごとにきれいに分かれる | 開き直しができるアプリなら最も確実 |
| create のみ(シグナルなし) | 0行 | 全行が app.log.1 に入り続け、app.log は0バイトのまま |
アプリは移動後のファイルに書き続ける。notifempty により2回目以降は何も起きない |
この検証は1秒に数万行という極端な書き込み量なので、通常のアプリで数千行が欠けるわけではありません。ただ、copytruncate には原理上「欠ける時間帯」があることは数字で確認できます。
copytruncate+追記モードでないアプリの組み合わせ
アプリが追記モード(O_APPEND、シェルなら >>)ではなく、書き込み位置を自分で持つモード(Pythonの "w"、シェルの >)で開いていると、切り詰め後も前の位置から書き続けます。
python3 writer.py /tmp/t89lr/ctw/app.log w &
sleep 1; logrotate -f -s /tmp/t89lr/st_ctw /tmp/t89lr/ctw.conf; sleep 1; kill %1
ls -ls /tmp/t89lr/ctw | tail -n +2
python3 check.py /tmp/t89lr/ctw/app.log
head -c 40 /tmp/t89lr/ctw/app.log | od -c
実行結果:
992 -rw-r--r-- 1 root root 2039700 Oct 1 22:52 app.log
1000 -rw-r--r-- 1 root root 1020500 Oct 1 22:52 app.log.1
4 -rw-r--r-- 1 root root 4 Oct 1 22:52 app.log.pid
files=2 lines=78250 first=1 last=78450 missing=200 dup=0 nul_bytes=1025700
0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0000040 \0 \0 \0 \0 \0 \0 \0 \0
切り詰めたはずの app.log が約2MBのまま、先頭に約1MBのNULバイトが入りました(ファイルに穴が空いた状態。ls -s の実使用ブロックは992KB)。less で開くと ^@ が並んで見えます。nohup java -jar app.jar > app.log のように > でリダイレクトしている場合は >> に変えると、copytruncate 後に正しく先頭から書かれます。
どちらを選ぶかの判断表
| アプリの状態 | おすすめ |
|---|---|
| シグナルや reload でログを開き直せる(nginx、Apache、rsyslog など) | create + postrotate |
Python の WatchedFileHandler を使っている |
create(シグナル不要。下で検証) |
| ログを開き直す仕組みがなく、再起動もしたくない | copytruncate(わずかな欠けを許容) |
| アプリ自身がローテート機能を持つ(logback、log4j2、pm2-logrotate など) | アプリ側に任せ、logrotate の対象から外す |
| systemd サービスで標準出力に出している | ファイルにせず journald に任せる(logrotate不要) |
PythonのWatchedFileHandlerならcreateだけで欠けない
Python 標準の logging.handlers.WatchedFileHandler は、書き込みのたびにファイルのinodeを確認し、変わっていたら自動で開き直します。postrotate なしの create 設定(nos.conf と同じ内容)で同じ検証をしました。
# wf_writer.py
import logging, logging.handlers, sys, time
h = logging.handlers.WatchedFileHandler(sys.argv[1])
log = logging.getLogger("app"); log.addHandler(h); log.setLevel(logging.INFO)
i = 0
while True:
i += 1
log.info("seq=%07d order created", i)
if i % 50 == 0:
time.sleep(0.001)
実行結果:
-rw-r--r-- 1 root root 429000 Oct 1 22:53 app.log
-rw-r--r-- 1 root root 521300 Oct 1 22:53 app.log.1
-rw-r--r-- 1 root root 587132 Oct 1 22:53 app.log.2
-rw-r--r-- 1 root root 516568 Oct 1 22:53 app.log.3
files=3 lines=79000 first=1 last=79000 missing=0 dup=0 nul_bytes=0
欠け0行で、世代ごとに分かれました。 Python アプリで logrotate を使うなら、FileHandler を WatchedFileHandler に替えるのがいちばん手軽です。
コピペ用テンプレート集
/etc/logrotate.d/ に、対象ごとに1ファイルで置きます。置いたら sudo logrotate -d /etc/logrotate.d/ファイル名 で文法を確認してください。
nginx(Ubuntu 24.04 の apt 版に同梱の設定)
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
nginx は USR1 シグナルでログを開き直します。sharedscripts があるので、access.log と error.log の2つがあっても postrotate は1回だけ実行されます。自分で書く場合は postrotate を次のようにしても同じ意味です。
postrotate
[ -f /run/nginx.pid ] && kill -USR1 "$(cat /run/nginx.pid)"
endscript
Node.js(node app.js >> app.log で動かしている場合)
/var/log/myapp/node.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
copytruncate
}
Node.js の fs.createWriteStream には開き直しの仕組みが無いため copytruncate を使います。pm2 で動かしている場合は、create 方式にして postrotate で pm2 reloadLogs(pm2 がログを開き直すコマンド)を呼ぶか、pm2-logrotate モジュールに任せる方法もあります。
Java(標準出力をファイルにリダイレクトしている場合)
/var/log/myapp/stdout.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
copytruncate
maxsize 500M
}
起動コマンドは >>(追記)にします(> だと前述のNULバイトが入ります)。logback の RollingFileAppender や log4j2 の RollingFile で出しているログは、アプリ側でローテートされるので logrotate の対象に含めないでください。二重にローテートすると世代がずれます。
Python(WatchedFileHandler を使う場合)
/var/log/myapp/app.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
create 0640 appuser appgroup
}
FileHandler のままの場合は、copytruncate を足すか、SIGHUP でハンドラーを開き直す処理を入れて postrotate で kill -HUP を送ります。
独自ログ(バッチの出力を日付付きで別ディレクトリに保存)
/var/log/batch/*.log {
weekly
rotate 12
maxage 90
missingok
notifempty
compress
dateext
dateformat -%Y%m%d
olddir /var/log/batch/archive
createolddir 0750 root adm
su root adm
create 0640 root adm
}
実際に olddir + createolddir + dateext + dateformat -%Y%m%d-%H%M%S + compress で試すと、次のようになりました。
od/archive:
-rw-r--r-- 1 root root 22 Oct 1 22:52 app.log-20261001-225251.gz
dateext の既定書式は -%Y%m%d(1日1つ)です。1日に2回以上ローテートする可能性がある場合(hourly、size、maxsize)は、%H%M%S まで入れると名前が重ならずに済みます。
cronとsystemd timer:logrotateは誰が動かしているか
logrotate は常駐しません。cron か systemd timer が1日1回起動しています。Ubuntu 24.04 では両方の仕組みが入っており、次のように振り分けられています。
/etc/cron.daily/logrotate の先頭には「/run/systemd/system があれば何もせず終了(systemd timer に任せる)」という分岐があり、/lib/systemd/system/logrotate.timer には OnCalendar=daily、AccuracySec=1h、Persistent=true が書かれています。
| 項目 | cron(/etc/cron.daily) | systemd timer(logrotate.timer) |
|---|---|---|
| 使われる条件 | systemd が動いていない環境(コンテナ等) | systemd が動いている通常のサーバー |
| 実行時刻 | cron.daily の時刻(anacron 環境では起動後に実行) | 毎日0時ごろ(AccuracySec=1h で最大1時間ずれる) |
| 止まっていた間の分 | anacron があれば後で実行 | Persistent=true で起動後に実行 |
| 実行状況の確認 | /var/log/syslog など |
systemctl list-timers logrotate.timer、journalctl -u logrotate |
| 時刻・頻度の変更 | crontab に独自行を追加 | sudo systemctl edit logrotate.timer で OnCalendar= を上書き |
上の2ファイルの中身は実際に確認しましたが、この検証用コンテナは systemd が PID 1 で動いていないため、systemctl list-timers などの timer 操作はこの環境では未実行です。
hourly や maxsize を効かせたい場合は、logrotate 自体を毎時動かします。
# sudo systemctl edit logrotate.timer で開いたファイルに書く
[Timer]
OnCalendar=
OnCalendar=hourly
OnCalendar= を一度空にしてから書くのは、既定の daily を消して置き換えるためです。cron で動かす場合は、/etc/cron.hourly/ に /usr/sbin/logrotate /etc/logrotate.conf を呼ぶスクリプトを置きます(cron の書式は「cronの書き方」の記事で詳しく扱っています)。
よくあるエラーと直し方(実際に再現)
parent directory has insecure permissions
ログのあるディレクトリが、root 以外のグループや全員から書き込める状態(例:777)のときに出ます。
error: skipping "/tmp/t89lr/ins/app.log" because parent directory has insecure permissions (It's world writable or writable by group which is not "root") Set "su" directive in config file to tell logrotate which user/group should be used for rotation.
直し方は2通りです。
| 方法 | 書き方 |
|---|---|
| ディレクトリの権限を絞る | chmod 755 /var/log/myapp |
そのログの所有者で回すよう su を書く |
su appuser appgroup |
su root root を追加したところ、exit=0 でローテートされ app.log.1 ができました。通常はログの所有者・グループを su に書くのが自然です。
Ignoring ... because it is writable by group or others
設定ファイルを 666 にして実行すると、error: Ignoring /tmp/t89lr/ins.conf because it is writable by group or others. と出てその設定は読み飛ばされ、終了コードは0でした。「なぜかローテートされない」原因になりやすいので、設定ファイルは chmod 644・chown root:root にします。
エラー一覧
| 症状・メッセージ | 原因 | 対処 |
|---|---|---|
log does not need rotating (log has already been rotated) が初回に出る |
状態ファイルに記録が無いログは初回は記録のみ | 翌日まで待つか -f で確認 |
parent directory has insecure permissions |
ディレクトリが root 以外から書き込み可 | chmod 755 または su |
Ignoring ... because it is writable by group or others |
設定ファイルが 664/666 など | chmod 644 |
duplicate log entry |
同じパスを複数設定で指定 | 片方を削除・移動 |
error verifying olddir path |
olddir が無い | createolddir |
| ローテート後も古いファイルにだけ書かれる | create 方式でアプリが開き直していない | postrotate でシグナル、または copytruncate |
| 切り詰め後もサイズが減らずNULが入る | > リダイレクトで copytruncate |
>> に変更 |
| ローテートしたのにディスクが空かない | 削除済みファイルをプロセスが開いたまま | 開き直させる(lsof +L1 で確認) |
よくある質問
Q. 設定を変えたら logrotate の再起動は必要ですか?
A. 不要です。logrotate は常駐せず、起動のたびに設定を読み直します。次回の定期実行から反映されます。すぐ試したい場合は sudo logrotate -d /etc/logrotate.d/対象 で確認してから sudo logrotate -f /etc/logrotate.d/対象 を実行します。
Q. copytruncate を使うとログは必ず欠けますか? A. コピーと切り詰めの間に書かれた行だけが対象なので、書き込みが少ないアプリでは欠けないことも多いです。この記事の検証では1秒に数万行書く条件で3回のローテートで3,600行欠けました。欠けを避けたい場合は、create + postrotate(シグナルで開き直し)を選びます。
Q. daily と size を両方書くとどうなりますか?
A. logrotate 3.21 では、size は時間間隔とは別の条件で、後に書いたほうが有効になります。「毎日、ただし大きくなったら途中でも」は daily + maxsize、「毎日、ただし小さければ見送り」は daily + minsize を使うと意図どおりになります。
まとめ
- logrotate は
/etc/logrotate.d/に1ファイル置くだけで、cron または systemd timer から1日1回動く - 基本は daily / rotate / missingok / notifempty / compress / delaycompress / create の7つ
-dでドライラン、-fで強制、-sで状態ファイルを分けて安全に試せる。初回は記録のみでローテートされない- create + postrotate(シグナル)は欠け0行、copytruncate は検証で3,600行欠けた。開き直せるアプリは create を選ぶ
- copytruncate を使うなら、アプリは
>>(追記モード)で開く。>だとNULバイトが入る - Python は WatchedFileHandler にすれば create だけで欠けなく分かれる
insecure permissionsはsuかchmod 755、設定ファイルは root 所有・644
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「logrotateの設定方法|コピペ用テンプレートとディレクティブ一覧」
株式会社RJCでは、ログ管理のような運用の基礎を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。