crontab 中 rsync 失败主因是环境缺失:需用绝对路径(如 /usr/bin/rsync)、确保 ssh 免密在 crond 上下文中生效、指定完整路径或 cd 切换目录、校准时区、记录日志并测试 --dry-run。

crontab 里写 rsync 命令,但总失败
直接在 crontab -e 里写 rsync -av /src/ user@host:/dst/ 很大概率失败,不是权限问题,而是环境缺失——crond 不加载用户 shell 的 PATH、不读取 ~/.bashrc,更不会自动加载 ssh key agent。
常见错误现象:Permission denied (publickey)、command not found: rsync、No such file or directory(路径是相对的)。
- 必须用绝对路径调用
rsync,比如/usr/bin/rsync(查路径用which rsync) - ssh 免密登录必须基于
crond运行时的用户上下文:root 的定时任务,就用 root 的~/.ssh/id_rsa;普通用户就用对应用户的密钥 - 加
-e "ssh -o StrictHostKeyChecking=no"避免首次连接卡住(仅内网可信环境可用) - 命令开头加
cd / &&或写全路径,避免工作目录不确定导致文件找不到
同步前要确认 ssh 免密是否真生效
很多人以为 ssh user@host 手动能连,定时任务就一定行——错。因为 crond 启动时不会继承你的终端 session,也不会触发 ssh-agent。
验证方法:切换到目标用户,用 crond 的方式模拟执行:
sudo -u username env -i HOME=/home/username /bin/bash -c 'ssh -o ConnectTimeout=5 -o BatchMode=yes user@host echo ok'
如果返回 ok,说明免密真正就绪;否则得检查:~/.ssh/authorized_keys 权限(必须 600)、私钥权限(600)、sshd_config 中的 PubkeyAuthentication yes 是否启用。
时间字段写错导致“看起来没执行”
比如想每小时整点同步,写了 0 * * * * /usr/bin/rsync ...,结果发现只在 0 点执行了一次——很可能是系统时区没对齐,或 crond 服务用的是 UTC 时间。
Linux 性能分析与调优专家,覆盖 CPU、内存、磁盘 I/O、网络、内核参数、编译优化、容器/K8s。适用场景:系统卡顿/高负载、内存不足/OOM/Swap 高、CPU 异常/iowait 高。
查当前 crond 解析的时间基准:
sudo systemctl status crond | grep "Active:"
再看系统时区和实际时间:
date; timedatectl status | grep "Time zone"
- 若显示
UTC而你期望按北京时间(CST)跑,要么改系统时区(timedatectl set-timezone Asia/Shanghai),要么把 cron 时间换算成 UTC(比如北京时间 2:00 = UTC 18:00) - 别用
*/2 * * * *这种写法做“每两小时”,它从 0 点开始计,实际是 0、2、4…点;如需从 1 点起,得写0 1-23/2 * * * - 测试阶段建议先设成
* * * * *(每分钟),确认命令能跑通,再收紧时间粒度
日志和错误捕获不能靠邮件或静默失败
MAILTO=root 在 /etc/crontab 里看似方便,但生产环境邮件常被丢、被过滤,根本收不到失败通知。
更可靠的做法是重定向输出并记录到文件:
0 2 * * * /usr/bin/rsync -av -e "ssh -o StrictHostKeyChecking=no" /data/ backup@192.168.1.100:/backup/ >> /var/log/rsync_backup.log 2>&1
- 注意
>> /path/log是追加,> /path/log是覆盖;用2>&1把 stderr 合并进 stdout 才能一并记录 - 定期轮转日志,避免撑爆磁盘:
logrotate配置或脚本里加find /var/log/rsync_*.log -mtime +7 -delete - 加个简单校验:同步后
ssh backup@192.168.1.100 'ls -l /backup/ | wc -l'写入日志,确认远端文件数变化
最易被忽略的点:rsync 的 --delete 参数一旦误配,会清空目标目录;哪怕只是测试,也要先加 --dry-run 看输出,确认删哪些、传哪些,再删掉这个参数正式跑。










