Linux服务器如何自动清理过期日志?find命令与定时任务实战

日志是服务器上最"温柔"的杀手。它每天安静地增长,你几乎不会察觉,等到某天 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;重要日志建议先压缩归档再删除,保留可追溯性和回滚能力。