nginx中php应用csp常失效,主因是add_header默认仅对2xx/3xx响应生效且易被php同名header覆盖;须加always参数、禁用php层csp输出,并用report-only模式灰度验证策略。

PHP 环境下用 Nginx 配置 CSP 响应头,核心问题不是“能不能配”,而是“配了为什么没生效”或“配了反而页面白屏/脚本报错”。关键在层级冲突和策略粒度——Nginx 层的 add_header 会覆盖 PHP 的 header(),且一旦写错指令顺序或漏掉必需源,浏览器直接拦截资源。
为什么 Nginx 的 CSP 配置常被 PHP 覆盖或静默失效
Nginx 的 add_header 默认只对 2xx/3xx 响应生效,如果 PHP 返回了 404、500 或重定向(302),这个头就根本不会发出去。更常见的是:你同时在 PHP 里写了 header('Content-Security-Policy: ...'),又在 Nginx 里用了 add_header,结果后者被前者覆盖(因为 PHP 在响应体生成前才发 header,而 Nginx 的 add_header 是在响应头组装阶段插入的,实际优先级取决于是否启用 always 参数)。
- 检查 Nginx 配置中是否加了
always:比如add_header Content-Security-Policy "..." always;,否则 4xx/5xx 响应不带 CSP - 确认没有在 PHP 中重复设置同名 header;若有,删掉 PHP 层的,统一收口到 Nginx
- 用浏览器 Network 面板 → Response Headers 查看真实发出的
Content-Security-Policy字符串,别信 PHP 日志或代码注释
script-src 写法不对,页面立刻挂掉的典型场景
最常踩的坑是只写 script-src 'self' 却没处理内联脚本、事件处理器和第三方域名。浏览器一旦看到 onclick="doIt()" 或 <script>console.log(1)</script>,且策略里没允许 'unsafe-inline'(不推荐)或提供 nonce/sha256-xxx,就直接拒绝执行,JS 逻辑中断。
- 禁止
'unsafe-inline'和'unsafe-eval'是必须的,但意味着你要把所有内联<script></script>拆成外部文件,并改用addEventListener绑定事件 - 第三方 JS(如 Google Analytics、微信 SDK)必须显式列出完整协议+域名:
script-src 'self' https://www.googletagmanager.com https://res.wx.qq.com - 如果你用 Vue/React 的 inline template 或 webpack 的 runtime 代码,可能需要加
'strict-dynamic',但注意它只在 HTTPS 下有效,且需配合 nonce 使用
开发期用 Report-Only 模式试跑,别一上来就 enforce
直接上线 Content-Security-Policy 容易炸掉整个前端,尤其是已有大量内联脚本的老项目。先切到报告模式,让浏览器上报违规但不阻断:
add_header Content-Security-Policy-Report-Only "default-src 'none'; script-src 'self'; img-src 'self' data:; report-uri /csp-report.php;" always;
-
/csp-report.php必须能接收 POST 请求,读取原始 JSON:file_get_contents('php://input'),然后记录日志(注意过滤document-url和blocked-uri字段防注入) - 报告里会明确告诉你哪段内联脚本缺 nonce、哪个
eval()被拦、甚至第三方库从哪个域名加载失败 - 观察 3–5 天真实用户行为,再把
Content-Security-Policy-Report-Only改成Content-Security-Policy,并收敛策略
容易被忽略的 sandbox 和 frame-ancestors 冲突
很多人以为 CSP 的 sandbox 指令只是给 iframe 用的,其实它也能直接加在主页面响应头里,限制整个页面的脚本能力(比如禁用弹窗、表单提交)。但要注意:它和 frame-ancestors 是正交指令,不能混用逻辑。
-
frame-ancestors 'none'是防点击劫持,禁止你的页面被嵌入别人 iframe;sandbox allow-scripts是限制当前页面自身能力,两者可共存,但sandbox一旦启用,默认禁掉所有能力,必须显式放开 - 若你在 Nginx 里写了
add_header Content-Security-Policy "sandbox; frame-ancestors 'none';",页面将无法运行任何脚本(包括allow-scripts没写),导致白屏 - 真要用 sandbox,至少得带上
allow-scripts allow-same-origin,否则连同源 AJAX 都发不出去
真正难的不是写出那行 add_header,而是搞清每条指令在真实流量里的行为边界——比如 upgrade-insecure-requests 在 HTTP 环境下无效,strict-dynamic 在没 nonce 的页面里等于摆设,还有 CDN 或 WAF 中间件偷偷覆写头。上线前务必用 curl -I 和真实浏览器双验证。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











