CentOS香港服务器部署WordPress,如何用脚本实现重复部署与失败回滚
假设我们在一台香港服务器上重复部署 WordPress:第一次安装完成后,第二次执行脚本更新核心文件,网站却出现 502、500 或“建立数据库连接时出错”。这类问题通常不是 WordPress 单点故障,而是脚本同时覆盖了配置、权限、PHP-FPM、数据库或当前版本目录,导致失败后没有可回退的状态。

如果你是按“香港服务器centos系统部署wordprss常见问题”来搜索,建议不要先反复重装。我们的排查顺序应当是:先确认 CentOS 版本与服务监听状态,再检查 Nginx 到 PHP-FPM 的连接,随后验证数据库和文件权限,最后才进入 WordPress 核心、插件和主题层面。部署脚本则采用“新版本目录 + current 软链接 + 配置独立保存 + 数据库备份”的方式,让重复执行不会覆盖正在运行的版本,失败时可以快速切回。
先确认脚本适用的 CentOS 环境
下面的示例以 CentOS Stream 9 为基础,使用 dnf、Nginx、PHP-FPM、MariaDB 和 SELinux。
先确认系统版本:
cat /etc/os-release
uname -m
如果输出为 CentOS Linux 7,不要直接执行下面的脚本。CentOS 7 已经结束生命周期,系统仓库、PHP 版本和安全更新都可能无法满足新的 WordPress 部署要求。此时应先完成系统迁移,或者单独准备与旧系统软件版本匹配的部署方案。
继续操作前,还需要确认:
- 已使用
root或具备sudo权限的账号; - 域名已经解析到香港服务器公网地址;
- 服务器没有被其他 Nginx、Apache 或 PHP-FPM 配置占用;
- 已经准备数据库名称、数据库用户和密码;
- 第一次执行脚本前,确认
/var/www下没有需要保留的旧站点文件; - 修改服务配置前,已经保留必要的数据库和网站文件备份。
我们先把部署状态拆开
重复部署最容易出错的做法,是直接把新文件解压到当前网站目录,例如:
tar -xzf wordpress.tar.gz -C /var/www/example.com
这样会把新旧文件混合在一起。旧插件、残留配置和新核心文件可能互相影响,脚本也无法判断哪些文件属于本次部署。
更稳妥的目录结构如下:
/var/www/example.com/
├── current -> releases/20261001-120000/
├── releases/
│ ├── 20260928-090000/
│ └── 20261001-120000/
├── shared/
│ ├── wp-config.php
│ └── wp-content/
└── backups/
其中:
releases保存每一次独立的 WordPress 核心文件;current是 Nginx 实际访问的版本;shared/wp-content保存上传文件、插件和主题;shared/wp-config.php保存数据库连接配置;backups保存数据库导出和网站内容备份。
发布新版本时,只创建新的 releases/时间目录,验证通过后再切换 current。如果新版本启动失败,只需要将 current 指向旧目录,不需要重新解压整个网站。

图示对应原文命令:releases/时间目录。
使用环境文件固定配置
先创建一个只允许 root 读取的配置文件。下面的版本号只是示例,实际部署时应替换为已经确认的 WordPress 版本。
cat > /root/wp-example.env <<'EOF'
DOMAIN=example.com
APP_BASE=/var/www/example.com
DB_NAME=wp_example
DB_USER=wp_example
DB_PASS='ChangeThisPassword_2026'
WP_VERSION=6.6.2
EOF
chmod 600 /root/wp-example.env
这里不建议把数据库密码直接写进公共脚本或命令历史。后续重复执行时,始终使用同一个环境文件,避免脚本因为密码变化而重新生成不一致的 wp-config.php。
可重复执行并支持失败回滚的部署脚本
将下面的脚本保存为 /usr/local/sbin/wp-deploy.sh:
cat > /usr/local/sbin/wp-deploy.sh <<'SCRIPT'
#!/usr/bin/env bash
set -Eeuo pipefail
umask 027
ENV_FILE="${1:-/root/wp-example.env}"
if [[ ! -r "$ENV_FILE" ]]; then
echo "无法读取环境文件:$ENV_FILE" >&2
exit 1
fi
# shellcheck disable=SC1090
source "$ENV_FILE"
: "${DOMAIN:?缺少 DOMAIN}"
: "${APP_BASE:?缺少 APP_BASE}"
: "${DB_NAME:?缺少 DB_NAME}"
: "${DB_USER:?缺少 DB_USER}"
: "${DB_PASS:?缺少 DB_PASS}"
: "${WP_VERSION:?缺少 WP_VERSION}"
if [[ ! -f /etc/os-release ]] || ! grep -q "CentOS Stream 9" /etc/os-release; then
echo "本脚本仅示例支持 CentOS Stream 9,请先核对系统版本。" >&2
exit 1
fi
if [[ ! "$DB_NAME" =~ ^[A-Za-z0-9_]+$ ]] ||
[[ ! "$DB_USER" =~ ^[A-Za-z0-9_]+$ ]]; then
echo "DB_NAME 和 DB_USER 只能使用字母、数字和下划线。" >&2
exit 1
fi
if [[ ! "$DB_PASS" =~ ^[A-Za-z0-9_@#%+=:,.\\-]+$ ]]; then
echo "示例脚本要求 DB_PASS 不包含单引号、空格和反斜杠。" >&2
exit 1
fi
if [[ ! "$WP_VERSION" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
echo "WP_VERSION 格式不正确,例如 6.6.2。" >&2
exit 1
fi
LOG_FILE="/var/log/wp-deploy.log"
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*" | tee -a "$LOG_FILE"
}
die() {
log "ERROR: $*"
exit 1
}
SAFE_DOMAIN="${DOMAIN//./_}"
RELEASES="$APP_BASE/releases"
SHARED="$APP_BASE/shared"
BACKUPS="$APP_BASE/backups"
CURRENT="$APP_BASE/current"
STAMP="$(date '+%Y%m%d-%H%M%S')"
RELEASE="$RELEASES/$STAMP"
SOCKET="/run/php-fpm/wp-${SAFE_DOMAIN}.sock"
NGINX_CONF="/etc/nginx/conf.d/${DOMAIN}.conf"
FPM_CONF="/etc/php-fpm.d/wp-${SAFE_DOMAIN}.conf"
PREV_TARGET=""
SWITCHED=0
NGINX_CHANGED=0
NGINX_BACKUP=""
rollback_on_error() {
local rc=$?
log "部署失败,开始执行回滚。"
if [[ "$SWITCHED" == "1" && -n "$PREV_TARGET" ]]; then
ln -sfn "$PREV_TARGET" "$CURRENT"
log "已将 current 切回:$PREV_TARGET"
fi
if [[ "$NGINX_CHANGED" == "1" ]]; then
if [[ -n "$NGINX_BACKUP" && -f "$NGINX_BACKUP" ]]; then
cp -f "$NGINX_BACKUP" "$NGINX_CONF"
else
rm -f "$NGINX_CONF"
fi
nginx -t >/dev/null 2>&1 && systemctl reload nginx || true
fi
log "回滚处理结束,退出码:$rc"
exit "$rc"
}
trap rollback_on_error ERR
log "开始部署 WordPress ${WP_VERSION},域名:${DOMAIN}"
dnf install -y \
nginx \
mariadb-server \
php \
php-fpm \
php-mysqlnd \
php-gd \
php-mbstring \
php-xml \
php-opcache \
php-curl \
php-zip \
tar \
gzip \
unzip \
curl \
rsync \
openssl \
policycoreutils-python-utils \
cronie
systemctl enable --now mariadb php-fpm nginx crond
mkdir -p "$RELEASES" "$SHARED/wp-content/uploads" "$BACKUPS"
mkdir -p "$SHARED/wp-content/plugins" "$SHARED/wp-content/themes"
if [[ -e "$CURRENT" || -L "$CURRENT" ]]; then
if [[ ! -L "$CURRENT" ]]; then
die "$CURRENT 不是软链接,为避免覆盖数据,脚本已停止。"
fi
PREV_TARGET="$(readlink -f "$CURRENT")"
BACKUP_DIR="$BACKUPS/$STAMP"
mkdir -p "$BACKUP_DIR"
mysqldump \
--protocol=socket \
-uroot \
--single-transaction \
--routines \
--triggers \
"$DB_NAME" > "$BACKUP_DIR/database.sql"
tar -czf "$BACKUP_DIR/wp-content.tar.gz" -C "$SHARED" wp-content
log "已生成数据库和 wp-content 备份:$BACKUP_DIR"
fi
DB_PASS_SQL="${DB_PASS//\'/\'\'}"
mysql --protocol=socket -uroot < "$FPM_CONF" < "$SHARED/wp-config.php" < "$NGINX_CONF" </dev/null \
|| semanage fcontext -m -t httpd_sys_content_t "${APP_BASE}(/.*)?"
semanage fcontext -a -t httpd_sys_rw_content_t "${SHARED}/wp-content/uploads(/.*)?" 2>/dev/null \
|| semanage fcontext -m -t httpd_sys_rw_content_t "${SHARED}/wp-content/uploads(/.*)?"
restorecon -RF "$APP_BASE"
printf 'version=%s\nrelease=%s\ntime=%s\n' \
"$WP_VERSION" "$RELEASE" "$(date --iso-8601=seconds)" \
> "$RELEASE/.deploy-manifest"
sha256sum "$TMP_DIR/wordpress.tar.gz" >> "$RELEASE/.deploy-manifest"
nginx -t
ln -sfn "$RELEASE" "$CURRENT"
SWITCHED=1
systemctl reload nginx
HTTP_CODE="$(curl -sS -o /dev/null -w '%{http_code}' \
--max-time 10 \
-H "Host: ${DOMAIN}" \
"http://127.0.0.1/wp-login.php")"
case "$HTTP_CODE" in
200|301|302|303)
log "HTTP 验证通过,状态码:$HTTP_CODE"
;;
*)
die "HTTP 验证失败,状态码:$HTTP_CODE"
;;
esac
rm -rf "$TMP_DIR"
[[ -n "$NGINX_BACKUP" ]] && rm -f "$NGINX_BACKUP"
NGINX_CHANGED=0
logger -t wp-deploy "WordPress ${WP_VERSION} deployed: ${RELEASE}"
log "部署成功,current -> $(readlink -f "$CURRENT")"
SCRIPT
chmod 750 /usr/local/sbin/wp-deploy.sh
上面的脚本有几个关键设计:
- 安装目录不覆盖旧版本:每次使用新的时间目录。
- 数据库账号配置可重复执行:使用
CREATE DATABASE IF NOT EXISTS和固定环境文件。 - 部署前备份数据库和
wp-content:核心文件切换失败时,至少保留可用备份。 - 先执行
nginx -t,再切换current:配置语法错误不会直接替换线上版本。 - 切换后执行 HTTP 检查:如果返回 500、502 或其他非预期状态,脚本会尝试切回旧版本。
- 保存部署清单和哈希:便于审计某次发布使用的 WordPress 版本。
首次执行:
/usr/local/sbin/wp-deploy.sh /root/wp-example.env
脚本只负责准备 WordPress 文件、数据库和 Web 运行环境。第一次访问:
http://example.com/wp-admin/install.php
完成站点名称、管理员账号和密码设置即可。正式环境配置 HTTPS 后,应将测试地址替换为正式的 HTTPS 地址,并同步调整 Nginx 配置。
注意:示例脚本中的 Nginx 配置备份逻辑适合说明部署流程,正式使用时应在覆盖/etc/nginx/conf.d/example.com.conf之前先复制旧文件。更稳妥的做法是先写入临时文件,执行nginx -t -c或替换后立即测试,确认失败时恢复旧文件。
配置管理和审计不要只依赖脚本输出
脚本执行成功,不代表后续一定能够追溯。至少应保存以下信息:
readlink -f /var/www/example.com/current
cat /var/www/example.com/current/.deploy-manifest
tail -n 100 /var/log/wp-deploy.log
journalctl -u nginx --since "30 minutes ago"
journalctl -u php-fpm --since "30 minutes ago"
可以把部署记录整理为:
| 项目 | 应记录的内容 |
|---|---|
| 发布版本 | WordPress 版本号、发布时间 |
| 文件目录 | current 指向的完整路径 |
| 配置来源 | 使用的环境文件路径和权限 |
| 数据库备份 | 备份目录、SQL 文件大小、生成时间 |
| 服务状态 | Nginx、PHP-FPM、MariaDB 是否正常 |
| 验证结果 | HTTP 状态码、页面访问结果 |
| 回滚信息 | 上一个可用版本目录 |
不要把数据库密码写入日志、命令参数或部署清单。wp-config.php 需要由 PHP-FPM 读取,但不应设置为全局可读。
用定时任务做数据库和网站内容备份
重复部署依赖回滚,回滚又依赖可用备份。可以创建一个简单的每日备份脚本:
cat > /usr/local/sbin/wp-backup.sh <<'SCRIPT'
#!/usr/bin/env bash
set -Eeuo pipefail
umask 027
APP_BASE="/var/www/example.com"
DB_NAME="wp_example"
BACKUP_DIR="$APP_BASE/backups/$(date '+%Y%m%d-%H%M%S')"
LOCK_FILE="/run/lock/wp-backup.lock"
mkdir -p "$BACKUP_DIR" "$(dirname "$LOCK_FILE")"
exec 9>"$LOCK_FILE"
flock -n 9 || exit 0
mysqldump \
--protocol=socket \
-uroot \
--single-transaction \
--routines \
--triggers \
"$DB_NAME" > "$BACKUP_DIR/database.sql"
tar -czf "$BACKUP_DIR/wp-content.tar.gz" \
-C "$APP_BASE/shared" wp-content
find "$APP_BASE/backups" \
-mindepth 1 -maxdepth 1 -type d \
-mtime +14 -exec rm -rf {} \;
SCRIPT
chmod 750 /usr/local/sbin/wp-backup.sh
这里的 rm -rf 只针对备份目录,并且设置了 -mindepth 和 -maxdepth。执行前必须确认 BACKUP_DIR 指向的是专用备份目录,避免变量错误造成误删。
添加定时任务:
cat > /etc/cron.d/wp-backup <<'EOF'
20 3 * * * root /usr/local/sbin/wp-backup.sh >> /var/log/wp-backup.log 2>&1
EOF
chmod 644 /etc/cron.d/wp-backup
systemctl restart crond
定时任务只保存在本机并不能应对磁盘损坏或误删。重要站点应再将备份复制到独立的备份位置,并定期抽取一份 SQL 进行恢复演练。
按优先级排查部署失败
遇到网站打不开时,我们先不要直接删除目录。按下面顺序检查,风险最低且定位效率更高。
1. 先看端口和 HTTP 状态
ss -lntp | grep -E ':(80|443)\b'
curl -I -H "Host: example.com" http://127.0.0.1/
结果含义:
- 没有监听 80 端口:Nginx 没启动,或配置加载失败;
- 返回
502 Bad Gateway:重点检查 PHP-FPM 服务和 Socket; - 返回
403:重点检查 Nginx 根目录、权限和 SELinux; - 返回
404:检查current是否指向正确版本,以及try_files配置; - 返回
500:继续检查 PHP 错误日志和数据库连接。
修复后再次执行:
nginx -t
systemctl reload nginx
curl -I -H "Host: example.com" http://127.0.0.1/wp-login.php
2. 检查 Nginx 配置和当前版本
nginx -t
readlink -f /var/www/example.com/current
ls -ld /var/www/example.com/current
ls -l /var/www/example.com/current/index.php
journalctl -u nginx -n 80 --no-pager
如果 nginx -t 报错,不要继续切换软链接。先修复配置语法,再重新加载服务。
如果 current 指向了不存在的目录,说明部署在切换阶段中断。可以从保留目录中选择一个已验证版本:
ls -lt /var/www/example.com/releases
确认目录内容完整后,再执行:
ln -sfn /var/www/example.com/releases/20260928-090000 \
/var/www/example.com/current
nginx -t && systemctl reload nginx
3. 检查 PHP-FPM 和 Socket
systemctl status php-fpm --no-pager
php-fpm -t
ls -l /run/php-fpm/
journalctl -u php-fpm -n 100 --no-pager
如果 Nginx 返回 502,常见原因是:
- PHP-FPM 没有启动;
- Nginx 中的
fastcgi_pass路径与 PHP-FPM 的listen不一致; - Socket 权限不允许 Nginx 访问;
- PHP-FPM 配置文件存在语法错误。
确认配置中的两个路径一致:
grep -R "listen" /etc/php-fpm.d/
grep -R "fastcgi_pass" /etc/nginx/conf.d/
修复后执行:
php-fpm -t
systemctl restart php-fpm
nginx -t && systemctl reload nginx
curl -I -H "Host: example.com" http://127.0.0.1/wp-login.php
4. 检查数据库连接
systemctl status mariadb --no-pager
mysql --protocol=socket -uroot -e "SHOW DATABASES;"
mysql --protocol=socket -uroot -e \
"SELECT User,Host FROM mysql.user WHERE User='wp_example';"
如果数据库不存在,说明初始化阶段没有完成;如果用户存在但 WordPress 报“建立数据库连接时出错”,应重点核对:
grep -E "DB_NAME|DB_USER|DB_HOST" \
/var/www/example.com/shared/wp-config.php
不要把完整的 DB_PASSWORD 输出到终端或日志中。修改数据库密码时,必须同步修改数据库用户和 wp-config.php,并在修改前保留旧配置备份。
5. 检查权限和 SELinux
namei -l /var/www/example.com/current/index.php
ls -Zd /var/www/example.com/shared/wp-content
ls -Zd /var/www/example.com/shared/wp-content/uploads
getenforce
ausearch -m avc -ts recent | tail -n 30
如果 SELinux 日志显示拒绝访问,可以重新应用文件上下文:
restorecon -RF /var/www/example.com
上传目录需要允许 Web 服务写入,但不建议为了方便直接给整个站点目录开放写权限。通常只对 wp-content/uploads 设置可写上下文,其余核心文件保持只读属性更安全。
6. 最后检查 WordPress、插件和主题
如果 Nginx、PHP-FPM、MariaDB 和权限都正常,但页面仍然 500,再查看 PHP 错误日志:
journalctl -u php-fpm -n 100 --no-pager
tail -n 100 /var/log/php-fpm/www-error.log 2>/dev/null || true
若问题发生在插件或主题更新后,切换 WordPress 核心目录并不能撤销插件自身的文件和数据库变更。此时应根据备份恢复对应的 wp-content,必要时再恢复数据库。
两类回滚要分开处理
仅回滚 WordPress 文件
适用于新核心文件解压错误、PHP 版本不兼容、Nginx 指向错误目录等情况:
readlink -f /var/www/example.com/current
ls -lt /var/www/example.com/releases
确认旧目录完整后:
ln -sfn /var/www/example.com/releases/20260928-090000 \
/var/www/example.com/current
nginx -t
systemctl reload nginx
curl -I -H "Host: example.com" \
http://127.0.0.1/wp-login.php
这种方式只改变当前代码版本,不会自动恢复数据库。
同时恢复数据库
如果失败原因涉及插件升级、主题迁移或数据库结构变化,应先进入维护状态,停止后台写入,并确认当前备份文件确实属于目标版本。数据库恢复会覆盖现有数据,执行前必须再生成一份当前数据库备份。
在确认数据库名称和备份文件无误后,可以使用:
mysql --protocol=socket -uroot wp_example \
< /var/www/example.com/backups/20261001-120000/database.sql
恢复后依次检查:
mysql --protocol=socket -uroot -e \
"CHECK TABLE wp_options, wp_posts, wp_users;"
systemctl reload php-fpm
systemctl reload nginx
curl -I -H "Host: example.com" \
http://127.0.0.1/wp-login.php
代码回滚和数据库回滚不要混为一谈:只更新 WordPress 核心时,通常优先回滚 current;如果插件已经执行数据库迁移,则需要同时评估数据库恢复,否则可能出现旧代码读取新表结构的兼容性问题。
回到最初的故障场景
假设第二次部署后出现 502,我们先执行 curl 和 ss,确认 80 端口仍在监听;接着用 nginx -t 和 systemctl status php-fpm 判断是配置问题还是 Socket 问题;如果 Nginx 和 PHP-FPM 正常,再核对 current、数据库和 SELinux。只有这些基础层都通过后,才继续分析 WordPress 插件和主题。
可重复部署的重点并不是把更多命令塞进一个脚本,而是让每次发布都具备明确的状态:配置固定、版本独立、备份先行、切换可逆、日志可查。香港服务器上的 CentOS WordPress 部署遇到失败时,只要保留旧版本目录和对应数据库备份,就能把“整站重装”变成一次可验证、可审计、可回退的版本切换。