资讯动态

Nginx安全头配置实战:从HTTP响应头到网关层防护

发布时间:2026/10/18 2:28:25 来源:尧图企业网站定制
1. 安全头到底解决了什么问题做 Web 服务的人应该都有过这种经历应用明明写得挺稳代码也没有明显漏洞结果一上扫描器就是一片红。点开报告一看大部分问题根本不是应用逻辑出错而是响应头缺失、Cookie 属性不对、MIME 类型猜测这些“边缘地带”的毛病。说白了这是 HTTP 协议层面留给攻击者的空间而 Nginx 作为最外层网关恰好是把这些空间堵上的最佳位置。我最早接触安全头是在一次内部安全审计之后。当时负责的一个业务系统被第三方扫描出十几项中高危问题开发团队很委屈说代码里没有 SQL 注入也没有文件上传漏洞。后来逐条看十有八九是 X-Content-Type-Options 缺失、Clickjacking 防护没有、HSTS 没开这类问题。修复方式也特别简单——不需要改业务代码只需要在 Nginx 配置里加上几行响应头即可。那一次之后我养成一个习惯不管项目大小Nginx 配置里必带一套完整的安全头模板。安全头并不是什么高深技术它就是一组 HTTP 响应头字段告诉浏览器“这个响应应该怎么处理、哪些能力可以禁用、哪些行为需要限制”。浏览器是执行方服务器是策略方策略方把话说清楚执行方就能帮用户挡住一大批常见攻击。适用对象很广只要你的服务是通过 Nginx 对外提供的无论是静态站点、反向代理、负载均衡还是作为 API 网关都可以在 Nginx 这一层统一加上安全头。这样做的好处是一处配置、全局生效不用跑到每个应用里去改代码。2. 每个安全头的含义和配置方法2.1 X-Content-Type-Options: nosniff这个头最简单但作用不能小看。它告诉浏览器不要对响应内容做 MIME 类型猜测服务器声明是什么类型就按什么类型处理。举个例子你上传了一个文件服务端返回的 Content-Type 是 text/plain但如果内容里嵌了 HTML 标签有些浏览器会自作主张把它当 text/html 渲染。攻击者可以利用这个特性把恶意脚本伪装成普通文件传上去用户一点开就中招。加了 nosniff 之后浏览器老实了声明 text/plain 就按纯文本显示不再瞎猜。add_header X-Content-Type-Options nosniff always;注意后面的 always 参数它表示不管响应状态码是 200 还是 404、500都会带上这个头。如果不加 alwaysNginx 默认只对 200、201、204、206、301、302、303、304、307、308 这些“标准响应”生效错误页就漏掉了。加了这个参数保证所有响应都覆盖。2.2 X-Frame-Options: SAMEORIGIN这个头管的是“能不能被别家页面套进 iframe”。攻击者经常构造一个恶意页面用 iframe 把你网站的登录页嵌进去用户在不知不觉中把账号密码输入到看似正常的框里实际信息却被截获这就是经典的点击劫持Clickjacking。SAMEORIGIN 表示只有同源页面才能用 iframe 嵌入本站内容。如果你确定自己的页面绝对不需要被第三方站点嵌入可以直接用 DENY完全禁止。add_header X-Frame-Options SAMEORIGIN always;顺带一提这个头虽然老但兼容性极好是所有浏览器都会认的。后来出现的 Content-Security-Policy 的 frame-ancestors 指令功能上部分重叠但 X-Frame-Options 依然是保底方案。2.3 X-XSS-Protection: 1; modeblock当年 IE 和早期 Chrome 内置了一个 XSS 过滤器这个头用来控制它的行为。1; modeblock 表示开启过滤器如果检测到反射型 XSS 攻击浏览器直接阻断页面渲染。这里要诚实地说明现代浏览器已经逐渐移除了这个内置过滤器因为过滤器本身也有被绕过的风险反而可能引入问题。但考虑到一些老版本的浏览器仍然存在加上它成本极低、副作用极小还是可以保留一行。add_header X-XSS-Protection 1; modeblock always;如果你用的是 Chrome 或 Firefox 最新版本这个头基本是心理安慰但心理安慰在某些行业合规扫描里能帮你少几个告警。2.4 Referrer-Policy: strict-origin-when-cross-origin这个头控制的是浏览器在跳转、加载子资源时要不要在 Referer 字段里带上来源地址。默认情况下浏览器会在跨域请求时带上完整的来源 URL包括路径和查询参数。这意味着你的 URL 如果带了个 token 或者敏感参数跳转到第三方站点时对方可能从 Referer 里看到这些信息。泄露面不小。strict-origin-when-cross-origin 的含义是同源请求带完整路径跨域请求只在 HTTPS 场景下带来源域名降级到 HTTP 时完全不发送。这个策略既兼顾了统计需求又最大程度避免了敏感信息泄露。add_header Referrer-Policy strict-origin-when-cross-origin always;这也是目前行业里公认的默认推荐值没有特殊需求的话不用改。2.5 Permissions-Policy这个头用来禁用浏览器的一些敏感功能比如摄像头、麦克风、地理位置、USB 设备等。如果你的网站根本用不到这些能力那就不应该允许页面调用它们。攻击者即使拿到了 XSS 执行权限也会发现很多能力被锁死攻击面进一步缩小。我常用的一套配置是直接阉割掉所有和业务无关的功能add_header Permissions-Policy camera(), microphone(), geolocation(), browsing-topics();Permissions-Policy 语法里括号为空代表完全禁止括号里写 self 代表只允许同源使用写具体域名代表允许指定源使用。如果你的页面确实需要某个功能就把它改成 self 或者对应的域名不要全部一刀切否则业务会出问题。早年这个头的名字叫 Feature-Policy后来规范改名成了 Permissions-Policy。有些老文章还在讲 Feature-Policy注意不要抄错了。2.6 Strict-Transport-SecurityHSTS这个头只对 HTTPS 有意义。它告诉浏览器在指定的时间内只能通过 HTTPS 访问本站不要用 HTTP也不要在用户手动输入 http:// 时发出版本不安全的请求。没有 HSTS 的时候用户如果输入的是 http://浏览器先发起一次明文请求然后服务端 301 跳转到 HTTPS。这个过程本身存在一个中间人劫持窗口攻击者在用户输入地址到收到跳转之间可以插入恶意响应。加了 HSTS 之后浏览器在有效期内直接一步到位用 HTTPS 访问连发 HTTP 请求的机会都不给。max-age 的秒数一般设得比较长常见的是 15552000也就是 180 天。includeSubDomains 表示子域名同样强制 HTTPSsubdomains 如果你确定子域都支持 HTTPS可以加。preload 则是申请浏览器预加载列表时需要带上的标记。add_header Strict-Transport-Security max-age15552000; includeSubDomains always;注意HSTS 只在 HTTPS 响应里生效如果你整站都是 HTTP这个头加了也白加。另外一旦浏览器记住了 HSTS 策略你临时想切回 HTTP 调试会很痛苦所以小范围测试时可以把 max-age 调小一点。2.7 Content-Security-PolicyCSPCSP 是所有这些头里块头最大、配置最复杂的一个但也是能效最强的一个。它允许你明确告诉浏览器页面允许加载哪些来源的脚本、样式、图片、字体、连接禁止哪些行为。一个典型的配置长这样add_header Content-Security-Policy default-src self; script-src self unsafe-inline; style-src self unsafe-inline; img-src self data:; font-src self; connect-src self always;这个配置的意思是所有资源默认只允许从同源加载脚本允许同源以及内联脚本样式允许同源以及内联样式图片允许同源以及 data: URI字体只允许同源网络请求只允许同源。CSP 最大的价值在于即使攻击者成功注入了恶意脚本只要你的 script-src 不允许外部域名他的脚本要么加载不出来要么执行不了等于把 XSS 的伤害降到了最低。但 CSP 也是最容易误伤业务的头。内联脚本被禁、外部 CDN 脚本被禁、动态生成的 style 属性被禁随便哪个都能让你页面瞬间白屏。所以配置 CSP 之前先把站点的资源来源全部梳理一遍再逐条收敛策略。不要在没做充分测试的情况下直接上全套 CSP先一步一步加看报错再调整。2.8 Cross-Origin-Embedder-Policy / Cross-Origin-Opener-Policy这两个头是从 COOP 和 COEP 组合来的主要处理跨源隔离问题。简单解释一下Cross-Origin-Opener-Policy 控制的是你的页面在打开另一个 Window 时和那个窗口之间的关联关系Cross-Origin-Embedder-Policy 控制的是页面是否可以加载跨源的资源以及这些资源是否需要显式地被授权。add_header Cross-Origin-Opener-Policy same-origin always; add_header Cross-Origin-Embedder-Policy require-corp always;这个组合会把你的页面置入一个“跨源隔离”的状态只有明确声明了 CORP 头的资源才能被加载。好处是可以启用一些高性能浏览器特性比如 SharedArrayBuffer。坏处是如果你的页面加载了不少第三方资源而这些资源没有配合加 CORP 头页面会直接加载失败。所以这两个头我不建议无脑上。只有在确实需要用 SharedArrayBuffer 之类的强隔离特性或者在做安全级别要求非常高的内部系统时才考虑开启。3. 从安全头到安全配置安全头虽然好看但它只是 Nginx 安全配置里的一个维度。我这里把和它配合密切的几个配置一起理一理因为它们本质上是在解决同一类问题让响应更严格、让连接更可信、让暴露面更小。3.1 关闭服务器版本号Nginx 默认会在响应头里带上版本号形如 Server: nginx/1.18.0。版本号一旦暴露攻击者就可以对照已知的漏洞库去尝试利用比如某个版本存在的解析漏洞或者越权读取问题。关闭的方式很简单在配置文件里加一行server_tokens off;这样响应头只会显示 Server: nginx不透露具体版本。已经踩在坑里的老版本也可以减少被识别的概率。3.2 禁用不必要的请求方法默认配置下Nginx 支持 GET、HEAD、POST、PUT、DELETE、OPTIONS这些方法。如果你的业务只是展示内容完全不需要 PUT 和 DELETE就应该把它们挡在门外。if ($request_method !~ ^(GET|HEAD|POST)$) { return 405; }这个 if 用在 location 或 server 块里都可以。405 比 403 更合适因为语义上是“方法不被允许”而不是“身份不被认可”。3.3 限制上传大小控制 client_max_body_size 可以防止恶意请求用超大报文打内存。不需要接收文件上传的接口直接设一个小值client_max_body_size 1m;需要上传文件的接口再单独放大比如 50m 或者 100m。这里的关键是不要全局放开要有收有放。3.4 超时时间收紧反向代理场景里代理超时时间设置不合理容易被慢速攻击拖住连接。核心参数有这几个proxy_connect_timeout 5s; proxy_read_timeout 10s; proxy_send_timeout 10s;这几个值可以根据业务接口的耗时情况做微调但不要设成 60s 起步的大数值。连接保持时间也不要太长keepalive_timeout 控制在 30s 以内比较稳妥。4. 实操记录完整配置模板前面讲的这些头放在一起组合成一个可直接用的 Nginx server 块配置。我自己在内部项目上用的模板大概是这样的server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; server_tokens off; # 安全头 add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; add_header X-XSS-Protection 1; modeblock always; add_header Referrer-Policy strict-origin-when-cross-origin always; add_header Permissions-Policy camera(), microphone(), geolocation(), browsing-topics(); add_header Strict-Transport-Security max-age15552000; includeSubDomains always; add_header Content-Security-Policy default-src self; script-src self unsafe-inline; style-src self unsafe-inline; img-src self data:; font-src self; connect-src self always; # 请求方法限制 if ($request_method !~ ^(GET|HEAD|POST)$) { return 405; } # 请求体大小限制 client_max_body_size 2m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 5s; proxy_read_timeout 10s; proxy_send_timeout 10s; } }类型安全头配置位置生效条件作用基础X-Content-Type-Optionsserver 块全部响应禁止 MIME 猜测基础X-Frame-Optionsserver 块全部响应防点击劫持基础X-XSS-Protectionserver 块全部响应老浏览器 XSS 过滤基础Referrer-Policyserver 块全部响应控制来源信息泄露能力限制Permissions-Policyserver 块全部响应禁用浏览器敏感功能传输安全HSTSserver 块HTTPS 响应强制 HTTPS 访问内容策略CSPserver 块全部响应锁定资源来源这个模板配置可以直接粘到你的配置里改一下域名和后端地址就能用。至于 COOP / COEP建议按需开启不要纳入默认模板。5. 常见问题与排查技巧实录5.1 配置了 add_header 但不生效最典型的原因是 add_header 的作用域覆盖问题。Nginx 有个规则如果当前层级的块里出现过 add_header那么上一层的 add_header 全部失效如果没有出现过则继承上一层的。也就是说你在 server 块定义了 add_header然后在某个 location 里又写了一个 add_header那么这个 location 里只会带上它自己写的那一个server 层的全部被覆盖。解决办法有两个要么把公共的 add_header 复制一份到 location 里要么把需要覆盖的头单独放一层其他全部放到 location 内部。5.2 CSP 导致页面白屏最常见的是 script-src 只写了 self但页面里用了内联脚本或者引用了外部 CDN。浏览器控制台会告诉你哪个资源被拒绝了报错信息形如 Refused to load the script ... because it violates the following Content Security Policy directive。排查思路先把 CSP 头去掉确认页面正常再按 script-src、style-src、img-src、connect-src 逐项放开每放开一项刷新页面看是否恢复找到罪魁祸首之后针对那个域名加到对应指令的白名单里。这里要强调的是CSP 设计成白名单不是用来刁难开发的而是用“默认全禁按需放行”的思路来收敛风险。宁可配置繁琐一点也比裸奔要强。5.3 HSTS 开启后没法用 HTTP 调试如果你设置了 max-age15552000浏览器会在半年内记住这个强制 HTTPS 策略你想在本机用 http:// 访问调试会被浏览器直接拦下来。解决方式开发机上的浏览器单独关掉 HSTS或者用不同的域名、不同的浏览器配置文件。Chrome 里可以访问 chrome://net-internals/#hsts在 Delete domain security policies 输入框里删掉对应域名。所以我在新环境里测试 HSTS 时第一次会故意把 max-age 设成 60 秒左右验证没问题再改成长期值。这样如果配置有问题最多一分钟就能恢复。5.4 检查安全头是否生效无脑且可靠的方式是用浏览器开发者工具的 Network 面板选中一个请求查看 Response Headers 里有没有对应的头。一个一个对缺哪个补哪个。批量检查可以用 curlcurl -I https://your-domain.com输出的响应头一目了然。如果你需要扫描全站也可以写个简单脚本遍历主要路径逐一检查。5.5 如果业务里有第三方接口第三方接口一般会出现在两个场景前端页面直接调第三方接口或者后端代理第三方接口。前端直接调第三方接口时CSP 的 connect-src 必须把第三方域名加进去否则请求会被浏览器拦截。后端代理的情况下CSP 不用改因为浏览器看到的请求始终是同源的由后端代发。这个区别经常被忽略。加 CSP 的时候先确认业务里的第三方请求是前后端直连还是后端代发再决定 connect-src 要不要放行。6. 最后再聊几句实操体会配置安全头这件事本身没有难度难的是搞清楚场景和取舍。我见过一些人把网上的安全头模板一梭子全粘上去结果上线当天页面白屏、第三方小部件全挂最后灰溜溜全部回滚。也见过一些人只加了一个 HSTS 就觉得自己安全了其实一堆基础配置还裸着。我的建议是分三步走第一步先把 X-Content-Type-Options、X-Frame-Options、Referrer-Policy 这些不会影响业务的头加上去几乎零风险第二步关闭版本号、限制方法、限制上传大小、收紧超时时间最后再根据自己的业务形态逐步引入 CSP 和 Permissions-Policy。每一层都要在测试环境验证过再上生产。我个人在实际操作中最深的体会是安全头不是配完就完事的。业务在演进会新增 CDN 域名、会引入新的第三方 SDK、会开放新的接口路径每次调整都要回头看 CSP 和 Permissions-Policy 是否需要同步更新。把这些检查和发布流程绑在一起形成习惯就不会出大问题。你自己的站点从今天开始先加上最基础的四个头跑两天观察一下有没有异常大概率你会发现一切照常但扫描报告已经好看了不少。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑