FrankenPHP 不会自动清除 X-Powered-By 头,该头仍可能来自 PHP 层(expose_php=On)、Symfony 应用中间件或前置反向代理,需分别在 frankenphp 的 php.ini、应用代码及 Nginx 配置中同步关闭或移除。

迁到 FrankenPHP 后,X-Powered-By 响应头**不会自动消失**,反而可能更难控制——因为 FrankenPHP 本身不加这个头,但 PHP 层和 Symfony 应用层的原有逻辑照常运行,两层泄露风险依然存在。
FrankenPHP 不会主动添加 X-Powered-By,但也不帮你删
FrankenPHP 是嵌入式 PHP 运行时(基于 Caddy + Go),它不像 Apache 的 mod_php 或 Nginx + PHP-FPM 那样依赖传统 CGI/FastCGI 协议栈,因此它**完全不生成、不注入、也不拦截 X-Powered-By 头**。这个头的来源只可能是:
- PHP 自身通过
expose_php = On输出X-Powered-By: PHP/8.x - Symfony 或你写的中间件/监听器显式设置了该头(极少见,但自定义安全中间件可能误加)
- 反向代理(如前置 Nginx)配置了
add_header X-Powered-By ...—— 这在 FrankenPHP 前置场景中容易被忽略
expose_php = Off 在 FrankenPHP 下是否生效?
生效,但必须改对位置:FrankenPHP 启动时读取的是它自己的 php.ini 路径,不是系统全局的 /etc/php/*/fpm/php.ini。常见错误是只改了 FPM 的 ini,却没动 FrankenPHP 的配置。
- FrankenPHP 默认使用内置 PHP,其
php.ini通常位于frankenphp/config/php.ini或通过FRANKENPHP_PHP_INI_PATH环境变量指定 - 必须确认
expose_php = Off出现在该 ini 文件中,且无其他地方(如.user.ini或ini_set())覆盖它 - 修改后需重启 FrankenPHP 进程(不是 reload),否则不生效
- 验证方式仍是
curl -I http://localhost,别信浏览器 Network 面板
Symfony 层有没有可能悄悄加 X-Powered-By?
默认不会。Symfony 核心不写 X-Powered-By,但要注意这些边缘情况:
立即学习“PHP免费学习笔记(深入)”;
- 如果你用了
symfony/web-server-bundle(已废弃)或旧版开发服务器,它曾硬编码该头;FrankenPHP 迁移后这类包应已移除 - 自定义响应中间件里写了
$response->headers->set('X-Powered-By', 'Symfony')—— 检查app/middleware/或src/Kernel.php中的registerBundles()相关逻辑 - 某些第三方 Bundle(如老版本
nelmio/cors-bundle配置不当)可能透传或重设该头,需逐个排查
前置代理(如 Nginx)仍可能补上这个头
很多团队把 FrankenPHP 当作后端服务,前面再套一层 Nginx 做 TLS 终结或负载均衡。这时最容易漏掉的是:
- Nginx 配置里写了
add_header X-Powered-By "PHP";—— 这种写法会强制加上,且优先级高于后端响应 - 即使你关了 PHP 层的
expose_php,Nginx 仍会补一个静态值 - 正确做法是:删掉所有
add_header X-Powered-By,必要时用proxy_hide_header X-Powered-By(如果上游真发了)
真正麻烦的不是“怎么删”,而是 FrankenPHP 让泄露路径变分散了:PHP ini、应用中间件、反向代理三处都得检查,缺一不可。别假设换服务器就自动安全——它只是换了执行方式,没改 PHP 和应用本身的默认行为。



















