日志是服务器上最"温柔"的杀手。它每天安静地增长,你几乎不会察觉,等到某天 df -h 弹出红色的 100%、数据库写不进去、网站报 500,才想起来 /var/log 早就堆了几十 GB。这类故障的可怕之处在于,它不挑时间、不分业务,几乎所有长期运行的服务器都会遇到。
这篇文章讲的是怎么用 find 配合定时任务,把过期日志按规则自动清理掉。内容覆盖:日志为什么必须定期清理、find 的按时间删除怎么写才安全、怎么配置 cron 和 systemd timer、以及怎么避免"手滑删错目录"这种运维事故。
文中给出的保留天数、文件大小阈值都是经验值,是很多中小团队在用的保守配置,你可以按磁盘容量和合规要求调整。凡是涉及 -delete、rm 的命令都会给出风险提示和"先试跑再删除"的安全做法,请务必看完再执行。
一、为什么日志必须定期清理
日志增大的速度因业务而异。一个日访问量几万的小网站,Nginx 的 access log 每天大概几百 MB;一个大流量站点或高并发 API,一天涨几个 GB 很正常。再算上应用日志(Java 应用的 GC log、框架的运行日志)、系统日志(/var/log/messages、syslog、journal)和数据库日志,一台没做过清理的机器,半年把磁盘写满毫不意外。
磁盘写满的后果不只是"写不进日志"。它会连锁引发:数据库无法写入导致业务中断、systemd 无法记录状态导致服务异常、SSH 登录可能因无法写 wtmp 而失败——严重时你连机器都登不上去,只能走控制台救援。所以日志清理不是"可做可不做"的优化,而是必须有的基础维护。
但也不能一删了之。日志有两类用途:排障和合规审计。排障通常看最近 7-15 天的日志就够,合规审计(比如等保、金融行业要求)则可能要求保留 6 个月甚至更久。清理策略要在这两者之间找平衡,而不是简单"全删最省心"。
| 清理方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
手工 rm |
简单直接 | 容易漏、容易删错、无法持续 | 临时应急 |
| 应用自身轮转(logrotate) | 规范、可压缩归档、有钩子 | 需为每个服务单独配置 | Nginx、PHP、系统日志等 |
find + cron 定时删除 |
通用、规则灵活、一条覆盖多目录 | 规则写错会误删 | 自定义目录、批量清理 |
| 日志集中收集(如 ELK) | 本地可只留几天 | 需要额外资源与维护 | 中大型、多机集群 |
实践中常见组合是:系统日志和应用日志交给 logrotate,业务自己写的自定义日志目录用 find + cron 兜底。这篇文章聚焦后者。
二、find 按时间删除的正确写法
find 删除日志的核心是 -mtime(修改时间,单位天)和 -mmin(单位分钟)两个参数。关键要理解它的语义:-mtime +7 表示"修改时间在 7 天以前",注意那个加号,它代表"大于 7 天",而不是"最近 7 天"。
先把常用参数列清楚:
| 参数 | 含义 | 示例 |
|---|---|---|
-mtime +7 |
修改时间超过 7 天 | 删除 7 天前的文件 |
-mtime -7 |
修改时间在 7 天内 | 查找最近 7 天改动过的 |
-mtime 7 |
恰好第 7 天(24 小时倍数) | 精确到某天,少用 |
-mmin +120 |
修改时间超过 120 分钟 | 清理高频产生的临时日志 |
-type f |
只匹配普通文件 | 避免误删目录 |
-name "*.log" |
按文件名匹配 | 限定要删的日志 |
-size +100M |
大于 100MB | 按大小筛 |
-maxdepth 1 |
只查当前层,不进子目录 | 控制清理范围 |
-delete |
直接删除匹配项 | 危险,慎用 |
最重要的一条原则:先 find 打印,确认无误后再删除。 也就是先跑只读版本,把要删的文件列表看清楚,再把 -print 换成 -delete。
# 第一步:只查看,不删除(务必先做这一步)
# 找出 /var/log/app 下 30 天前、以 .log 结尾的普通文件
find /var/log/app -type f -name '*.log' -mtime +30 -print
# 顺便统计会删掉多少文件、占用多少空间
find /var/log/app -type f -name '*.log' -mtime +30 -print0 | du -ch --files0-from=- | tail -1
# 第二步:确认列表符合预期后,再执行删除
find /var/log/app -type f -name '*.log' -mtime +30 -delete
-delete 是 find 内置动作,比 -exec rm {} \; 更安全也更快:它不会对目录误操作,且避免了文件名里的空格、通配符被 shell 二次解析的问题(这在使用 -exec rm 时是真实存在的风险)。如果确实要用 -exec,正确写法是 -exec rm -f {} +,用 + 而不是 \;,能批量传参、性能更好。
三、一套可直接用的日志清理脚本
下面这段脚本把常见日志目录、保留天数、最小删除阈值都参数化,并带"试运行"开关和删除清单日志,适合直接放到生产环境。
#!/usr/bin/env bash
# 日志清理脚本
# 用法:
# bash log_clean.sh # 试运行(只打印,不删除)
# bash log_clean.sh --run # 真正执行删除
set -uo pipefail
DRY_RUN=1
[ "${1:-}" = "--run" ] && DRY_RUN=0
# ---- 配置区(经验值,按需调整)----
RETENTION_DAYS=30 # 保留天数
MIN_AGE_MIN=120 # 分钟级清理阈值(用于高频临时日志)
LOG_DIRS=(
"/var/log/app"
"/var/log/nginx"
"/data/www/logs"
)
# 永不删除的目录(白名单)
EXCLUDE_DIRS=("/var/log/app/audit")
AUDIT_LOG="/var/log/log_clean_audit.log"
log() { echo "[$(date '+%F %T')] $*" | tee -a "$AUDIT_LOG"; }
log "==== 开始清理,模式:$([ $DRY_RUN -eq 1 ] && echo 试运行 || echo 执行) ===="
for dir in "${LOG_DIRS[@]}"; do
[ -d "$dir" ] || { log "跳过(不存在): $dir"; continue; }
# 构造排除条件
EXPR=()
for ex in "${EXCLUDE_DIRS[@]}"; do
EXPR+=( -path "$ex" -prune -o )
done
if [ "$DRY_RUN" -eq 1 ]; then
log "将删除 $dir 下超过 ${RETENTION_DAYS} 天的 .log / .gz 文件:"
find "$dir" "${EXPR[@]}" -type f \( -name '*.log' -o -name '*.gz' \) -mtime +"$RETENTION_DAYS" -print
else
# 先记录清单,再删除,便于事后追溯
find "$dir" "${EXPR[@]}" -type f \( -name '*.log' -o -name '*.gz' \) -mtime +"$RETENTION_DAYS" -print0 \
| tee >(tr '\0' '\n' >> "$AUDIT_LOG") \
| xargs -0 -r rm -f
log "已清理 $dir"
fi
done
# 单独处理高频临时日志(按分钟)
if [ -d /var/log/app/tmp ]; then
if [ "$DRY_RUN" -eq 1 ]; then
find /var/log/app/tmp -type f -name '*.tmp' -mmin +"$MIN_AGE_MIN" -print
else
find /var/log/app/tmp -type f -name '*.tmp' -mmin +"$MIN_AGE_MIN" -delete
fi
fi
log "==== 清理结束 ===="
这段脚本有几个刻意的设计:第一,默认是试运行,必须显式加 --run 才真正删,降低误操作概率;第二,删除前把清单写进审计日志,出问题时能查出删了哪些文件;第三,用了白名单排除,像审计目录、重要归档目录可以排除在外。
四、配置定时任务
清理任务通常每天跑一次,凌晨低峰更合适。
# 推荐:系统级目录 /etc/cron.d/,文件需带用户名字段
sudo tee /etc/cron.d/log_clean >/dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 每天 03:30 执行日志清理
30 3 * * * root /usr/local/bin/log_clean.sh --run >> /var/log/log_clean.out 2>&1
EOF
sudo chmod 644 /etc/cron.d/log_clean
# 或者用 crontab -e(当前用户)
crontab -e
# 每天 03:30 清理
30 3 * * * /usr/local/bin/log_clean.sh --run >/dev/null 2>&1
# 或者用 systemd timer(推荐用于需要日志集成、随机延迟的场景)
# /etc/systemd/system/log_clean.service
[Unit]
Description=Clean expired logs
[Service]
Type=oneshot
ExecStart=/usr/local/bin/log_clean.sh --run
# /etc/systemd/system/log_clean.timer
[Unit]
Description=Daily log cleanup
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=600
[Install]
WantedBy=timers.target
Persistent=true 表示如果机器在计划时间没开机,开机后会补跑一次;RandomizedDelaySec=600 会给执行时间加 0-10 分钟的随机抖动,避免多台机器在同一秒同时清理、把共享存储打满。
sudo systemctl daemon-reload
sudo systemctl enable --now log_clean.timer
systemctl list-timers log_clean.timer # 确认下次执行时间
五、风险提示与回滚
清理日志是典型的"不可逆"操作,rm 掉的文件基本找不回来。下面几条务必记住。
第一,永远先试跑。 上面脚本默认试运行就是为此设计。就算用一条命令,也要把 -delete 换成 -print 先看一眼列表,尤其确认 -path、-name 的匹配范围没写错。
第二,不要在根目录或系统目录上乱删。 类似 find / -name '*.log' -delete 这种命令是高危操作,它会扫到 /proc、/sys、/etc 下的匹配项。真要全盘清理,也必须加 -xdev 限定在当前文件系统,并排除 /proc、/sys、/dev。
第三,注意被删除但仍被进程占用的文件。 如果 Nginx 的日志文件被你 rm 了但它没重启,空间不会释放——因为进程还持有文件句柄。正确做法是先 logrotate 切割或 systemctl reload,再清理。用 lsof +L1 能看到这类"已删除但仍占用"的文件。
第四,脚本先备份。 把脚本放到 /usr/local/bin/ 后,用 cp log_clean.sh log_clean.sh.bak 留一份,避免改坏后无从恢复。
回滚方式:定时任务层面,删除 /etc/cron.d/log_clean 或 systemctl disable --now log_clean.timer 即可立即停止清理,不影响已删除文件。已删文件若需恢复,只能依赖备份(如 rsync、快照或对象存储归档)——这也是为什么重要日志建议先归档再删除。
# 更稳妥的归档式清理:先压缩归档,再删除源文件(保留可追溯性)
ARCHIVE=/backup/logs
mkdir -p "$ARCHIVE"
find /var/log/app -type f -name '*.log' -mtime +7 -print0 \
| xargs -0 -r tar -czf "$ARCHIVE/applogs-$(date +%F).tar.gz"
# 压缩成功(无报错)后再删源文件
find /var/log/app -type f -name '*.log' -mtime +7 -delete
常见问题
Q1:logrotate 和 find 清理,到底该用哪个?
优先用 logrotate。它能按天/周切割、压缩、保留指定份数,还有 postrotate 钩子通知服务重新打开日志文件,是最规范的方案。find 清理适合那些不受 logrotate 管理、路径不固定或临时产生的日志。两者不冲突,可以并存。
Q2:删了日志但 df -h 显示空间没释放,为什么?
因为还有进程持有被删文件的句柄。用 lsof +L1 | grep deleted 找到,重启或 reload 对应服务即可释放。这也是为什么直接 rm 正在写的日志是个坏习惯。
Q3:-mtime +7 真的是删 7 天前的吗?
是的,指"最后修改时间在 7×24 小时之前"。"+"代表大于。如果误写成 -mtime -7,删的反而会是最近 7 天的文件,非常危险。建议写完先用 -print 验证。
Q4:日志要保留多久比较合适?
看用途:排障一般 7-15 天够用;等保二级通常建议 6 个月,三级或金融行业可能要求更长。把日志集中收集到日志平台后,本地只留很少天数既省空间又不丢数据,是中大型环境的主流做法。
Q5:多台服务器怎么统一管理清理策略?
少则几台用 Ansible 批量分发脚本和 cron 配置;规模大或对合规有要求,建议接入集中日志平台,本地只保留少量天数,从根上减少清理复杂度。
总结
- 日志不清理迟早写满磁盘,连锁后果包括业务中断甚至无法登录,是必须做的基础维护。
find清理的核心是-mtime/-mmin的时间语义,加号代表"更早",写错方向会误删新文件。- 删除前务必先
-print试跑、确认列表,再用-delete;生产脚本建议设置默认试运行和删除审计日志。 - 定时任务推荐
/etc/cron.d/或 systemd timer,后者支持补跑和随机延迟,多机场景更友好。 - 直接
rm正在写入的日志不会释放空间,需配合logrotate/服务 reload;重要日志建议先压缩归档再删除,保留可追溯性和回滚能力。
企业QQ咨询




