WordPress Nginx伪静态后台404修复指南

Nginx 下 WordPress 加伪静态规则后前台正常但 wp-admin 404,根因是 if + rewrite 三连吞掉目录路径。本文给出 301 重定向兜底写法与官方推荐的 try_files 现代方案,附完整生产配置可直接照抄。

更新 31 分钟阅读 3,231 阅读

保哥这些年帮朋友把 WordPress 站点从 Apache 迁到 Nginx,遇到最频繁的一个怪问题就是:前台文章页 URL 伪静态化之后访问完全正常,但一登录后台就发现地址栏里的 /wp-admin/ 莫名其妙不见了,页面跳到一个奇怪的路径并直接返回 404 错误页。第一次碰到的时候我以为是 WordPress 自身坏了,重装、清缓存、换主题统统试了一遍才意识到——这其实是 Nginx 的 rewrite 规则写得不够严谨导致的副作用。这个坑一旦掉进去,普通用户基本上无从下手,因为 WordPress 自己没报错、Nginx 也没报错,你只能盯着浏览器地址栏里那个少了 /wp-admin/ 的诡异 URL 发呆。这篇文章保哥把根因、坑点、修复方案以及最稳的现代配置一次性讲清楚,给同样卡在这里的朋友一份能直接照抄的参考。

为什么 .htaccess 在 Nginx 上完全无效

很多刚从 Apache 转到 Nginx 的朋友会本能地把 .htaccess 文件复制到网站根目录,然后疑惑「为什么伪静态没生效」。原因很简单:.htaccess 是 Apache 的私有配置机制,由 mod_rewrite 模块在请求到达时动态解析,Nginx 根本不读取它,哪怕你写得再漂亮也只是一堆死文本,搬到 Nginx 上就跟没这个文件一样。这是保哥见到的最高频的初学者误区,没有之一。理解了这一点,后面所有的伪静态问题都会变得有迹可循。

Nginx 的等价物是写在 server 块或 location 块里的 rewrite 指令,配置文件通常叫 nginx.conf 或者 conf.d/yoursite.confsites-enabled/yoursite 这一类。WordPress 官方文档里也明确给出了 Nginx 推荐配置,但保哥发现网上转载最广的那一份老配置在某些场景下会引发 wp-admin 路径丢失,这就是本文要解决的核心问题。如果你已经在用 .htaccess 思路写 Nginx 配置,建议先全部清空,按本文从头配一遍,省得新老规则混在一起难以排查。还有一种常见的混淆是把 .htaccess 里的 RewriteRule 用法直接平移到 Nginx,这种语法 Nginx 也不认识,必须用 Nginx 自己的 rewrite 指令重新写一遍。

坑爹的传统伪静态配置长什么样

下面这段配置是早年中文社区里流传最广的一份,前台看着完全没问题:

location / {
    if (-f $request_filename/index.html) {
        rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename/index.php) {
        rewrite (.*) $1/index.php;
    }
    if (!-f $request_filename) {
        rewrite (.*) /index.php;
    }
}

保哥早期也照搬过这套写法。它的逻辑是:先看路径下是不是有 index.html,再看是不是有 index.php,最后兜底全部丢给 WordPress 入口 index.php。前台所有文章页、分类页确实都能用伪静态形式访问,看起来一切完美。

但当你在浏览器访问 https://example.com/wp-admin/ 的时候,会发生什么呢?由于 wp-admin 是一个目录,最后那条 if (!-f $request_filename) 会被命中(因为它本身不是文件而是目录),请求被改写成 /index.php,路径里原本的 /wp-admin/ 信息直接被丢掉了。WordPress 拿到 /index.php 后认为这是前台首页,于是后台跳转链路就此断裂,最终在某些主题或插件下表现为 404 错误页或者反复重定向死循环。这就是问题的根本原因。

保哥多说一句:Nginx 官方文档里有一篇专门叫 If is Evil 的页面,明确警告 location 块里的 if 在某些组合下行为完全不可预测,能不用就不用。上面这套老配置本质上就是踩到了这个坑。

一行重定向规则补回 wp-admin 路径

保哥最早采用的修复方式是在原有规则后面再追加一条针对 /wp-admin 的强制斜杠重定向:

location / {
    if (-f $request_filename/index.html) {
        rewrite (.*) $1/index.html break;
    }
    if (-f $request_filename/index.php) {
        rewrite (.*) $1/index.php;
    }
    if (!-f $request_filename) {
        rewrite (.*) /index.php;
    }
}
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

这条 rewrite /wp-admin$ $scheme://$host$uri/ permanent; 的含义是:当请求路径精确匹配 /wp-admin(注意末尾没有斜杠)时,301 永久重定向到带斜杠的 /wp-admin/。这样浏览器拿到 301 后会重新发起一个带斜杠的请求,命中 WordPress 自己内部的目录处理逻辑,路径就保住了。

保哥实测这套写法在 WordPress 4.x、5.x 时代都能用,绝大多数场景下问题就此解决。但严格来讲它并不是最优雅的方案,因为前面那个 if + rewrite 三连本身就是隐患,相当于在用一个补丁掩盖另一个设计缺陷。下面给出更现代、更稳的写法,建议直接采用。

官方推荐的现代 Nginx 伪静态写法

WordPress 官方在 Codex 上给出的推荐配置只有短短几行,干净得多:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        expires max;
        log_not_found off;
    }
}

关键就是 try_files $uri $uri/ /index.php?$args; 这一行。保哥逐步解释一下它的执行顺序:

第一步,先尝试把请求当作真实文件 $uri,比如 /wp-content/uploads/photo.jpg 这种静态资源直接命中并返回。

第二步,再尝试把请求当作目录 $uri/,并自动追加默认 index 文件,这一步会保留原始路径,所以 /wp-admin/ 不会丢掉。

第三步,前两步都没命中才回退到 /index.php?$args,把查询参数也一起带上交给 WordPress 处理。

相比老式 if + rewrite 三连,try_files 是 Nginx 后来专门为这种场景设计的指令,性能更好、行为更可预测、不会触发 wp-admin 丢失的问题,而且 Nginx 官方明确警告过 location 块里的 if 是 evil 的,能不用就不用。保哥的所有新项目从 2018 年之后就全部统一改用 try_files,再也没遇到过类似的伪静态怪问题。

完整可直接抄走的生产环境配置

保哥把自己跑了好几年的一份 WordPress + Nginx 配置贴出来,已经包含 HTTPS 跳转、Gzip 压缩、静态资源缓存、安全响应头与 PHP 处理:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header Referrer-Policy strict-origin-when-cross-origin;
    add_header Strict-Transport-Security "max-age=31536000" always;

    gzip on;
    gzip_min_length 1k;
    gzip_types text/plain text/css application/javascript application/json image/svg+xml;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_read_timeout 300;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot)$ {
        expires 30d;
        access_log off;
        log_not_found off;
    }

    location ~ /\.(ht|git|svn) {
        deny all;
    }
    location = /wp-config.php {
        deny all;
    }
    location ~* /wp-content/.*\.php$ {
        deny all;
    }
    location ~* /wp-includes/.*\.php$ {
        deny all;
    }
}

保哥强调几个细节。第一,try_files $uri =404; 在 PHP location 里很重要,能防止 PHP-FPM 被恶意诱导去执行不存在的脚本(CVE-2019-11043 那个老洞的常见缓解措施)。第二,对 /wp-content//wp-includes/ 下的 .php 直接 deny 是防止恶意上传文件被执行的标准做法,几乎所有 WordPress 安全插件也都会建议这么配。第三,记得在 WordPress 后台「设置 → 固定链接」里选一个非默认结构(保哥推荐 /%postname%/),保存一次让 WordPress 内部的 rewrite 规则也同步刷新,否则即使 Nginx 配置对了文章 URL 还是会带着 ?p=123 这种丑陋的参数。

上线前的自测清单

配置改完不要急着关 SSH 终端,保哥的习惯是用一份固定的清单逐项验证,确保前台、后台、静态资源、404 兜底全部正常。

第一步,nginx -t 检查语法,没问题再 systemctl reload nginx,永远不要上来就 restart 否则一旦语法错误整个站都会掉线。

第二步,访问首页 https://example.com/,确认返回 200 状态码。

第三步,访问任意一篇文章的伪静态地址,比如 https://example.com/hello-world/,确认正常显示且 URL 中不带 index.php 后缀。

第四步,访问 https://example.com/wp-admin(注意不带末尾斜杠),观察是否被 301 到 /wp-admin/,浏览器开发者工具 Network 面板可以清楚看到这次重定向。

第五步,登录后台,进入「文章 → 所有文章」「插件」「主题」几个常用菜单,确保地址栏里始终保留 /wp-admin/ 前缀,并且每个菜单点开后页面都能正常加载、不出现样式错乱。

第六步,故意访问一个不存在的链接 https://example.com/this-does-not-exist,确认返回的是 WordPress 自带的 404 模板而不是 Nginx 的默认 404 页。这一步很重要,能验证 try_files 的回退路径是不是正常工作。

第七步,curl -I https://example.com/wp-content/uploads/test.jpg 看看图片资源能不能直接命中,并且响应头里有没有 expires 缓存字段。

保哥每次上新机器都跑这七步,前后不到三分钟,但能把 90% 的伪静态配置问题挡在生产环境之外。

性能调优的延伸建议

伪静态规则只是 Nginx 配置的起点,保哥这里再分享几条实战中常用的延伸建议,能让 WordPress 在 Nginx 上跑得更稳更快。

第一,把 PHP-FPM 切到 Unix Socket 而不是 TCP 端口。本地通信用 Socket 更快,避免内核 TCP 栈的开销,配置写法就是 fastcgi_pass unix:/run/php/php8.1-fpm.sock; 这种。前提是 PHP-FPM 配置里 listen 字段也改成 socket 路径,并且权限要给到 Nginx 用户。

第二,启用 OPCache。WordPress 是个相当吃 PHP 解析性能的应用,开启 OPCache 之后页面响应可以快上两到三倍。在 php.ini 里把 opcache.enable=1opcache.memory_consumption=256opcache.max_accelerated_files=20000 几项调好,几乎是零成本的性能提升。

第三,给静态资源加上 CDN。即使是单机 Nginx,也可以用宝塔或者七牛、阿里云、Cloudflare 这种把 /wp-content/uploads/ 下的图片回源拉过去,前端用户访问图片就不再回到你的服务器,主机负载立刻能降下来一大截。这一步对 SEO 也友好,搜索引擎抓取速度会明显提升。

第四,开启 fastcgi_cache 缓存动态页面。WordPress 的页面在没登录用户访问时其实可以直接缓存几分钟到几小时不等,Nginx 内置的 fastcgi_cache 比 WordPress 插件级缓存更高效,对于流量大的站点效果立竿见影。

第五,定期检查 Nginx 错误日志 /var/log/nginx/error.log,很多潜在问题在错误日志里早就有蛛丝马迹,等用户反馈上来才发现就晚了。保哥的习惯是每周抽十分钟扫一眼错误日志,看看有没有反复出现的奇怪请求或者权限错误,往往能提前发现安全攻击或配置漂移。

第六,给后台单独加一层 IP 白名单或 HTTP Basic 认证,进一步降低 wp-admin 被暴力破解的风险。配置示例:在 /wp-admin/ 这个 location 块里加 allow 你的办公 IP; deny all; 或者引入 auth_basic 模块,配合一个 htpasswd 文件就能挡住绝大多数自动化扫描器。这一步不影响普通用户访问前台,但能极大降低后台被入侵的概率。

实操检查清单:迁移前后的关键节点

把上面这些零散的实战经验汇总成一份可直接照抄的清单,方便每次新建站点或迁移老站时按部就班执行:

  • 迁移前确认:备份原站数据库与文件、记录原 Nginx 或 Apache 配置、检查 PHP 版本与扩展、确认数据库账户密码可用
  • Nginx 主配置:在 server 块内用 try_files 替代 if + rewrite 三连、PHP location 加 try_files =404 防止脚本误执行
  • 安全加固:deny 掉 wp-content 与 wp-includes 下的 .php、限制 wp-config.php 访问、屏蔽 .ht/.git/.svn 等隐藏目录
  • HTTPS 与响应头:80 跳 443、TLSv1.2+、添加 HSTS、X-Frame-Options、X-Content-Type-Options、Referrer-Policy
  • WordPress 后台:固定链接选 /%postname%/、检查 WordPress 地址与站点地址都是 https、wp-config.php 加 FORCE_SSL_ADMIN
  • 静态资源:js/css/图片/字体设置 expires 缓存、关闭 access_log、考虑接入 CDN
  • 性能调优:PHP-FPM 用 Unix Socket、启用 OPCache、按需开启 fastcgi_cache
  • 上线验证:nginx -t 通过、reload 而非 restart、按 7 步自测清单逐项过
  • 持续监控:每周扫一次 error.log、订阅 PHP 与 WordPress 安全公告、按季度复盘 SSL 证书与 PHP-FPM 版本

常见问题解答

已经改了 nginx.conf 但伪静态还是不生效怎么排查?

保哥见过最多的原因是改错文件。Nginx 通常会有多个 server 块,分布在 nginx.confconf.d/*.confsites-enabled/* 等位置,要确认你改的是当前域名实际命中的那个。可以用 nginx -T(注意大写 T)打印当前生效的全部配置自查,搜索你的 server_name 看看到底命中了哪一段。还有一种情况是 Nginx 没有真正 reload,可以 ps -ef | grep nginx 看看 master 进程的启动时间是否更新。

用宝塔面板时应该在哪里加这些规则?

宝塔在「网站设置 → 伪静态」里有现成的 WordPress 模板,直接选「WordPress」点保存即可,背后帮你写的就是 try_files 那一套。如果你需要更多自定义规则,建议在「配置文件」面板里手动编辑而不是反复切模板,因为切模板会覆盖你的自定义内容。

为什么加了 try_files 之后图片反而 404 了?

大概率是 root 路径写错了或者文件权限不对。Nginx 进程通常以 www-data 或 nginx 用户跑,确认网站目录的属主是它,并且至少 755 权限。可以用 sudo -u www-data ls /var/www/example.com/wp-content/uploads/ 模拟一下访问,能列出文件就说明权限没问题。还有一种可能是你的 location 块匹配优先级写反了,把图片请求路由到了 PHP 处理块。

HTTPS 重定向会不会让 wp-admin 出现混合内容警告?

会。保哥的建议是在 WordPress 后台「设置 → 常规」里把「WordPress 地址」和「站点地址」都改成 https 开头,并且在 wp-config.php 里加上 define('FORCE_SSL_ADMIN', true);,从源头消除混合内容。如果是迁移过来的老站,还需要批量替换数据库里 http 链接为 https 链接,可以用 wp-cli 的 wp search-replace 命令一键完成。

使用了 try_files 还会遇到 wp-admin 路径丢失吗?

正常情况下不会。try_files 会按文件、目录、回退入口的顺序处理请求,目录形式的 /wp-admin/ 会在第二步直接命中,路径不会被改写。如果还遇到丢失,多半是 location 块匹配优先级配错了,或者前面还有遗留的 rewrite 规则在干扰,建议把 server 块从头梳理一遍。

Nginx 错误日志和访问日志多大合适?需要切割吗?

访问日志单文件超过 500MB 就建议切割了,否则文本编辑器打开会卡。Linux 自带 logrotate 工具可以按天或按大小自动切割,配置文件通常在 /etc/logrotate.d/nginx。错误日志增长一般很慢,按月切割即可。切割后记得给 Nginx 发 USR1 信号(reload 也会触发)让它重新打开日志文件,否则继续往老 inode 写。

升级 PHP 版本时 fastcgi_pass 需要改吗?

需要。fastcgi_pass unix:/run/php/php8.1-fpm.sock; 这种路径里嵌入了 PHP 版本号,升级 PHP 后旧版本对应的 socket 文件会消失,新版本会创建带新版本号的 socket。如果你不想每次升级都改 Nginx 配置,可以在 PHP-FPM 配置里手动指定一个不带版本号的 socket 路径,或者用 systemd 软链接固定路径,这样 Nginx 配置就只需要写一次。

分享到
标签
版权声明

本文标题:《WordPress Nginx伪静态后台404修复指南》

本文链接:https://zhangwenbao.com/wordpress-nginx-rewrite-404-wp-admin.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交