Nginx默认会把访问日志写入access.log,错误日志写入error.log。如果没有日志切割机制,这些文件会一直增长,高并发站点一天就可能产生数GB的数据。日志文件过大不仅浪费磁盘空间,还会让排查问题变得困难,甚至影响磁盘I/O性能。因此,建立一套自动清理过期日志的策略是生产环境运维的基本功。

手动清理通常使用rm命令,但直接删除正在被写入的日志文件并不会立即释放磁盘空间。原因是Linux中文件删除只减少了目录中的链接计数,进程仍持有文件描述符时,数据块不会被回收。Nginx进程会继续向已删除的文件写入数据,直到进程退出或重新打开日志。这就导致磁盘空间看似被占用,但通过文件名又找不到对应文件。正确做法是先重命名日志文件,再向Nginx主进程发送USR1信号,让Nginx重新打开日志文件,旧文件句柄才会被释放。
手动清理的局限与基本切割思路
很多情况下,直接执行rm -f /var/log/nginx/access.log并不能解决问题。因为Nginx还持有这个文件的描述符,内核不会释放对应的inode和数据块,磁盘使用率不会下降。要真正释放空间,必须先让Nginx停止向旧文件写入。常见做法是将当前日志重命名,比如加上日期后缀,然后发送USR1信号。Nginx收到USR1后,会重新打开配置文件里指定的日志路径,由于原文件已被改名,Nginx会自动创建新的access.log继续写入。
下面是一个简单的手动切割脚本:
mv /var/log/nginx/access.log /var/log/nginx/access.log.$(date +%Y%m%d) kill -USR1 $(cat /var/run/nginx.pid)
这段脚本将当前日志重命名为带日期的文件,然后读取Nginx的pid文件并发送USR1信号。执行后,Nginx会打开新的access.log,旧文件可以安全地压缩或删除。不过手动操作在日志文件多、需要频繁处理时效率很低,而且容易忘记发送信号,因此需要引入自动化工具。
使用logrotate实现自动轮转与清理
logrotate是Linux发行版自带的日志管理工具,几乎不需要额外安装。它通过配置文件定义日志文件的处理策略,并由系统cron任务每天自动触发。对于Nginx日志,常见的做法是在/etc/logrotate.d/nginx中定义规则,指定轮转周期、保留份数、压缩方式以及轮转后执行的操作。这样既能保持当前日志文件较小,又能自动删除超过保留数量的历史归档。
下面是一个典型的Nginx logrotate配置示例:
/var/log/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
这个配置的含义是:每天轮转一次,保留30个归档文件,超过30个的旧归档会被删除;归档文件使用gzip压缩;delaycompress表示延迟一个周期再压缩,避免压缩正在写入的文件;missingok表示日志文件不存在时不报错;notifempty表示空文件不轮转;create指定新建日志文件的权限和属主;sharedscripts确保所有匹配的日志文件只执行一次postrotate脚本;postrotate中的命令在轮转后执行,向Nginx发送USR1信号。
logrotate的执行通常由系统的cron任务负责,例如在/etc/cron.daily/logrotate中调用。用户也可以手动运行logrotate -f /etc/logrotate.d/nginx来测试配置是否正确。需要注意的是,如果Nginx的pid文件路径不是/var/run/nginx.pid,需要根据实际部署调整postrotate中的路径。
编写Shell脚本配合crontab按时间删除过期日志
有些场景下logrotate无法完全满足需求,例如需要删除所有超过30天的日志归档,不管它们是否已经被压缩,或者需要按照更灵活的文件名模式匹配。此时可以编写自定义Shell脚本,使用find命令按修改时间筛选文件并删除。find命令可以精确控制删除条件,比如只删除后缀为.log.开头且修改时间超过指定天数的文件。
下面是一个基于find的清理脚本示例:
#!/bin/bash LOG_DIR="/var/log/nginx" FIND="/usr/bin/find" $FIND "$LOG_DIR" -name "*.log.*" -type f -mtime +30 -delete
脚本中-name "*.log.*"匹配带日期或序号后缀的日志归档文件,-type f只针对普通文件,-mtime +30表示修改时间超过30天,-delete直接删除匹配到的文件。这种方式不会影响当前正在写入的access.log,因为当前日志文件名通常不带后缀。如果需要对压缩文件也进行清理,可以将-name改为"*.log.*.gz"或者使用多个-name条件组合。
对于不支持-delete选项的旧版本find,可以使用-exec配合rm命令:
$FIND "$LOG_DIR" -name "*.log.*" -type f -mtime +30 -exec rm -f {} \;
这里-exec后面的{}会被替换为找到的文件路径,\;是转义的分号,表示-exec参数的结束。两种写法效果相同,但-delete在现代find中更简洁且效率稍高。
脚本编写完成后,需要赋予可执行权限,并将其加入crontab定时任务。例如每天凌晨3点执行清理:
0 3 * * * /usr/local/bin/nginx_log_clean.sh
crontab的时间格式为分、时、日、月、周,0 3表示每天3点0分。添加后系统会自动执行脚本,无需人工干预。建议在正式部署前先手动运行一次脚本,确认日志文件名匹配正确,避免误删重要文件。
日志切割中的信号机制与copytruncate选择
Nginx日志切割的核心在于文件重命名后如何让进程继续正确写入。Nginx主进程在工作时打开日志文件,当配置文件中的access_log指令指定了路径,Nginx会保持该文件的描述符。如果直接使用mv重命名日志,Nginx仍然向重命名后的文件写入数据。此时需要发送USR1信号,Nginx会重新打开所有日志文件,新日志写入重新创建的access.log。这个过程中旧文件的描述符被关闭,磁盘空间得以释放。
logrotate还提供了一种copytruncate方式,它的做法是先复制日志内容到归档文件,然后清空原文件而不进行重命名。这样Nginx无需重新打开日志,省去了发送信号的过程。但copytruncate有一个明显缺点:复制和清空之间存在时间窗口,如果日志写入非常频繁,可能会丢失这期间的少量日志。对于高并发或日志完整性要求高的场景,不推荐使用copytruncate。
对比来看,使用重命名加USR1信号的方式更加可靠,只是需要在postrotate中正确发送信号。如果日志写入量不大且不想处理信号,可以使用copytruncate,但要接受可能出现的日志丢失风险。在选择时需要根据业务对日志完整性的要求来权衡。
常见坑点与磁盘空间监控建议
在实施Nginx日志自动清理时,有几个常见的错误需要避免。首先是忘记发送USR1信号,导致Nginx继续写入旧文件,新日志文件不会被创建。其次是日志文件权限设置错误,例如logrotate中的create参数指定的用户与Nginx运行用户不一致,导致Nginx无法写入新日志。另外,cron任务的环境变量可能与交互式Shell不同,脚本中如果使用相对路径或依赖某些环境变量,可能会执行失败。
建议在清理脚本中尽量使用绝对路径,比如/usr/bin/find、/bin/rm等。同时,定期检查磁盘使用率,可以使用df -h命令查看挂载点空间。当磁盘使用率超过80%时,应该触发告警,提醒运维人员检查日志增长情况。可以将清理脚本与磁盘空间检查结合,例如在脚本开头判断当前磁盘使用率,如果超过阈值则只保留最近7天的日志,进一步压缩空间。
合理的日志清理策略不仅要删除过期文件,还要保证当前日志写入不受影响,并保留足够的历史记录用于问题排查。通常建议保留30天左右的日志,既能满足审计需求,又不会占用过多磁盘。通过logrotate或自定义脚本配合crontab,可以完全自动化这一过程,减少人工干预和误操作风险。

