有天凌晨三点,我做了个梦:梦见自己手一抖,把网站目录删了。惊醒之后第一个念头不是"还好是梦",而是:如果这是真的,我拿什么恢复?答案是:什么都没有。那天早上送完孩子,我哪儿也没去,直接坐进书房,把备份方案搭了起来。
家里有保险柜放重要文件,服务器也得有个"保险柜"。下面这套方案跑在我这台 2核2G 的机器上,简单、便宜、够用。
先想清楚:备份什么
服务器要备份,家里的证件文件更得备份——我差点因为证件误过事,那篇的教训比这篇还疼。
我的博客是 Hugo 静态站,没有数据库,要备份的东西很清楚:
- 网站源文件:
/var/www/blog整个目录,包括 markdown 原稿、hugo.toml、主题。原稿丢了,博客就只剩个空壳。 - Nginx 配置:
/etc/nginx,特别是我手写的那些 server 块。重装系统后照着恢复,十分钟就能复活。 - 数据库:我这站没有,但脚本里留了
mysqldump的位置。哪天加了个带数据库的服务,直接把那两行注释打开就行,不用重写脚本。
备份目标就一个原则:能让我从零恢复整站的东西,才值得备份。零零碎碎的日志、缓存,不在名单上。
备份脚本:tar 打包,按日期命名
脚本放在 /root/backup.sh,内容不复杂:
#!/bin/bash
BACKUP_DIR="/root/backups"
DATE=$(date +%Y%m%d)
mkdir -p "$BACKUP_DIR"
# 打包网站源文件
tar -czf "$BACKUP_DIR/blog-$DATE.tar.gz" -C /var/www blog
# 打包 Nginx 配置
tar -czf "$BACKUP_DIR/nginx-$DATE.tar.gz" -C /etc nginx
# 如果有数据库,把下面两行打开
# mysqldump -u root -p'你的密码' --all-databases | gzip > "$BACKUP_DIR/mysql-$DATE.sql.gz"
# 只保留最近 7 天的备份,旧的自动删
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete
echo "backup done: $DATE"
几个说明:tar -czf 打包加压缩,一行搞定;文件名带日期,blog-20261024.tar.gz 这种,一眼知道是哪天的;最后两行 find ... -delete 是自动清理,只留 7 天。40G 的硬盘经不起无节制堆备份,7 天对个人博客足够了——真出问题,你不会等到第八天才发现。
给脚本执行权限,手动跑一遍确认能用:
chmod +x /root/backup.sh
/root/backup.sh
ls -lh /root/backups/
看到 tar.gz 文件生成,大小合理,第一步就成了。
定时:cron 每天凌晨跑
crontab -e
加一行:
0 3 * * * /root/backup.sh >> /var/log/backup.log 2>&1
每天凌晨 3 点执行,日志写到 /var/log/backup.log。选凌晨 3 点是因为那会儿没访客(希望我自己也在睡觉)。cron 这东西跟家里的自动喷水定时器一个逻辑:设好就不用管,但偶尔要看一眼它是不是真在工作。
验证 cron 生效,最简单的办法是第二天早上看一眼:
ls -lh /root/backups/
tail /var/log/backup.log
cron 每天凌晨跑,但它要是哪天悄悄罢工了我也不知道,所以我后来给机器装了个会喊疼的监控,免得备份停了几个月才发现。
有新日期的文件,日志里有 “backup done”,说明定时器在干活。
异地:再存一份,别把鸡蛋放一个篮子里
备份放在本机,只能防"我手抖删文件";要是整台 VPS 挂了、服务商跑路了,本机备份跟着一起完蛋。所以每天备份完,再同步一份到别处。
我用的 rclone,把备份目录同步到网盘:
rclone sync /root/backups/ remote:blog-backups/ --min-age 1d
这行也可以塞进 cron,在本地备份完成后执行。--min-age 1d 是只同步一天内的新文件,省流量。至于是 Google Drive、Dropbox 还是另一台 VPS,看你手里有什么——关键是"异地"这两个字,介质不重要。
我现在的链条是:VPS 本地每天打包 → 保留 7 天 → 新备份同步到网盘。两层保险,每年多花 0 美元(网盘用的免费额度,博客备份就几十 M)。
脚本本身也别忘了备份。/root/backup.sh 哪天手抖删了,cron 里那行还在,但执行的脚本没了,等于定时器空转。我把脚本内容贴了一份在私人笔记里,恢复起来就是复制粘贴的事。备份脚本的备份,听起来像绕口令,做起来就一分钟。
服务器这套备份只是我 3-2-1 习惯的一半,个人文件那边的备份逻辑写在另一篇里,思路一模一样:别把鸡蛋放一个篮子里。
实战:我真恢复过一次
备份方案上线第三个月,真用上了。有次我手抖(对,就是梦里那个动作,现实版),rm -rf 多敲了一个路径,博客的静态资源目录没了半边。心跳停了一拍,然后想起:有备份。
恢复过程:
# 1. 找到最近的备份
ls -lt /root/backups/ | head
# 2. 解压覆盖回去
tar -xzf /root/backups/blog-20261024.tar.gz -C /var/www
# 3. 重新构建站点
hugo --minify
从发现到恢复,前后 12 分钟。那 12 分钟里我手是抖的,但心里不慌——这就是备份的意义:它买的不是数据,是镇定。没备份的那次(梦见删库之前),我连抖的资格都没有,只能祈祷。
恢复完我还做了件事:把这次事故记进了笔记,标题就叫"rm -rf 惨案"。运维的经验,都是拿事故换的,换完了不记,下次还得再换一次。
3-2-1 法则:给我的方案打个分
备份界有个 3-2-1 法则:3 份拷贝,2 种不同介质,1 份异地存放。拿我的方案对照一下:
| 要求 | 我的方案 | 得分 |
|---|---|---|
| 3 份拷贝 | 本机 7 天滚动 + 网盘 1 份 + 原稿在 GitHub | ✅ 3 份 |
| 2 种介质 | VPS 硬盘 + 网盘 | ✅ 2 种 |
| 1 份异地 | 网盘(异地) | ✅ 达标 |
满分。不是我多专业,是 Hugo 静态站天生备份友好——原稿在 GitHub 就天然多了一份异地拷贝,纯属沾光。用动态站(WordPress 之类)的朋友,数据库那份异地备份要更上心,mysqldump 的定时任务别偷懒。
别忘了:备份要能恢复才算数
很多人(包括曾经的我)备份完就觉得万事大吉,从没验证过备份包能不能用。这就跟买了灭火器从没看过压力表一样,关键时刻可能掉链子。
我的做法:每季度找个时间,把最新的备份包解压到 /tmp 看一眼:
tar -tzf /root/backups/blog-20261024.tar.gz | head
tar -tzf 只列文件清单不解压,确认包是完整的、文件都在。花一分钟,买个安心。这个习惯我记在了日历上,跟"换空调滤芯"同一个季度提醒——中年人的运维,靠日历驱动。
一句话总结
备份方案不用复杂:tar 按日期打包网站和配置,cron 每天凌晨自动跑,只留 7 天,rclone 同步一份到异地。每季度解压验证一次备份包是好的。先让博客"丢得起",再谈别的折腾。
