上一篇 下一篇 分享链接 返回 返回顶部

CentOS香港服务器部署WordPress,如何用脚本实现重复部署与失败回滚

发布人:Minchunlin 发布时间:2026-10-02 00:21 阅读量:5

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

建立 WordPress 重复部署失败后的真实技术排障场景。

如果你是按“香港服务器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 指向旧目录,不需要重新解压整个网站。

解释 WordPress 可重复部署的目录状态模型及 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

上面的脚本有几个关键设计:

  1. 安装目录不覆盖旧版本:每次使用新的时间目录。
  2. 数据库账号配置可重复执行:使用 CREATE DATABASE IF NOT EXISTS 和固定环境文件。
  3. 部署前备份数据库和 wp-content:核心文件切换失败时,至少保留可用备份。
  4. 先执行 nginx -t,再切换 current:配置语法错误不会直接替换线上版本。
  5. 切换后执行 HTTP 检查:如果返回 500、502 或其他非预期状态,脚本会尝试切回旧版本。
  6. 保存部署清单和哈希:便于审计某次发布使用的 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 部署遇到失败时,只要保留旧版本目录和对应数据库备份,就能把“整站重装”变成一次可验证、可审计、可回退的版本切换。

目录结构
全文