`rewrite_rules` 刷新后自定义端点 502 又变 404:我卡在 Nginx `try_files` 与 WordPress 重写的"夹心层"里两小时
上周给插件加了个前端提交页,走自定义重写规则:/submit-idea/ 映射到插件模板。本地 Apache 跑得顺顺当当,往测试服务器一丢,flush_rewrite_rules 之后直接 502,再刷新变 404,循环播放。
先贴我当时自以为稳的代码:
// 注册端点
add_action( 'init', function() {
add_rewrite_endpoint( 'submit-idea', EP_ROOT );
add_rewrite_rule( '^submit-idea/?$', 'index.php?submit_idea=1', 'top' );
} );
// 查询变量白名单
add_filter( 'query_vars', function( $vars ) {
$vars[] = 'submit_idea';
return $vars;
} );
// 模板劫持
add_action( 'template_redirect', function() {
if ( get_query_var( 'submit_idea' ) ) {
include plugin_dir_path( __FILE__ ) . 'templates/submit-idea.php';
exit;
}
} );
Apache 下 template_redirect 正常触发,Nginx 下直接进虚空。排查路径如下,每一步都是泪:
第一层:伪静态规则"假传圣旨"
测试环境 Nginx 配置是从某面板一键拉的,location / 里写的是:
try_files $uri $uri/ /index.php?$args;
看起来标准,但问题在 $args 不带原始查询串的重组。更坑的是,面板为了"兼容",偷偷在 index.php 后面又套了一层 if (!-e $request_filename) 的 legacy 规则,导致 WordPress 的 $_SERVER['REQUEST_URI'] 被改写,parse_request 阶段匹配不到 submit-idea。
验证方法:在插件里临时 error_log( $_SERVER['REQUEST_URI'] );,发现请求 /submit-idea/ 时 URI 变成了 /index.php,原始路径丢了。
修正后的 Nginx 片段:
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
注意 $is_args$args 和 ? 的区别,前者在没参数时不挂空问号,避免 $_SERVER['QUERY_STRING'] 出现脏数据。
第二层:flush_rewrite_rules 的"延时生效"陷阱
我代码里在 init 钩子直接调了 flush_rewrite_rules( true ),每次请求都写数据库。Nginx 环境下这玩意会引发 race:规则还没落库,下一个请求已经进来,wp_rewrite 用的还是内存里的旧规则数组。
更隐蔽的是,如果服务器开了 object cache(Redis),rewrite_rules option 可能被缓存层拦截,数据库里明明更新了,WP_Rewrite::wp_rewrite_rules() 读到的却是缓存版本。
我现在改成"懒刷新"模式,只在激活/升级时触发:
register_activation_hook( __FILE__, function() {
// 先注册规则,再刷新
my_plugin_add_rewrite_rules();
flush_rewrite_rules( true );
} );
register_deactivation_hook( __FILE__, function() {
flush_rewrite_rules( true );
} );
// 日常 init 只注册,不刷新
add_action( 'init', 'my_plugin_add_rewrite_rules', 10, 0 );
第三层:EP_ROOT 与 add_rewrite_rule 的"叠床架屋"
回头看我的代码,同时用了 add_rewrite_endpoint 和 add_rewrite_rule,这俩在 $wp_rewrite->endpoints 和 $wp_rewrite->extra_rules_top 里各占一条。Apache mod_rewrite 对冗余规则容忍度高,Nginx 的 try_files fallback 路径短,先匹配到 endpoint 的 submit_idea=(注意等号后面空值),get_query_var 返回空字符串,template_redirect 里的判断 if ( get_query_var( 'submit_idea' ) ) 被短路。
修正:二选一,我保留了 add_rewrite_rule,干掉 endpoint。或者如果必须用 endpoint,判断条件改成 if ( '' !== get_query_var( 'submit_idea' ) ),但这样语义别扭。
第四层:template_redirect 的触发时机盲区
即使前面都对了,Nginx + PHP-FPM 环境下,如果某个上游插件在 parse_request 或 send_headers 阶段调了 wp_die() 或 exit,template_redirect 会被跳过。我最后加了个兜底,在 wp 钩子也挂一份检查:
add_action( 'wp', function() {
if ( get_query_var( 'submit_idea' ) && ! did_action( 'template_redirect' ) ) {
// 日志告警,说明有东西提前截断了流程
do_action( 'my_plugin_template_redirect_missed' );
include plugin_dir_path( __FILE__ ) . 'templates/submit-idea.php';
exit;
}
} );
总结 checklist,给同样踩坑的:
- Nginx
try_files必须带$is_args$args,检查是否有 legacyif规则干扰 flush_rewrite_rules绝不放在高频钩子,激活时一次刷新,日常靠init注册- object cache 环境下,刷新后手动踢缓存或加版本号强制失效
- endpoint 和 rewrite_rule 别混用,查询变量判断要区分
false/''/0 - 核心钩子被截断时,
wp是最后的"救生舱"
最后吐槽:Apache 的容错像海绵,Nginx 的严格像钢板,插件跨服务器部署时,重写规则这趴最好写进安装检测脚本,自动报环境差异。

