Nginx日志如果不做切割,几个月就能撑爆服务器磁盘,这绝不是危言耸听。线上环境里,一个中等流量的网站每天产生的access.log轻松突破几个GB。logrotate是Linux系统自带的一款日志轮转工具,它并不是Nginx的专属功能,但却是管理Nginx日志最成熟、最稳定的方案。很多人配置完Nginx后直接用默认的logrotate配置,结果发现日志根本没切成功,或者切完后Nginx还在往旧文件写日志,问题就出在细节上。
logrotate切割Nginx日志的核心原理logrotate的工作机制并不复杂。它按照你设定的时间周期或文件大小阈值,把当前的日志文件重命名为带日期后缀的归档文件,然后创建新的空白日志文件,同时给Nginx进程发送一个信号,让Nginx重新打开日志文件句柄。这里的关键在于“重新打开文件句柄”这一步。Linux系统下,进程读写文件靠的是文件描述符,你把文件改名了,进程如果不主动刷新,它依然会往原来的inode写入数据,结果就是日志看起来切了,实际上数据全写进了备份文件里,新的日志文件空空如也。
解决这个问题的标准做法是利用Nginx的USR1信号。向Nginx的master进程发送USR1信号,Nginx会优雅地重新打开所有日志文件。logrotate配置里的postrotate脚本段就是干这个的。很多人漏掉了这一步,或者信号发送的路径写错了,导致切割形同虚设。
基础配置:从零搭建可靠的日志切割先说最通用的配置方案。Nginx日志通常存放在/var/log/nginx/目录下,logrotate的主配置文件在/etc/logrotate.conf,但实际项目中我们强烈建议在/etc/logrotate.d/目录下为Nginx单独创建一个配置文件,比如/etc/logrotate.d/nginx。这样做的好处是模块化管理,不会影响系统其他日志的轮转策略。
一个生产环境可用的基础配置如下:
/var/log/nginx/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
逐条解释这些参数的含义。daily表示每天切割一次,这是最常见的策略。missingok的意思是如果日志文件不存在,logrotate不会报错,直接跳过,这在某些场景下很有用,比如某个虚拟主机当天没有产生日志。rotate 30保留30天的归档日志,超过30天的会被删除。compress启用gzip压缩,把切下来的日志压缩成.gz文件,节省磁盘空间。delaycompress和compress配合使用,延迟一天压缩,保证最新的那份归档日志不压缩,方便紧急排查问题时直接查看。
notifempty是个很实用的参数,如果日志文件是空的,就不做轮转,避免产生一堆零字节的垃圾文件。create 0640 nginx adm指定新创建的日志文件的权限、属主和属组,这里需要根据你实际的Nginx运行用户来调整,很多发行版用的是www-data或nginx用户。sharedscripts表示在多个日志文件匹配的情况下,postrotate脚本只执行一次,而不是每个文件都执行一次,这能避免重复向Nginx发送信号。
postrotate到endscript之间的脚本是核心。它先检查Nginx的PID文件是否存在,如果存在就读取PID并向该进程发送USR1信号。这里有个细节,PID文件的路径在不同系统和安装方式下可能不一样,常见的有/var/run/nginx.pid、/run/nginx.pid或者/usr/local/nginx/logs/nginx.pid,你需要确认自己环境里的实际路径。
按文件大小切割:应对突发流量daily策略在流量平稳的场景下够用,但如果遇到突发流量,一天之内日志就可能暴涨到几十GB,这时候按天切割就显得力不从心了。logrotate支持size参数,可以按文件大小触发切割。配置方式如下:
/var/log/nginx/*.log {
size 100M
missingok
rotate 50
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
size 100M表示当日志文件超过100MB时就触发切割。这里把daily换成了size,两者也可以同时存在,同时存在时是“或”的关系,哪个条件先满足就触发切割。rotate 50加大保留数量,因为按大小切割可能一天产生多个归档文件。需要注意的是,logrotate默认每天只运行一次,由cron任务调度。如果你希望更频繁地检查文件大小,可以在/etc/crontab中增加logrotate的执行频率,或者把logrotate的cron脚本从cron.daily移到cron.hourly目录下,但要注意避免过于频繁导致系统负载升高。
解决日志切割后Nginx不写新文件的经典故障这个问题困扰过很多运维人员。现象是logrotate执行了,归档文件也生成了,但新的日志文件一直是空的,所有新日志都写进了归档文件里。根本原因就是前面说的文件句柄问题。除了postrotate脚本没正确发送USR1信号之外,还有几种情况会导致这个问题。
第一种是Nginx的PID文件路径配置错误。Nginx编译安装时如果指定了--pid-path参数,PID文件可能不在默认位置。检查nginx.conf里的pid指令,确保logrotate配置中的路径与之匹配。第二种情况是Nginx以非root用户运行,而logrotate以root身份执行,发送信号时权限不足。虽然root用户可以给任何进程发信号,但如果PID文件本身不可读,cat命令就会失败。确保PID文件所在目录对logrotate执行用户可读。
第三种是容器化环境下的特殊问题。Docker容器里的Nginx,PID文件路径和宿主机不同,而且容器内的进程对宿主机来说PID是不同的。这种情况下,建议使用docker exec或者通过Nginx的容器内信号机制来处理,比如在postrotate中使用docker exec nginx_container nginx -s reopen这样的命令。
进阶技巧:自定义日志命名和归档策略logrotate默认的归档命名规则是文件名后加.1、.2、.3这样的序号,最新的归档是.1。如果你希望用日期来命名归档文件,方便按日期快速定位,可以使用dateext参数:
/var/log/nginx/*.log {
daily
missingok
rotate 30
dateext
dateformat -%Y%m%d
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
dateext启用日期扩展名,dateformat自定义日期格式,这里设置为-年月日的形式,归档文件名会变成access.log-20240101.gz。这种命名方式在需要按日期范围批量处理日志时非常方便。还有一个参数olddir,可以把归档文件移动到指定目录,而不是和当前日志混在一起:
/var/log/nginx/*.log {
daily
missingok
rotate 30
dateext
dateformat -%Y%m%d
olddir /var/log/nginx/archive
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
使用olddir需要确保目标目录存在且有正确的写入权限,logrotate不会自动创建这个目录。这种做法的好处是/var/log/nginx/目录下始终只有当前活跃的日志文件,归档全部集中在archive子目录,目录结构更清晰。
多虚拟主机场景下的日志管理生产环境中一台Nginx往往承载几十上百个虚拟主机,每个虚拟主机单独配置了access_log和error_log。如果把这些日志文件都放在同一个目录下用通配符匹配,logrotate会一次性处理所有文件。但有时不同站点的日志切割策略需要差异化,比如核心业务站点保留90天日志,边缘站点只保留7天。
这种情况下,可以在logrotate配置中使用多个配置块,每个块匹配不同的日志路径模式:
/var/log/nginx/core/*.log {
daily
rotate 90
missingok
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
/var/log/nginx/edge/*.log {
daily
rotate 7
missingok
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
这里把核心站点和边缘站点的日志放在不同子目录,通过不同的配置块实现差异化的保留周期。sharedscripts配合postrotate时需要注意,每个配置块都会独立执行一次postrotate脚本,所以Nginx会收到多次USR1信号,这没有副作用,Nginx处理重复的USR1信号不会有问题。
手动测试与排错方法配置完成后不要等着cron自动执行,先用debug模式手动跑一遍验证配置是否正确:
logrotate -d /etc/logrotate.d/nginx
-d参数是debug模式,它会模拟执行整个轮转流程,输出详细的处理步骤,但不会真正修改任何文件。通过debug输出可以检查文件匹配是否正确、轮转条件是否满足、postrotate脚本是否会执行。如果debug输出显示一切正常,可以用-f参数强制执行一次:
logrotate -f /etc/logrotate.d/nginx
-f是force模式,忽略所有时间或大小条件,强制触发轮转。执行完后检查日志目录,看归档文件是否生成,新的日志文件是否创建,Nginx是否开始往新文件写入。可以用lsof命令验证Nginx进程打开的文件句柄:
lsof -p `cat /var/run/nginx.pid` | grep log
这条命令列出Nginx master进程打开的所有文件,过滤出日志相关的条目。如果看到Nginx仍然持有旧归档文件的句柄,说明USR1信号没有生效,需要排查postrotate脚本的执行情况。
另一个常见的排错点是logrotate的状态文件。logrotate在/var/lib/logrotate/目录下维护一个状态文件,记录了每个日志文件上次轮转的时间。如果状态文件记录有误,可能导致logrotate认为还没到轮转时间。可以用-v参数查看详细的执行日志,或者在状态文件中手动调整对应条目的日期来触发轮转。
与系统日志管理体系的整合logrotate本身依赖cron来触发,默认的cron脚本在/etc/cron.daily/logrotate,每天执行一次。如果你的日志量特别大,需要更频繁的切割,可以把脚本复制到/etc/cron.hourly/目录下,实现每小时检查一次。但要注意,logrotate的状态文件机制保证了同一个日志文件在一天内不会被重复切割,即使你每小时执行一次,只要条件不满足就不会触发。
对于日志集中管理的场景,切割后的归档文件通常需要同步到日志中心或对象存储。可以在postrotate脚本中增加rsync或s3cmd同步命令,但要注意控制脚本的执行时间,避免阻塞logrotate的后续操作。如果同步操作耗时较长,建议在postrotate中放入后台任务或者使用异步方式处理。
还有一个值得注意的点是日志文件的权限和所有权。如果Nginx以www-data用户运行,而logrotate以root执行,新创建的日志文件默认属主是root,这会导致Nginx无法写入新日志。create参数显式指定了权限和属主属组,这是必须的。同时要确保日志目录对Nginx运行用户有写入权限,否则即使文件创建成功,Nginx也写不进去。
磁盘空间监控也应该和日志切割策略联动。即使配置了rotate限制保留份数,如果日志增长速度超过预期,磁盘仍然可能被填满。建议在postrotate中增加磁盘使用率的检查逻辑,当使用率超过阈值时触发告警或强制清理更老的归档。这不是logrotate的内置功能,但通过自定义脚本可以轻松实现。
Nginx日志切割看似是个小问题,实际上涉及文件系统原理、进程信号机制、cron调度策略等多个层面的知识。把logrotate配置好,日志管理就成功了一大半。剩下的就是根据实际业务情况调整切割周期、保留策略和归档处理流程,让日志既不会占用过多存储资源,又能在需要排查问题时快速找到对应的历史数据。
