rsync异地备份实战
VPS数据只有一份等于没有。小够手把手教你用rsync做异地备份:密钥免密、增量同步、定时任务、恢复演练全流程。跟着做就行,够用不折腾。
我是小够,只推够用的 VPS。
先说句得罪人的话:VPS 里的数据如果只有一份,那它等于没有。商家跑路、硬盘损坏、误删数据库、手滑 rm -rf——小够见过太多"我以为不会轮到我"的案例。备份不是技术问题,是习惯问题。这篇教你用 rsync 搭一套异地备份,免费、可靠,配一次长期受益。
先建立备份观:3-2-1 原则
业界经典的 3-2-1 原则,背下来:
- 3:重要数据至少保留 3 份副本。
- 2:副本放在 2 种不同介质上(比如 VPS 硬盘 + 对象存储)。
- 1:其中 1 份放在异地(不同机房、不同城市,最好不同国家)。
为什么强调异地?同一机房的两台机器,可能共用电力、网络、甚至同一批硬盘。机房级故障一来,全灭。异地备份防的是这种"一锅端"。
备份频率看数据价值:每天更新的博客,至少每天备一次;静态小站,一周一次也行。记住:备份的价值不在于"备了",而在于"恢复得了"。文末有恢复演练,别跳过。
rsync 是什么,为什么选它
rsync 是 Linux 下的文件同步神器,几乎所有发行版自带(没有就 apt install rsync / yum install rsync)。选它的理由:
- 增量传输:只传变化的部分。第一次全量同步可能很慢,之后每天只传新增和修改的文件,几分钟搞定。
- 走 SSH:数据加密传输,不用额外搭服务,VPS 之间直接能用。
- 免费:不像某些备份软件按量收费,rsync 只有目标机器的存储成本。
它的基本语法长这样:
rsync -avz /本地目录/ 用户@备份机IP:/远程目录/
参数记住三个就够:-a 归档模式(保留权限、时间、属主),-v 显示过程,-z 传输时压缩。注意源目录末尾的斜杠:/本地目录/ 表示同步目录里的内容,/本地目录(无斜杠)表示把目录本身也同步过去。这个小斜杠是新手踩坑重灾区。
实战一:网站目录全量同步
假设你的网站在 /var/www/html,备份机 IP 是 203.0.113.10,先手动跑一次全量:
rsync -avz /var/www/html/ [email protected]:/backup/www/
第一次会传所有文件,之后每天再跑,只传变化的。为了安全,建议在备份机上用非 root 用户,配合密钥登录(下一节讲)。
如果你想在目标端保留删除记录(源删了目标也删,保持完全一致),加 --delete:
rsync -avz --delete /var/www/html/ [email protected]:/backup/www/
警告:--delete 是双刃剑。源目录如果被误删或清空,下一次同步会把备份也清空。稳妥的做法是备份端做版本保留(后面讲 --link-dest),而不是依赖 --delete 镜像。
实战二:数据库先导出再同步
数据库不能直接 rsync 数据文件(正在写的文件拷过去可能是坏的)。正确姿势:先导出为 SQL,再同步导出文件。
MySQL/MariaDB:
mysqldump -u root -p'你的密码' --all-databases | gzip > /backup/db-$(date +%F).sql.gz
PostgreSQL:
pg_dumpall -U postgres | gzip > /backup/db-$(date +%F).sql.gz
然后把 /backup/ 整个目录 rsync 到异地。注意密码别写在历史命令里明文裸奔,生产环境建议用 .my.cnf 配置文件存凭证并设 600 权限。
备份文件名带日期,方便按天找回。配合保留策略(比如只留最近 30 天),写个清理:
find /backup -name "db-*.sql.gz" -mtime +30 -delete
实战三:SSH 密钥免密(定时任务的前提)
定时备份不能每次输密码,得配密钥。在源机器上生成密钥(已有可跳过):
ssh-keygen -t ed25519 -f ~/.ssh/backup_key -N ""
把公钥拷到备份机:
ssh-copy-id -i ~/.ssh/backup_key.pub -p 端口 用户@备份机IP
然后测试免密登录:
ssh -i ~/.ssh/backup_key -p 端口 用户@备份机IP "echo ok"
为了安全,建议给备份用户做最小授权:备份机上把该用户的 shell 限制一下,或者用 ~/.ssh/authorized_keys 里的 command= 限制只能执行 rsync。家用小站可以先跑起来,有精力再收紧。
如果备份机 SSH 不是默认 22 端口,rsync 里这样指定:
rsync -avz -e "ssh -i ~/.ssh/backup_key -p 2222" /var/www/html/ 用户@备份机IP:/backup/www/
进阶:硬链接增量备份(每天一个完整快照)
只保留一份"最新同步"有个问题:想找回三天前误删的文件,没辙。用 --link-dest 可以每天生成一个完整目录快照,但没变化的文件用硬链接,不占额外空间:
#!/bin/bash
SRC="/var/www/html/"
DST="用户@备份机IP:/backup/www"
DATE=$(date +%F)
LINKDEST="/backup/www/latest"
rsync -avz --delete \
-e "ssh -i ~/.ssh/backup_key -p 2222" \
--link-dest=$LINKDEST \
$SRC $DST/$DATE/
ssh -i ~/.ssh/backup_key -p 2222 用户@备份机IP "rm -f /backup/www/latest && ln -s $DATE /backup/www/latest"
原理:--link-dest 指向昨天的快照,没变化的文件直接硬链接过去,只存一份数据。每天一个日期目录,看起来是全量,实际只多占变化部分的空间。30 天的快照,空间占用可能只有全量的 1.2 倍。这才是"既能回滚,又不烧钱"的正确姿势。
快照保留策略:每天一个留 7 天,每周一个留 4 周,每月一个留 3 个月。写个清理脚本按规则删旧快照,硬链接删目录不影响其他快照的数据。
定时任务:让备份自己跑
把上面的脚本存为 /root/bin/backup.sh,加执行权限,然后写 cron:
crontab -e
加一行(每天凌晨 3 点跑):
0 3 * * * /root/bin/backup.sh >> /var/log/backup.log 2>&1
凌晨跑是为了避开业务高峰。日志重定向到文件,方便排查。关键一步:备份脚本自己要加告警。脚本末尾判断 rsync 退出码,非 0 就发通知(邮件、Telegram Bot、Server 酱都行)。没有告警的备份等于没有备份——失败了你都不知道。
简单的退出码检查:
if [ $? -ne 0 ]; then
echo "备份失败 $(date)" | mail -s "备份告警" [email protected]
fi
备份目标选哪
rsync 的另一端放哪,有几个选项:
- 另一台便宜 VPS:最推荐。不同商家、不同地区,比如主站用 Vultr 新加坡,备份机用 RackNerd 洛杉矶年付小鸡,几十块一年。异地、异商家,双保险。
- 家用 NAS / 旧电脑:零成本,但家里宽带上传慢、断电断网是常态,适合做第三副本,不适合做唯一异地副本。
- 对象存储:rsync 不直接支持 S3,需要 rclone 中转。rclone 的
sync命令和 rsync 思路一样,支持各大对象存储,适合数据量大的场景。
小够的推荐组合:主 VPS + 便宜年付备份 VPS(rsync 每日)+ 对象存储(rclone 每周),正好凑齐 3-2-1。
恢复演练:备份的下半场
每季度做一次恢复演练,流程:
- 在备份机上找个历史快照,确认文件都在、数据库 SQL 能解压。
- 拿一台测试机(大厂云按小时开一台,用完删),把网站目录和数据库恢复上去。
- 确认网站能打开、能登录后台、数据是新的。
演练一次,你才知道备份链条里哪一环是纸糊的。多少人的"备份"在真要用时发现:密钥过期了、cron 早停了、磁盘满了三个月没备上。
完整备份脚本示例(拿去改改就能用)
把前面讲的拼成一个完整脚本,注释写清楚了,改路径和 IP 就能跑:
#!/bin/bash
# 小够的每日备份脚本:网站目录 + 数据库导出 + rsync 异地 + 告警
set -e
# ===== 配置区(按你的实际情况改)=====
WEB_DIR="/var/www/html"
LOCAL_BAK="/backup"
BAK_USER="backup"
BAK_HOST="203.0.113.10"
BAK_PORT="2222"
SSH_KEY="/root/.ssh/backup_key"
DB_PASS="你的数据库密码"
KEEP_DAYS=30
NOTIFY_EMAIL="[email protected]"
DATE=$(date +%F)
LOG="/var/log/backup.log"
log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }
log "备份开始"
# 1. 导出数据库
mkdir -p "$LOCAL_BAK"
mysqldump -u root -p"$DB_PASS" --all-databases --single-transaction \
| gzip > "$LOCAL_BAK/db-$DATE.sql.gz"
log "数据库导出完成"
# 2. 打包网站目录(本地留一份,恢复快)
tar -czf "$LOCAL_BAK/www-$DATE.tar.gz" -C /var/www html
log "网站目录打包完成"
# 3. rsync 推到异地(网站实时目录 + 本地备份包)
rsync -avz --delete -e "ssh -i $SSH_KEY -p $BAK_PORT" \
"$WEB_DIR/" "$BAK_USER@$BAK_HOST:/backup/www/$DATE/" \
--link-dest=/backup/www/latest >> "$LOG" 2>&1
rsync -avz -e "ssh -i $SSH_KEY -p $BAK_PORT" \
"$LOCAL_BAK/" "$BAK_USER@$BAK_HOST:/backup/local/" >> "$LOG" 2>&1
ssh -i "$SSH_KEY" -p "$BAK_PORT" "$BAK_USER@$BAK_HOST" \
"rm -f /backup/www/latest && ln -s $DATE /backup/www/latest"
log "异地同步完成"
# 4. 清理本地旧备份
find "$LOCAL_BAK" -name "db-*.sql.gz" -mtime +$KEEP_DAYS -delete
find "$LOCAL_BAK" -name "www-*.tar.gz" -mtime +$KEEP_DAYS -delete
ssh -i "$SSH_KEY" -p "$BAK_PORT" "$BAK_USER@$BAK_HOST" \
"ls /backup/www/ | head -n -$KEEP_DAYS | xargs -r -I{} rm -rf /backup/www/{}"
log "旧备份清理完成"
log "备份成功"
注意 set -e:任何一步失败脚本直接退出,配合 cron 的邮件告警,你能第一时间知道。--single-transaction 是 InnoDB 热备的关键参数,导出时不锁表,网站不用停机。
备份清单:这些最容易漏
很多人备份只备了网站目录,真恢复时发现缺这缺那。对照这张清单:
- 网站程序目录(
/var/www) - 数据库导出(SQL 文件)
- Web 服务器配置(
/etc/nginx/、/etc/apache2/) - SSL 证书(
/etc/letsencrypt/,或者直接重新申请也行) - PHP/应用配置(
php.ini、.env文件) - 定时任务(
crontab -l导出一份) - 防火墙规则(
iptables-save/ ufw 配置) - SSH 密钥和服务器登录信息(放密码管理器)
- 域名 DNS 配置截图(换机器时对照)
.env 文件尤其容易漏:里面有数据库密码、API 密钥,丢了网站起不来,泄露了更麻烦。备份时单独加密存放。
rclone 对接对象存储(第二异地副本)
rsync 搞定了 VPS 到 VPS,再加一个对象存储副本,3-2-1 就齐了。rclone 是 rsync 的云存储版本,配置一次:
# 安装
curl https://rclone.org/install.sh | sudo bash
# 配置(交互式,选你的对象存储厂商,按提示填密钥)
rclone config
配好后同步命令和 rsync 异曲同工:
# 同步到对象存储(增量)
rclone sync /backup/ remote:bucket-name/vps-backup/ --progress
# 保留历史版本(不删除目标端多余文件)
rclone copy /backup/ remote:bucket-name/vps-backup/ --progress
sync 和 copy 的区别对应 rsync 加不加 --delete,重要数据建议用 copy,宁可目标端多占空间,不冒误删风险。对象存储按量计费,几十 GB 的备份一个月也就几块钱。
备份频率和保留策略怎么定
备份不是越频繁越好,是"丢得起多少"决定"多久备一次"。给你一个决策框架:
先算 RPO(能接受的最大数据丢失量)。每天更新的博客,RPO 就是 1 天,备份频率至少每天一次;订单系统 RPO 可能是 1 小时,那就得每小时备数据库(binlog 增量 + 定时全量)。
再定保留期(GFS 策略)。祖父-父亲-儿子轮换:每天的备份留 7 天(儿子),每周的留 4 周(父亲),每月的留 6-12 个月(祖父)。这样既能找回昨天误删的文件,也能在半年后发现数据被篡改时有旧版本可查。
存储成本公式:日增量 × 保留天数 + 全量 × 几个版本。按这个公式反推备份机硬盘买多大,别买小了备份一半写不进去。
一个小站的典型配置:每天凌晨全量数据库 + 增量网站文件,本地留 7 天,异地快照留 30 天,对象存储留 3 个月。成本:一台便宜年付 VPS + 几块钱对象存储,足够。
真出事了:30 分钟恢复流程
备份的终极考验是恢复。假设主 VPS 挂了,按这个流程走:
- 第 0-5 分钟:开一台新机器(大厂云按小时计费,选离你用户近的机房),装好系统。
- 第 5-15 分钟:从备份机把最新快照拉回来:
rsync -avz 备份机:/backup/www/latest/ /var/www/html/,数据库 SQL 传回来导入。 - 第 15-25 分钟:恢复 Nginx 配置、SSL 证书(Let’s Encrypt 重新申请也行,DNS 验证几分钟),起服务。
- 第 25-30 分钟:改 DNS 解析到新 IP,确认网站能打开,登录后台验证数据。
平时演练过一次,真出事时 30 分钟能搞定;没演练过,3 小时手忙脚乱还不一定成。DNS 的 TTL 提前设短一点(比如 300 秒),切换时生效更快。
常见坑汇总
- 斜杠坑:
/dir/和/dir含义不同,写脚本前先手动试一次,--dry-run参数可以先演练不真传。 --delete误删:源目录清空会导致备份被同步清空,重要数据别用纯镜像,用--link-dest版本快照。- 权限坑:用 root 跑 rsync 最省心,但备份机上建议用专用低权限用户,
.ssh目录权限必须是 700,authorized_keys必须是 600,否则 SSH 拒绝密钥登录。 - 时间坑:两台机器时区不一致会导致增量判断异常,都设成 UTC 或都设成北京时间,保持一致。
- 只备文件不备配置:Nginx 配置、cron 任务、SSL 证书、环境变量,这些也要进备份清单。恢复时缺了配置,网站照样起不来。建议单独一个目录备
/etc/nginx、/etc/cron.d这类配置。
备份健康度自查表
对照打勾,全勾上才算备份真正落地:
- 备份任务每天自动跑,连续 7 天成功
- 失败告警能收到(故意搞失败一次,验证告警通路)
- 异地备份机和主 VPS 不是同一商家、不同地区
- 做过一次完整恢复演练,网站能正常打开
- 数据库 SQL 文件能正常解压、导入
- 备份清单里的配置项(Nginx、cron、.env)都在备份里
- 保留策略生效,旧备份按时清理,磁盘没爆
- 备份机 SSH 密钥、登录信息记在密码管理器里
8 项里缺任何一项,备份链条就有断点。花一个周末全搞定,之后每天只花 0 分钟——自动化就是干这个的。备份这件事,开始永远比完美重要,先跑起来,再慢慢补齐。
常见问题
Q:备份机买多大配置? A:备份机只存文件、跑 rsync,1 核 1G 足够,关键是硬盘要够大(至少是源数据量的 3 倍,留给多版本快照)。便宜年付小鸡是备份机的完美人选。
Q:rsync 和 rclone 选哪个? A:VPS 到 VPS 用 rsync,简单直接;VPS 到对象存储(S3、OSS、COS)用 rclone。两者思路相通,都会增量,都值得学。
Q:备份需要加密吗? A:传输走 SSH 本身就是加密的。存储端如果放的是敏感数据(用户数据库),建议备份前先加密再传,比如用 gpg。普通博客内容不加密问题也不大。
Q:多久备份一次合适? A:看数据更新频率和你能接受的最大丢失量。每天更新的站,每天一次;几乎不变的静态站,一周一次。数据库比文件更值得高频备份。
Q:备份脚本跑失败了怎么第一时间知道? A:脚本里加退出码检查 + 告警推送(邮件/TG Bot)。另外建议在备份机上反向检查:每天备份目录的时间戳是不是新的,旧了就说明源端没传过来。
Q:备份机和主 VPS 用同一家商家行不行? A:能用,但不推荐。同一商家可能共用底层设施和账单系统,商家出问题会连坐。异地备份的"异"字,指的是故障域隔离:不同商家、不同地区才算数。
Q:网站数据量很大(几百 GB),每天全量传不动怎么办?
A:rsync 本来就是增量的,第一次全量慢,之后只传变化部分。如果单日增量也很大,考虑:排除缓存目录(--exclude)、压缩传输(-z)、错峰传输、或者升级备份机带宽。真到 TB 级,考虑对象存储 + 生命周期归档。