备份策略:3-2-1 原则与一份能自动跑的脚本
服务器会挂、会误删、会被入侵。备份这件事唯一的难点是坚持自动跑,以及真的恢复过一次。
一、3-2-1 原则
- 3 份副本(1 份生产数据 + 2 份备份);
- 2 种不同介质(本地盘 + 对象存储 / 另一台机器);
- 1 份异地(机房火灾不至于全灭)。
个人站点可以简化为:服务器本机保留 7 天 + 异地机器/对象存储保留 30 天。
二、要备份什么
| 内容 | 方式 | 频率 |
|---|---|---|
| 网站文件 | tar / rsync | 每天 |
| 数据库 | mysqldump / pg_dump | 每天 |
| 配置文件 | /etc/nginx、/etc/ssh 打包 | 变更时 |
| 证书 | /etc/letsencrypt 打包 | 每月 |
| 应用数据卷 | docker run --rm -v ... tar | 每天 |
别只备份数据库。我吃过亏:数据库天天有备份,Nginx 配置没备份,重装后一点点重新写。
三、一份完整脚本
#!/usr/bin/env bash
# /opt/scripts/backup.sh
set -euo pipefail
BACKUP_DIR="/var/backups/baize"
KEEP_DAYS=7
STAMP=$(date +%F-%H%M)
mkdir -p "$BACKUP_DIR"
# 1. 网站文件
tar czf "$BACKUP_DIR/www-$STAMP.tar.gz" -C /var/www baize
# 2. 数据库
mysqldump -u root -p"$MYSQL_PWD" --single-transaction --routines baize_db \
| gzip > "$BACKUP_DIR/db-$STAMP.sql.gz"
# 3. 关键配置
tar czf "$BACKUP_DIR/conf-$STAMP.tar.gz" \
/etc/nginx /etc/ssh /etc/letsencrypt 2>/dev/null || true
# 4. 清理旧备份
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$KEEP_DAYS -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
echo "[$(date)] backup done: $STAMP"chmod +x /opt/scripts/backup.sh
# 先手动跑一次,确认没问题再上 cron
MYSQL_PWD=xxx /opt/scripts/backup.sh
ls -lh /var/backups/baize四、定时执行
crontab -e
# 每天凌晨 3 点
0 3 * * * MYSQL_PWD=xxx /opt/scripts/backup.sh >> /var/log/backup.log 2>&1cron 的几个坑:
- 环境变量缺失:cron 里 PATH 很短,脚本里命令最好写绝对路径,或在脚本顶部
export PATH=/usr/bin:/bin:/usr/sbin:/sbin; - 看不到输出:一定要重定向到日志文件,否则失败了也不知道;
- 末尾换行:crontab 文件最后必须有空行,否则最后一条不执行。
更推荐用 systemd timer,能看到状态和日志:
# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup
[Service]
Type=oneshot
Environment=MYSQL_PWD=xxx
ExecStart=/opt/scripts/backup.sh# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl enable --now backup.timer
systemctl list-timers | grep backup
journalctl -u backup.service五、异地同步
# 推到另一台机器(建议用独立的备份专用密钥)
rsync -avz --delete -e "ssh -p 2222 -i /root/.ssh/backup_key" \
/var/backups/baize/ backupuser@backup-host:/backups/baize/
# 推到对象存储(以 rclone 为例)
rclone sync /var/backups/baize remote:baize-backup --transfers 4 -P安全性提醒:执行备份的账号权限要够读数据,但异地端不要给它删除权限(比如对象存储只给写权限),否则服务器被入侵时备份会被一起删掉。
六、恢复演练(最重要的一步)
至少每半年做一次,流程:
# 1. 找一台临时机器 / 临时目录
mkdir -p /tmp/restore && cd /tmp/restore
# 2. 解包文件
tar xzf /var/backups/baize/www-2026-05-28-0300.tar.gz
# 3. 恢复数据库到临时库,确认能查到数据
gunzip < db-2026-05-28-0300.sql.gz | mysql -u root -p restore_test
mysql -u root -p -e "SELECT COUNT(*) FROM restore_test.posts;"
# 4. 核对条数、最新一条记录的时间是否符合预期七、检查清单
- 备份在跑(
systemctl list-timers/ 日志有输出) - 备份文件非空(偶尔会出现 0 字节的假备份)
- 异地副本存在且时间是最新的
- 恢复演练做过至少一次
- 备份脚本里没有明文硬编码密码(用环境变量或
.my.cnf)
# 快速核对备份健康度
ls -lh /var/backups/baize | tail -5
du -sh /var/backups/baize