自己搭的 WordPress,备份是最容易忽略的事
很多人在 VPS 上用 Docker 跑起 WordPress,配好 MySQL,写完第一篇文章,就觉得万事大吉了。但实际运营一段时间后才会意识到一个问题:如果服务器挂了,数据怎么恢复?
数据库丢了,文章、评论、设置全部归零。wp-content/uploads 目录里的图片、附件丢一次就要命。而大多数 VPS 厂商的快照要么是额外收费的,要么恢复起来非常麻烦。
这篇文章会从零开始,讲清楚用 Docker 部署 WordPress + MySQL 的关键要点,然后重点解决一个实际问题:如何用定时任务自动把整个站点(数据库 + 文件)备份到 Cloudflare R2。
全部用免费或低成本方案,不需要额外买备份插件。
Docker Compose 部署 WordPress + MySQL
如果你已经在服务器上跑 WordPress 了,可以直接跳过这段。如果还没有,下面是最简单的 docker-compose 配置。
version: '3.8'
services:
db:
image: mysql:8.0
container_name: wordpress_db
restart: always
volumes:
- ./db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: your_db_password
wordpress:
image: wordpress:6-php8.1-apache
container_name: wordpress_app
restart: always
depends_on:
- db
ports:
- "80:80"
volumes:
- ./wp_data:/var/www/html
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: your_db_password
WORDPRESS_DB_NAME: wordpress
几个关键点:
- db_data 和 wp_data 是命根子,备份的核心对象就是这两个目录。
- MySQL 容器和 WordPress 容器通过 Docker 内部网络通信,域名直接写容器名
db就可以。 - 如果要做 HTTPS,前面加一层 Nginx 反代,或者用 Traefik / Caddy,这不是本文重点。
备份到底要背什么
一个 WordPress 站点有两部分数据:
- 数据库:文章内容、用户、评论、插件配置……全部在 MySQL 里。
- 文件:wp-content/uploads 下的图片、附件、主题文件、插件文件。虽然主题和插件可以从源重新下载,但上传的附件是不可恢复的。
所以备份方案必须同时覆盖这两块。
Cloudflare R2 为什么适合做备份存储
选择存储位置的时候,对比了几种方案:
- 同一台服务器的另一个磁盘:服务器挂了就全没了,等于没备份。
- GitHub Releases / Git LFS:单个文件有大小限制,不适合数据库 dump。
- S3 / 对象存储:主流选择,但 AWS S3 的出站流量要收费。
- Cloudflare R2:兼容 S3 API,存储和出站流量都不收费(免流量费是 R2 最大的优势)。
R2 的免费档是 10GB 存储 + 每月 1000 万次操作,个人博客完全够用。超出的部分价格也很低,每 GB 每月 $0.015。
思路:一行脚本 + crontab + rclone
整个方案的链路很简单:
crontab 定时触发 → 脚本执行 mysqldump 导出数据库 → tar 打包数据库 + wp-content → rclone 上传到 R2 → 清理本地临时文件
全部工具都是开源的,没有额外成本。
第一步:安装 rclone 并配置 R2
rclone 是一个命令行同步工具,支持几乎所有对象存储。
# 安装 rclone
curl https://rclone.org/install.sh | sudo bash
配置 R2 需要先拿到 Access Key 和 Secret Key:
- 登录 Cloudflare Dashboard,进入 R2 页面。
- 点击「管理 R2 API 令牌」→「创建 API 令牌」。
- 权限选「对象读写」,指定一个 Bucket(比如 blog-backup)。
- 记下 Access Key ID 和 Secret Access Key。
然后配置 rclone:
rclone config
按照交互提示:
- name:
r2 - type:
s3 - provider:
Cloudflare - access_key_id: 填入你的 Access Key
- secret_access_key: 填入你的 Secret Key
- endpoint:
https://<你的账户ID>.r2.cloudflarestorage.com - 其余选项默认即可
配置完成后测试一下:
rclone ls r2:blog-backup
第二步:写备份脚本
在服务器上创建一个目录:
mkdir -p /opt/backup
创建 /opt/backup/backup.sh:
#!/bin/bash
# 配置变量
BACKUP_DIR="/tmp/wordpress-backup"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
SITE_NAME="myblog"
R2_BUCKET="blog-backup"
R2_CONF="r2"
# Docker 容器名
DB_CONTAINER="wordpress_db"
WP_VOLUME="/root/wordpress/wp_data"
# 数据库配置(从 docker-compose 的环境变量保持一致)
DB_NAME="wordpress"
DB_USER="wpuser"
DB_PASSWORD="your_db_password"
# 1. 创建临时目录
mkdir -p "$BACKUP_DIR/$TIMESTAMP"
# 2. 导出数据库
docker exec "$DB_CONTAINER" mysqldump -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "$BACKUP_DIR/$TIMESTAMP/db.sql"
# 3. 压缩 wp-content(上传的文件主要在这里)
tar -czf "$BACKUP_DIR/$TIMESTAMP/wp-content.tar.gz" -C "$WP_VOLUME" wp-content/ 2>/dev/null
# 4. 生成一份简单的备份信息
echo "Backup time: $(date)" > "$BACKUP_DIR/$TIMESTAMP/backup-info.txt"
echo "Site: $SITE_NAME" >> "$BACKUP_DIR/$TIMESTAMP/backup-info.txt"
echo "DB size: $(du -sh "$BACKUP_DIR/$TIMESTAMP/db.sql" | cut -f1)" >> "$BACKUP_DIR/$TIMESTAMP/backup-info.txt"
# 5. 上传到 R2
rclone copy "$BACKUP_DIR/$TIMESTAMP" "$R2_CONF:$R2_BUCKET/$SITE_NAME/$TIMESTAMP" --progress
# 6. 清理临时文件
rm -rf "$BACKUP_DIR/$TIMESTAMP"
# 7. 可选:清理 R2 上 30 天前的旧备份,节省存储
rclone delete "$R2_CONF:$R2_BUCKET/$SITE_NAME" --min-age 30d --quiet 2>/dev/null
echo "Backup completed: $TIMESTAMP"
给脚本执行权限:
chmod +x /opt/backup/backup.sh
先手工跑一遍测试能不能用:
bash /opt/backup/backup.sh
如果一切正常,去 Cloudflare R2 的 Dashboard 应该能看到备份文件了。
第三步:配置 crontab 定时执行
crontab -e
加入以下行:
# 每天凌晨 3 点执行备份
0 3 * * * /opt/backup/backup.sh >> /var/log/wordpress-backup.log 2>&1
这样每天凌晨 3 点服务器会自动执行一次完整的备份。日志写入 /var/log/wordpress-backup.log,方便排查问题。
如果想调整频率:
- 每 6 小时:
0 */6 * * * - 每周日凌晨 4 点:
0 4 * * 0 - 每天早晚各一次:
0 3,15 * * *
第四步:验证恢复流程
备份做得再好,如果恢复不了就是废的。建议至少在部署完成后手动验证一次:
# 从 R2 下载备份
rclone copy r2:blog-backup/myblog/20250315_030002 /tmp/restore-test/
# 恢复数据库
cat /tmp/restore-test/db.sql | docker exec -i wordpress_db mysql -uwpuser -pyour_db_password wordpress
# 恢复文件
tar -xzf /tmp/restore-test/wp-content.tar.gz -C /root/wordpress/wp_data/
验证完毕别忘了删掉测试目录。
容易踩坑的几个地方
mysqldump 在 Docker 环境中的路径
上面脚本里用的是 docker exec 直接在 MySQL 容器里执行 mysqldump,这样不用在宿主机装 MySQL 客户端,也不会出现版本不兼容的问题。如果你的 MySQL 不是用 Docker 跑的,改成宿主机路径即可。
数据库密码在脚本里明文
为了简洁,上面脚本直接把密码硬编码了。生产环境建议用环境变量或者用 .env 文件加载:
source /opt/backup/.env
docker exec "$DB_CONTAINER" mysqldump -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > backup.sql
然后把 .env 的权限设为 600:
chmod 600 /opt/backup/.env
备份文件越来越大怎么办
R2 存储价格很低,但如果你每天备份、保留所有版本,还是会上涨的。脚本里已经加了一句 --min-age 30d 自动清理 30 天前的备份。你也可以改成 14d 或 7d,看你自己的需求。
R2 的 endpoint 地址
Cloudflare R2 的 endpoint 格式固定:https://<account-id>.r2.cloudflarestorage.com。account ID 在 Cloudflare Dashboard 的右侧边栏可以找到,写的是「账户 ID」。
最终的备份目录结构
在 R2 里备份的目录结构大概是这样:
blog-backup/
myblog/
20250315_030002/
db.sql
wp-content.tar.gz
backup-info.txt
20250316_030002/
db.sql
wp-content.tar.gz
backup-info.txt
每次备份是一个独立的时间戳目录,包含三个文件。如果需要回滚到某一天,直接下载对应的目录就行。
总结
用 Docker 部署 WordPress 本身不难,麻烦的是后续的维护。备份这件事,早做比晚做好,自动做比手动做好。整套方案用到的工具(Docker、mysqldump、rclone、crontab)都是免费且成熟的,Cloudflare R2 在前 10GB 以内也是免费的。对个人博客来说,这是成本最低、可靠性也足够高的方案。
另外提一句:上面这个备份脚本是针对单机运行的 WordPress 做的。如果你的站点用到了 Redis 缓存、Elasticsearch 或者外部 CDN,需要根据情况调整备份范围。