用 map 指令动态设置 CSP 是更可靠的选择,因其在请求解析阶段完成变量映射,生成确定字符串供 add_header 使用,规避了 location 覆盖、继承失效等问题,且全局生效、轻量灵活。

用 map 指令动态设置 CSP,核心是把策略逻辑从“写死”变成“按需生成”,尤其适合多区域缓存站点——不同地区用户访问的可能是不同 CDN 节点、不同静态资源域名、甚至不同前端构建产物,硬编码一条 CSP 往往顾此失彼。关键不在加 header,而在让 header 的值能随请求上下文实时变化。
为什么 map 是更可靠的选择
add_header 本身不支持变量插值(比如 "script-src $cdn_domain" 会原样输出),而 map 可以在请求解析阶段就完成变量映射,生成确定字符串供 add_header 使用。它天然规避了 location 块覆盖、继承失效、重定向丢失等问题,也比用 Lua 或外部模块轻量得多。
-
map在http块中定义,全局生效,不受 server 或 location 作用域限制 - 匹配基于 Nginx 内置变量(如
$host、$arg_region、$http_x_forwarded_for),可精准区分区域来源 - 支持正则和字符串匹配,适合处理多级子域(如
cn.example.com、us.example.com)、路径前缀(如/jp/、/de/)或请求头标识
按区域域名动态生成 CSP 值
假设你通过子域区分区域:cn.example.com 对应中国 CDN(cdn-cn.example.com),us.example.com 对应美国 CDN(cdn-us.example.com),其他走默认 cdn-global.example.com。可在 http 块中这样配置:
map $host $csp_script_src {
~^cn\.example\.com$ "'self' https://www.php.cn/link/e8791c81f0dbb5e99c8abe851ec1900b 'unsafe-inline'";
~^us\.example\.com$ "'self' https://www.php.cn/link/39469285974d13e4e2a6d1010747820d 'unsafe-inline'";
default "'self' https://www.php.cn/link/65334334604197c6286c6edf2892909b 'unsafe-inline'";
}
<p>map $host $csp_img_src {
~^cn.example.com$ "'self' <a href="https://www.php.cn/link/e8791c81f0dbb5e99c8abe851ec1900b">https://www.php.cn/link/e8791c81f0dbb5e99c8abe851ec1900b</a> data:";
~^us.example.com$ "'self' <a href="https://www.php.cn/link/39469285974d13e4e2a6d1010747820d">https://www.php.cn/link/39469285974d13e4e2a6d1010747820d</a> data:";
default "'self' <a href="https://www.php.cn/link/65334334604197c6286c6edf2892909b">https://www.php.cn/link/65334334604197c6286c6edf2892909b</a> data:";
}</p><p>add_header Content-Security-Policy "default-src 'none'; script-src $csp_script_src; img-src $csp_img_src; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self';" always;</p>
注意:map 值必须是完整字符串,不能拼接;所有引用的变量(如 $csp_script_src)需先定义;add_header 必须带 always,否则 4xx/5xx 响应里不会带 CSP。
结合请求头或参数识别区域(无子域场景)
若区域由请求头(如 X-Region: JP)或 URL 参数(如 ?region=jp)传递,map 同样适用:
map $http_x_region $csp_base {
jp "'self' https://www.php.cn/link/bc19a99ed9cd4ddd7a57e89d112c1ade";
kr "'self' https://cdn-kr.example.com";
default "'self' https://www.php.cn/link/65334334604197c6286c6edf2892909b";
}
<p>map $arg_region $csp_base_fallback {
jp "'self' <a href="https://www.php.cn/link/bc19a99ed9cd4ddd7a57e89d112c1ade">https://www.php.cn/link/bc19a99ed9cd4ddd7a57e89d112c1ade</a>";
de "'self' <a href="https://www.php.cn/link/0ded6348389f35483e961d04d7babcc8">https://www.php.cn/link/0ded6348389f35483e961d04d7babcc8</a>";
"" $csp_base; # 参数为空时回退到 X-Region
}</p><p>add_header Content-Security-Policy "default-src $csp_base_fallback; script-src $csp_base_fallback 'unsafe-inline';" always;</p>
这种写法支持 fallback 链式判断,也便于灰度发布——比如只对 arg_region=beta 开放额外内联脚本白名单,其余保持严格策略。
避免常见陷阱
-
map不支持嵌套或条件运算,复杂逻辑需拆成多个 map 或前置预处理(如用set提取子串) - 所有
map变量名不能与内置变量同名,建议加前缀(如csp_)避免冲突 - CSP 值中含空格或引号时,
map定义里要用单引号包裹整个值,内部引号用双引号或转义 - 务必在生产环境开启
Content-Security-Policy-Report-Only模式先观察报告,再切到正式策略










