Nginx 动态 CSP 策略可通过 map 指令按域名、请求 ID 或地域实时生成,配合 add_header always 实现安全灵活的头部注入,无需后端修改。

直接在 Nginx 中用 add_header 写死 CSP 策略,简单但僵硬。真正安全又实用的做法,是让策略随请求动态变化——比如按域名区分 CDN 来源、按地区收紧脚本权限、或为每次页面请求生成唯一 nonce 放行内联脚本。这些都不需要改后端代码,全靠 Nginx 自身能力就能闭环实现。
用 map 指令按域名/子域切换 CSP 策略
把不同站点的策略逻辑提前定义在 http 块里,避免重复写多个 server 块:
- 必须在
http级定义map,不能放在server或location内 - 用
$host匹配原始 Host 头(注意大小写,建议统一小写) - 每个策略值必须用英文双引号包裹,防止空格或分号被误解析
- 用
default设兜底策略,避免未匹配时变量为空导致头缺失
示例配置:
http {
map $host $csp_policy {
default "default-src 'self'; script-src 'self';";
www.example.cn "default-src 'self'; script-src 'self' https://cdn.cn;";
www.example.com "default-src 'self'; script-src 'self' https://cdn.us 'unsafe-eval';";
test.example.com "Content-Security-Policy-Report-Only: default-src 'none'; report-uri /csp-report;";
}
<p>server {
listen 443 ssl;
server_name www.example.cn www.example.com test.example.com;
add_header Content-Security-Policy $csp_policy always;
}
}用 $request_id 自动生成 CSP nonce
解决内联脚本(<script>...</script>)无法通过严格 CSP 的问题,无需后端参与:
-
$request_id是 Nginx 原生 UUID,每次请求唯一,实时生成,零额外开销 - 先用
map提取并格式化为 base64 兼容字符串(如去掉短横线) - 在响应头中注入
script-src 'nonce-$csp_nonce' - 前端 HTML 的
<script nonce="xxx">中的值需与响应头中一致
配置片段:
map $request_id $csp_nonce {
default "";
"~^(.{8})(.{4})(.{4})" "$1$2$3";
}
<p>server {
add_header Content-Security-Policy "script-src 'self' 'nonce-$csp_nonce'; style-src 'self' 'unsafe-inline';" always;
}结合真实 IP 地理位置做区域化策略
反向代理场景下,若已通过 real_ip_header X-Forwarded-For 获取客户端真实 IP,可进一步按地域定制策略:
- 先用
geo模块标记国家/地区(如$is_cn为 1 表示国内用户) - 再用
map根据该布尔变量拼出对应 CSP 字符串 - CSP 规则本身不支持运行时表达式,所有判断必须在
map阶段完成 - 实际使用需配合 GeoIP 数据库,不能只靠网段硬编码
示意逻辑:
geo $remote_addr $is_cn {
default 0;
include geoip-country.conf; # 第三方 GeoIP 数据文件
}
<p>map $is_cn $csp_script_src {
1 "'self'";
0 "'self' <a href="https://www.php.cn/link/599eb7f487aef6c8cd90c4842503621a">https://www.php.cn/link/599eb7f487aef6c8cd90c4842503621a</a>";
}</p><p>server {
add_header Content-Security-Policy "default-src 'self'; script-src $csp_script_src;" always;
}关键细节和避坑点
动态注入 CSP 容易因配置细节失效,务必注意:
-
add_header ... always必须加always,否则 3xx/4xx/5xx 响应不带 CSP 头 - 正则匹配域名时写
server_name ~^([a-z]{2})\.example\.com$,开头加~\.防止cn.example.com错误匹配到example.com - 所有变量插值(如
$csp_policy)在add_header中不加引号;而静态字符串值(如map中的策略)必须加双引号 - location 块中的
add_header会覆盖上级同名头,建议统一在server或http级管理


















