Linux服务器如何实现异地备份?rsync、SSH与自动化任务配置

一台跑在华东机房的业务服务器,本地做了完整的每日备份,备份盘 2TB 用了 60%。某天机房市电切换异常,整排机柜断电 4 小时,业务停了,本地备份因为和主机在同一机柜,一样拿不到。更常见的情况是:机器被入侵,攻击者先 rm -rf 了备份目录再加密业务数据。这时候唯一能救命的,是放在另一个城市、另一台机器上的那份副本。

异地备份(offsite backup)是 3-2-1 原则里"1"的具体落地。对中小企业和站长来说,成本最低、可控性最高的方案就是:一台异地 Linux 服务器 + rsync + SSH 免密通道 + crontab 定时。整套方案不需要购买商业软件,全部由系统自带工具实现,出问题也能自己排查。

一、通道设计:备份专用账号与 SSH 免密

异地备份的第一条纪律:绝不用 root 做跨机备份。生产机上创建一个专用的 backup 账号,只赋予读权限;备份机上为它创建同名账号,并限制其只能通过 rsync 工作。这样即使备份机被攻陷,攻击者拿到的也只是一个只读账号,无法反向写入生产机。

1.1 在备份机(目标端)创建账号并生成密钥

# 备份机:创建专用账号,禁止登录 shell
useradd -m -s /bin/bash backup
passwd -l backup          # 锁定密码,只允许密钥方式

# 切换到 backup 用户生成密钥对
su - backup
ssh-keygen -t ed25519 -C "backup@offsite" -f ~/.ssh/id_ed25519
# 一路回车,不设 passphrase(无人值守脚本需要)

# 查看公钥,稍后写入生产机
cat ~/.ssh/id_ed25519.pub

这里用 ed25519 而不是 RSA:ed25519 密钥短、生成快、安全性不输 RSA 3072。如果目标系统较老(OpenSSH < 6.5)不支持 ed25519,则退回到 ssh-keygen -t rsa -b 4096。

1.2 在生产机(源端)部署公钥并做命令限制

# 生产机:创建同名只读账号
useradd -m -s /bin/bash backup
passwd -l backup

# 写入公钥,并在前面加 command 限制,使其只能执行 rsync
su - backup
mkdir -p ~/.ssh && chmod 700 ~/.ssh
cat >> ~/.ssh/authorized_keys <<'EOF'
command="/usr/bin/rsync --server --sender -vlDtprze.iLsfxCIvu . /data",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-ed25519 AAAA... backup@offsite
EOF
chmod 600 ~/.ssh/authorized_keys

authorized_keys 里的 command="..." 是这套方案的关键安全措施:它把该密钥绑定到唯一一条命令,任何尝试用这把密钥执行 bash、rm 或反向 rsync 拉取其他目录的行为都会被拒绝。注意 command 中的路径必须与备份机实际发起的路径一致,否则会报 "protocol version mismatch" 或静默失败。

1.3 确保备份账号有读权限

/data 目录若属 root 且权限 700,backup 账号读不到。推荐用 ACL 精确授权,而不是粗暴地 chmod 755:

# 授予 backup 用户对 /data 的只读 + 遍历权限
setfacl -R -m u:backup:rx /data
setfacl -R -d -m u:backup:rx /data    # 默认 ACL,新建文件自动继承

# 验证:切换到 backup 用户,确认可读不可写
su - backup -c "ls /data && touch /data/testfile"

最后一条命令应该输出目录列表,并在 touch 时报 "Permission denied"。如果 touch 成功了,说明权限给大了,要收回来。

二、rsync 核心参数与第一次同步

rsync 的精髓是"增量传输":它先对比源端与目标端文件的大小与修改时间(quick check),只传差异部分。常用参数组合如下:

参数含义使用建议
-a归档模式,等价于 -rlptgoD必选,保留权限/时间/软链
-v显示详细过程调试时使用,生产建议去掉
-z传输时压缩跨公网推荐;内网可不加以省 CPU
-P等价于 --partial --progress断点续传 + 进度,大文件必选
--delete删除目标端多余文件,保持严格一致慎用,见下文说明
--link-dest与指定目录做硬链接,未变化文件不占空间多版本快照核心参数
--bwlimit限速,单位 KB/s避免打满业务带宽
--exclude排除匹配路径排除缓存/临时目录
-e指定远程 shell如 -e "ssh -p 2222 -i key"

2.1 基础异地同步命令

# 备份机上执行:从生产机拉取 /data 到本地
rsync -aPz --delete \
    --exclude='cache/' \
    --exclude='*.tmp' \
    --exclude='/data/logs/' \
    --bwlimit=8000 \
    -e "ssh -p 22 -i /home/backup/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new" \
    backup@203.0.113.10:/data/ \
    /backup/offsite/data/

# 检查退出码:0 成功,23/24 为部分传输错误,需告警
echo "rsync exit code: $?"

注意源路径结尾的斜杠:/data/ 表示同步目录内容,/data(无斜杠)表示同步目录本身。这个细节写错会导致目标端多出一层 data 目录,是新手最常见的坑。

--delete 是把双刃剑:它能保证两端严格一致,但如果生产机误删了文件,下一次同步会把备份端的副本也删掉。正确做法是:在使用 --link-dest 做多版本快照时不加 --delete,让每个版本独立保存;只在"当前版本"目录上,配合延迟执行(比如删除操作先记录到日志、24 小时后才真正执行)使用。

三、用 --link-dest 做多版本快照

这是异地备份最有价值的一步。利用硬链接,可以让每天的备份看起来是一个完整目录,但实际只占用"变化文件"的磁盘空间。10 天的备份,视觉上是 10 份全量,磁盘占用可能只有 1.2 份。

#!/bin/bash
# /usr/local/bin/offsite_backup.sh
set -euo pipefail

SRC_HOST="203.0.113.10"
SRC_USER="backup"
SRC_PATH="/data/"
BASE_DIR="/backup/offsite"
CUR="$BASE_DIR/$(date +%F-%H%M)"
LATEST="$BASE_DIR/latest"
SSH_OPTS="ssh -p 22 -i /home/backup/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new"

mkdir -p "$CUR"

# 若已存在上一版本,则以它为硬链接基准
if [ -d "$LATEST" ]; then
    LINK_DEST="--link-dest=$LATEST"
else
    LINK_DEST=""
fi

rsync -az $LINK_DEST \
    --exclude='cache/' --exclude='*.tmp' \
    --bwlimit=8000 \
    -e "$SSH_OPTS" \
    "$SRC_USER@$SRC_HOST:$SRC_PATH" "$CUR/"

# 只有 rsync 成功才更新 latest 指针
if [ $? -eq 0 ]; then
    rm -f "$LATEST"
    ln -s "$(basename "$CUR")" "$LATEST"
    echo "$(date '+%F %T') backup OK: $CUR" >> /var/log/offsite_backup.log
else
    echo "$(date '+%F %T') backup FAILED" >> /var/log/offsite_backup.log
    rmdir "$CUR" 2>/dev/null || true
    exit 1
fi

# 清理 30 天前的旧版本(硬链接自动回收空间)
find "$BASE_DIR" -maxdepth 1 -type d -name "20*" -mtime +30 -exec rm -rf {} +

关键点:--link-dest 指向的目录必须存在,否则参数被忽略(rsync 不会报错,只会安静地做一次全量复制,磁盘瞬间翻倍)。脚本里用 if [ -d "$LATEST" ] 做了保护。另外,latest 用软链接而不是硬链接目录,避免 find -mtime 误判。

四、crontab 自动化任务配置

脚本写完不等于自动化完成,必须挂到定时任务并配置好输出与告警。

4.1 配置定时任务

# 以 backup 用户编辑定时任务
crontab -u backup -e

# 每天凌晨 3:10 执行异地备份
10 3 * * * /usr/local/bin/offsite_backup.sh >> /var/log/offsite_backup.log 2>&1

# 每周日凌晨 4:00 做一次全量校验
0 4 * * 0 /usr/local/bin/verify_backup.sh >> /var/log/verify_backup.log 2>&1

# 查看已配置任务
crontab -u backup -l

cron 的环境变量和交互式 shell 不同,PATH 通常只有 /usr/bin:/bin。如果脚本里调用了 /usr/local/bin 下的工具(如 mysql、aws),必须在脚本开头显式声明:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH

另外要避开整点:多个任务同时触发会造成 I/O 风暴。选 :10、:37 这类错峰时间更好。

4.2 防止任务重叠执行

如果一次备份耗时超过 24 小时(大数据量首次同步常见),下一次 cron 会再启一个进程,两个 rsync 同时写同一目录会造成数据损坏。用 flock 加锁:

# 在 crontab 中用 flock 保证同一时刻只有一个实例
10 3 * * * /usr/bin/flock -n /tmp/offsite_backup.lock /usr/local/bin/offsite_backup.sh >> /var/log/offsite_backup.log 2>&1

-n 表示拿不到锁立即退出(而不是等待),避免任务堆积。也可以配合 -w 60 等待 60 秒。查看是否发生过重叠:grep -c "backup OK" /var/log/offsite_backup.log 与实际运行天数对比。

五、验证与故障排查

异地备份最容易出现的三种失败:SSH 握手失败、权限不足、磁盘满。下面是标准排查步骤。

  1. 手动跑一次,不要直接丢给 cron:先 su - backup,再手动执行脚本,观察完整输出。
  2. 单独验证 SSH 通道:ssh -p 22 -i ~/.ssh/id_ed25519 backup@203.0.113.10 "echo ok",能返回 ok 才算通。
  3. 用 --dry-run 预演:rsync -avz --dry-run ... 只列出将要传输的文件,不实际传输,用于确认路径和排除规则是否正确。
  4. 检查目标端磁盘:df -h /backup,剩余空间低于备份总量 1.5 倍时告警。
  5. 校验数据一致性:在源端和目标端分别执行 sha256sum,比对结果。
  6. 确认 cron 真的跑了:grep CRON /var/log/syslog 或 journalctl -u cron,很多"备份没执行"其实是 cron 服务没启动。
# 两端分别生成校验清单并比对
# 源端(生产机)
find /data -type f -exec sha256sum {} + | sort > /tmp/src.sha256
# 目标端(备份机)
cd /backup/offsite/latest && find . -type f -exec sha256sum {} + | sort > /tmp/dst.sha256
# 比对
diff /tmp/src.sha256 /tmp/dst.sha256 | head -20

六、异地备份的保留周期与容量规划

多版本快照虽然靠硬链接节省了空间,但只有内容完全未变的文件才是共享的,任何一个被修改的文件都会在新快照中独占一份实际空间。因此容量规划不能简单按"单份数据大小 × 版本数"计算,而要按"每日变更量 × 保留天数 + 一份全量基线"来估算。

举例说明估算方法:假设原始数据 200GB,日均变更 5GB,保留 30 天,那么实际占用约为 200GB + 5GB × 30 = 350GB,而不是 200GB × 30。判断标准是:备份目录的实际占用应该显著小于"份数 × 全量大小",如果接近后者,说明 --link-dest 没有生效,需要回头检查基准路径是否写成了相对路径——这是最常见的失效原因。

# 区分"逻辑大小"与"实际占用",验证硬链接是否生效
du -sh --apparent-size /backup/offsite/2025-01-10-0200   # 逻辑大小(看起来的一份完整副本)
du -sh /backup/offsite/2025-01-10-0200                   # 实际占用(硬链接共享后)
du -csh /backup/offsite/* | tail -1                      # 全部快照的总占用
df -h /backup

# 清理超过保留周期的历史快照(先打印再删除)
find /backup/offsite -maxdepth 1 -type d -mtime +30 -print
find /backup/offsite -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +

删除硬链接快照时可以直接用 rm -rf 删除整个快照目录:因为硬链接的特性,只有当最后一个指向该 inode 的目录项被删除后,磁盘空间才会真正释放,所以删除中间某个版本不会影响其他版本的完整性。这一点是硬链接快照相对于传统增量备份链的最大优势——没有链条依赖,删任何一份都不会导致其他份失效。

小结

Linux 异地备份的完整链路是:专用只读账号 → SSH 密钥 + command 限制 → rsync -a --link-dest 多版本快照 → crontab + flock 定时防重叠 → sha256sum 校验。这套方案的全部组件都是系统自带的,成本只是一台异地服务器的租用费用,取决于机房、带宽与磁盘容量,通常需要联系服务商按配置询价。

避坑清单

  • 不要给备份账号写权限,也不要用 root;authorized_keys 里务必加 command= 限制。
  • --delete 会同步删除动作,多版本快照方案下不要与 --link-dest 混用。
  • 源路径结尾斜杠:/data/ 同步内容,/data 同步目录本身。
  • --link-dest 目标不存在时 rsync 静默降级为全量复制,务必先判断目录存在。
  • cron 环境 PATH 极简,脚本内必须显式声明。
  • 备份任务务必加 flock,否则长任务重叠会写坏数据。
  • 公网传输要加 --bwlimit,否则备份流量会挤占业务带宽。
  • 只验证"文件存在"不够,必须定期做一次真实恢复演练。