关键解法是用copytruncate模式:先拷贝日志再清空原文件,使进程持续写入同一fd,避免句柄残留导致的日志错乱与磁盘空间不释放;需禁用create和compress,搭配rotate 0与daily等触发条件。

要让 logrotate 处理“不释放文件句柄”的日志(比如 Nginx、rsyslog 或某些长期运行的 Java 应用),关键不是强制关掉句柄,而是绕过它——用 copytruncate 模式安全清空原文件,让进程继续写入同一路径,无需重启或发信号。
为什么文件句柄不释放是个问题
很多服务启动后打开日志文件并长期持有 fd(文件描述符),不会主动监听轮转事件。如果 logrotate 直接重命名日志(如 access.log → access.log.1),进程仍往旧 fd 写,实际数据就进了已重命名的 .1 文件,导致当前日志为空、历史日志错乱、磁盘空间没真正释放。
copytruncate 是最通用的解法
它分两步执行:先拷贝当前日志内容到归档名(如 app.log → app.log.1),再清空原文件(truncate app.log)。进程 fd 依然有效,写入位置从 EOF 开始,相当于“无缝续写”。
- 必须搭配 rotate N(哪怕 N=0)和 daily/size 等触发条件
- 不能和 create 同时用(create 是新建空文件,会改变 inode,可能触发某些应用报错)
- 避免与 compress 冲突:若设了 rotate 0,compress 必须禁用;否则 logrotate 可能跳过压缩或报错
配置示例(适用于无重载能力的服务)
以 /var/log/myapp/out.log 为例,写入 /etc/logrotate.d/myapp:
/var/log/myapp/out.log {
daily
missingok
notifempty
rotate 0
copytruncate
# 不加 compress、delaycompress、create
}
这样每天执行时:若有内容就拷走 + 清空;若为空则跳过;旧副本(.1)立刻被删除(因 rotate 0);服务全程无感知。
替代方案:发信号重开日志(需服务支持)
如果应用支持日志重开(如 Nginx 收 USR1、Log4j2 支持 reload),优先用 create + postrotate 发信号,更干净:
- rename 原日志 → 归档名
- create 新日志文件(权限/属主可指定)
- postrotate 里用 kill -USR1 或对应信号通知进程 reopen
注意:必须确认 PID 文件路径准确(如 /var/run/nginx.pid),且信号语义正确(USR1 对 Nginx = reopen logs,HUP 对 rsyslog = reload config)。











