Web 服务器暴露在公网之后,每天都会收到大量扫描器、爬虫与攻击流量。很多入侵的第一步并非复杂的漏洞利用,而是先通过响应指纹判断服务器的版本与组件,再挑选对应的已知漏洞发起攻击。Nginx 作为接入层的第一道关口,天然具备隐藏指纹、过滤请求、限制连接、下发安全响应头的能力。本文按「隐藏指纹 → 请求过滤 → 连接限制 → 响应头加固 → 恶意流量拦截」的顺序,给出可逐条落地的 Nginx 安全加固清单。
一句话总结: Nginx 安全加固的实质是在接入层收敛攻击面,把版本指纹、非法请求、超量连接与恶意流量拦截在业务代码之前。
1. 隐藏版本与指纹消除
一句话总结: 关闭 server_tokens、定制错误页并清除组件标识,让扫描器无法通过响应头与错误页判断 Nginx 版本与后端技术栈。
Nginx 默认会在响应头与默认错误页中携带版本号,例如 Server: nginx/1.24.0。攻击者据此可以直接查 CVE 表,针对该版本发起定向攻击。第一步加固是关闭版本号输出:server_tokens off 后响应头只保留 Server: nginx,去掉了点号后的具体版本号。
# 关闭版本号,只暴露 "nginx" 字样
server_tokens off;
# 需要 ngx_headers_more 模块时,可彻底清除或改写 Server 头
more_clear_headers Server;
more_set_headers "Server: ws-edge";
server_tokens off 已经足够应对绝大多数扫描器,但部分安全要求严格的环境还需要彻底移除 Server 头或改写为自定义值,这需要 ngx_headers_more 模块(OpenResty 默认自带,Nginx 官方版需单独编译)。more_set_headers 可以改写任意响应头,把真实组件标识替换成迷惑性的值,进一步提高指纹收集成本。
除了响应头,默认错误页也会泄露版本信息与文件路径:Nginx 自带的 404 页面底部通常会显示 nginx/1.24.0,后端应用(如 Tomcat、PHP)的默认错误页则可能泄露绝对路径与框架版本。加固时应统一替换为自定义错误页:
# 统一错误页,避免泄露默认页中的版本与路径
error_page 400 /err/400.html;
error_page 403 /err/403.html;
error_page 404 /err/404.html;
error_page 500 502 503 504 /err/5xx.html;
location = /err/400.html { internal; root /srv/error_pages; }
location = /err/403.html { internal; root /srv/error_pages; }
location = /err/404.html { internal; root /srv/error_pages; }
location = /err/5xx.html { internal; root /srv/error_pages; }
internal; 是关键:它保证错误页只能被 Nginx 内部跳转访问,外部请求直接访问 /err/404.html 也会被拒绝。错误页内容建议保持极简,只提供状态码与提示文字,不携带任何版本、时间戳或路径信息。
指纹隐藏不只是 Nginx 自身。反向代理后端的应用如果输出 X-Powered-By: PHP/7.4、X-AspNet-Version 之类的头,也会把技术栈暴露给攻击者。加固时要在 Nginx 层统一清除这些标识,同时把后端 5xx 错误页统一替换为定制页,避免后端默认页泄露内部信息。指纹隐藏是「纵深防御」的第一层,它不能阻止攻击,但能让攻击者无法低成本地挑选攻击路径,从而显著提高自动化扫描的无效命中率。
2. 请求过滤与参数校验
一句话总结: 在 location 上用 limit_except 收紧方法、校验关键请求头、规范化 URI,把明显非法的请求挡在业务层之外。
很多攻击在 URI、请求方法或请求头上就有明显特征:GET 以外的危险方法、超长参数、畸形请求头、目录穿越路径。Nginx 可以在接入层做第一道过滤,语法简单、开销极低。方法过滤用 limit_except 比 if 更干净,它直接声明「该 location 只允许哪些方法,其余一律拒绝」:
# 只读接口:只允许 GET/HEAD,其余方法一律拒绝
location /api/read/ {
limit_except GET HEAD {
deny all;
}
proxy_pass http://api_cluster;
}
# 显式拦截带请求体的 GET(常见于扫描器探测)
if ($request_method = GET) {
if ($content_length) {
return 400;
}
}
请求头校验适合用 map 把「非法特征」映射为拒绝。例如很多扫描器会携带伪造的 Content-Length: 0、异常的 Transfer-Encoding 或超大的 Accept-Encoding 头,接入层可针对这些已知畸形特征做拦截。更常见的是校验 Host 头:把 Host 头白名单化,能有效防止 Host 头注入与 DNS 重绑定攻击:
# 只允许白名单 Host,其余一律断开连接(444)
map $http_host $is_allowed_host {
hostnames;
default 0;
.example.com 1;
api.example.com 1;
admin.example.com 1;
}
server {
listen 80 default_server;
server_name _;
if ($is_allowed_host = 0) {
return 444; # 444 表示直接断开连接,不给任何响应
}
}
return 444 是 Nginx 特有的非标准状态码,直接关闭连接,扫描器得不到任何 HTTP 响应,减少了探测反馈。对于业务服务所在的 443 端口,同样应配置 Host 白名单,避免攻击者用 IP 直连绕过虚拟主机的鉴权逻辑。
URI 规范化同样重要。../、%2e%2e 编码穿越、双斜杠等路径变体会绕过部分业务层的鉴权判断,接入层应拒绝这类畸形路径。Nginx 的 merge_slashes 能把 // 合并为 /,缓解一部分问题,更彻底的做法是把含编码穿越特征的请求直接拒绝:
# 拦截编码穿越与畸形路径
location ~ (\.\./|\.\.%2f|%2e%2e|//) {
return 444;
}
# 拦截超长 URI 与参数(防哈希碰撞与解析耗尽)
large_client_header_buffers 4 8k;
client_header_buffer_size 4k;
请求过滤的原则是「默认拒绝,显式放行」,把过滤规则沉淀成配置资产,与业务路由规则一起评审、一起发布。过滤规则要避免「一刀切」:每个规则上线前先评估对正常流量的影响,命中日志单独收集,定期复盘误杀比例。
3. CC 防护与连接限制
一句话总结: limit_req 与 limit_conn 分别控制请求速率与并发连接,配合超时与请求体大小限制,可有效压制 CC 攻击与慢速攻击。
CC 攻击的本质是用大量低频请求或大量并发连接耗尽服务器资源。Nginx 的 limit_req(漏桶)与 limit_conn(并发连接)是两道核心防线,上一篇文章《Nginx 限流与安全防护》已详细讲过漏桶参数,这里给出安全视角的组合配置:
limit_req_zone $binary_remote_addr zone=cc_req:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=cc_conn:10m;
server {
location / {
# 每 IP 每秒 10 次,允许 20 个突发后开始丢弃
limit_req zone=cc_req burst=20 nodelay;
# 每 IP 同时最多 30 个连接
limit_conn cc_conn 30;
# 触发限流时返回 429
limit_req_status 429;
limit_conn_status 429;
proxy_pass http://web_cluster;
}
}
limit_req_status 429 与 limit_conn_status 429 让被限流的请求以标准的 429 状态码返回,客户端可以据此做退避重试,而不是收到 503 或连接被直接切断。429 响应建议再带上 Retry-After 头,配合 add_header 下发,客户端就能合理安排重试时机。
CC 防护还要防慢速攻击(Slowloris)。Slowloris 通过「连接建立后不发送完整请求体」的方式长期占用 worker 连接,导致连接池耗尽。Nginx 默认的 client_header_timeout、client_body_timeout、send_timeout 已经提供了基础防护,但需要显式收紧并限制请求体大小:
http {
# 慢速攻击防护:收紧各类超时
client_header_timeout 10s;
client_body_timeout 10s;
send_timeout 10s;
keepalive_timeout 15s;
# 限制请求体大小,拒绝超大上传与畸形分块
client_max_body_size 10m;
# 丢弃分块请求的空分块(防 chunked 攻击)
chunked_transfer_encoding on;
}
连接限制要同时考虑 Nginx 与后端的容量。limit_conn 只是 Nginx 侧的准入控制,后端连接池同样有限,因此限流参数要与后端承载能力对齐,而不是拍脑袋定值。CC 防护上线后要监控限流触发量:如果大量正常用户被误杀,说明阈值过低;如果限流几乎没有触发而资源仍在耗尽,则问题可能在应用层而非接入层。限流本身会消耗 CPU 与共享内存,压测时要包含限流触发路径,确认限流模块不会成为新的瓶颈。
4. 安全响应头
一句话总结: 通过 add_header 统一下发 CSP、HSTS、X-Frame-Options 等安全响应头,在浏览器侧建立基础防护边界。
安全响应头是浏览器侧的第一道防线,它们不消耗后端资源,却能把 XSS、点击劫持、MIME 嗅探等风险挡在渲染层。Nginx 在 server 块统一下发这些头是最省事、最不易遗漏的做法:
server {
# 点击劫持防护:禁止页面被 iframe 嵌入
add_header X-Frame-Options "SAMEORIGIN" always;
# MIME 嗅探防护:禁止浏览器猜测响应类型
add_header X-Content-Type-Options "nosniff" always;
# 强制 HTTPS:一年内所有子域都走 HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 控制 Referer 泄露范围
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 关闭浏览器高级能力,收窄攻击面
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=()" always;
}
add_header 的 always 参数很重要:不带 always 时,4xx/5xx 响应不会携带这些头,错误页也就失去了保护。另一个坑是 add_header 的继承规则:一旦在 location 里新增了任何 add_header,该 location 就不会再继承 server 块里定义的其它 add_header。因此要么把安全头统一放在 http 块,要么每个 location 都补齐全套,避免某条路由悄悄丢失安全头。
内容安全策略(CSP)是其中最有分量也最需要调校的一项,它通过白名单控制页面可以加载的脚本与样式来源。一份保守的 CSP 如下:
# 严格 CSP:只允许同源脚本与样式
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'" always;
CSP 上线前务必先在 report-only 模式下观察一段时间,确认没有破坏正常业务后再切换为强制模式:
# report-only 模式:只上报违规不拦截
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; report-uri /csp-report" always;
frame-ancestors 'none' 与 X-Frame-Options 的职责重叠,现代浏览器优先支持 CSP 的 frame-ancestors,两者同时下发不会冲突。安全响应头的维护与业务耦合度低,可以作为站点的统一基线配置,随加固清单一起定期复查。如果站点引用了第三方 CDN 脚本或内联脚本,CSP 白名单要相应放开,但每放开一项都意味着攻击面的扩大,需要权衡。
5. 防爬与恶意流量
一句话总结: 用 map 按 UA、Referer、来源 IP 等特征分流恶意流量,配合动态封禁脚本把已知爬虫与扫描器挡在接入层。
搜索引擎爬虫是良性流量,但暴力采集、撞库、扫描器、数据抓取工具则是恶意流量。接入层过滤恶意流量的核心是「特征识别 + 分流拒绝」。UA(User-Agent)是最常见的识别维度,用 map 维护一份可扩展的黑名单:
# 按 UA 特征识别恶意爬虫
map $http_user_agent $bad_bot {
default 0;
"~*curl" 1;
"~*(python|scrapy|libwww)" 1;
"~*(sqlmap|nikto|nmap|masscan)" 1;
"~*(ahrefs|semrush|mj12bot)" 1; # 激进采集的 SEO 爬虫可按需拦截
"~*(wget|java|okhttp)" 0; # 部分合法下载工具,谨慎处理
}
server {
if ($bad_bot = 1) {
return 403;
}
location / { proxy_pass http://web_cluster; }
}
注意 map 的值与 if 的配合:if ($bad_bot = 1) 是 Nginx 中少数被官方支持的合法用法(与 return 搭配),不应在 if 里做复杂逻辑。UA 黑名单要避免误伤:很多采集工具会用浏览器 UA 伪装,因此 UA 过滤只能拦截「诚实暴露特征」的爬虫,真正的防御还需要结合行为分析。
Referer 与来源 IP 是另外两个维度。盗链与采集往往带有固定的 Referer 特征,可用 valid_referers 校验;扫描器往往集中在特定网段,可结合 geo 模块按 IP 归属地或网段封锁:
# geo 模块:按网段封禁
geo $blocked_region {
default 0;
10.20.0.0/16 1; # 内部异常网段
203.0.113.0/24 1; # 示例恶意网段
}
server {
if ($blocked_region = 1) {
return 403;
}
}
# 防盗链:只允许本站与空 Referer(直接输入 URL)访问
location ~* \.(gif|jpg|png|webp)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
}
静态特征只能拦截已知流量,动态封禁才能应对变化中的恶意来源。生产实践中常见的做法是把 Nginx 访问日志喂给分析程序,识别出高频恶意 UA 或高频 403 来源 IP,再通过管理接口动态更新 Nginx 的封禁配置并 reload。如果使用 OpenResty,可以直接用 lua_shared_dict 在内存中维护动态封禁名单,无需 reload 即可生效(后续《Lua 扩展与 OpenResty》一文会展开)。恶意流量过滤要避免误伤:把 UA 过滤做成 map 白名单式管理,每个新特征都先观察命中率再决定是否启用。
6. 目录与敏感资源防护
一句话总结: 禁止目录列举、屏蔽隐藏文件与敏感路径(.git/.env/备份文件),并把敏感文件访问重定向到 404 以迷惑攻击者。
很多数据泄露源于「敏感文件被直接访问」:.git 目录泄露源码、.env 泄露密钥、备份文件泄露数据库。接入层应当把这些路径一律挡在门外。最稳妥的做法是返回 404 而非 403,因为 403 会告诉攻击者「路径存在但不允许访问」,等于给了探路的回应。
server {
# 禁止目录列举
autoindex off;
# 隐藏文件与敏感目录:一律 404
location ~ /\. {
deny all;
return 404;
}
# 敏感后缀:备份、SQL 导出、压缩包、编辑器临时文件
location ~* \.(env|bak|sql|conf|log|tar|gz|zip|swp|old|~)$ {
deny all;
return 404;
}
# 版本库与密钥目录
location ~ ^/(\.git|\.svn|\.hg|\.env) {
deny all;
return 404;
}
}
正则 location 的优先级需要留意:location ~ 的优先级高于普通前缀 location,但低于 location =。上述规则应当放在 location / 之前定义,确保命中敏感路径时先走正则拒绝。对于静态文件目录,还应在 root 层面保证 Web 根目录之外的文件不可被请求,例如备份文件放在 root 之外,仅通过专用入口下载。
另一个常被忽略的点是 location 的精确匹配与 alias 的目录穿越。使用 alias 时若路径末尾的斜杠处理不当,可能造成目录穿越读取任意文件。下面的写法是安全基线:
# alias 与 location 的斜杠必须一致,防止目录穿越
location /static/ {
alias /srv/web/static/; # 两端都以 / 结尾
}
# 使用 root 时无需担心该问题,root 会把 location 路径拼到末尾
location /static/ {
root /srv/web;
}
敏感资源防护要定期巡检。加固清单本身也会过时:新框架会引入新的敏感路径(如 Laravel 的 .env、Spring Boot 的 actuator、配置中心的 /config 端点),因此建议把「敏感路径黑名单」做成可扩展列表,配合扫描器定期验证,确保新增的敏感路径及时进入拒绝名单。Git 提交前用 git-secrets 或 pre-commit 钩子扫描,从源头杜绝敏感文件进入版本库。
7. 传输安全与纵深防御
一句话总结: TLS 基线、与 WAF/防火墙联动、日志审计构成纵深防御,接入层加固只是整体安全的其中一环。
前面的加固都发生在 HTTP 应用层,传输层安全同样不能缺席。TLS 基线建议:只启用 TLS 1.2 与 TLS 1.3、禁用弱密码套件、开启 HSTS 与 OCSP Stapling。这些内容在《Nginx HTTPS 与 TLS 加固》与《Nginx TLS 进阶与 mTLS》中有完整实践,这里给出一份可直接套用的基线:
server {
listen 443 ssl;
http2 on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000" always;
}
ssl_session_tickets off 会在每次会话结束后禁用 TLS 会话票据,避免会话票据被截获后冒充会话。对于需要会话复用的场景,使用共享内存的 ssl_session_cache 即可,无需会话票据。TLS 基线的完整参数(证书链、OCSP、mTLS)建议直接参考专题内的 TLS 专项文章。
接入层加固不能替代纵深防御。Nginx 之外的防线包括:云防火墙与安全组限定暴露端口;WAF(如 ModSecurity,或 OpenResty 生态的 lua-resty-waf)识别应用层攻击签名;日志审计发现异常访问模式。Nginx 在其中承担「流量闸门」的角色,它做粗粒度过滤,WAF 做应用层签名检测,安全组做网络层隔离,三层互补。
# 日志审计:把被拦截请求单独记录,便于分析误杀与绕过
log_format blocked '$remote_addr - $request_method $request_uri '
'$status $http_user_agent';
server {
set $blocked_log off;
if ($bad_bot = 1) { set $blocked_log on; }
access_log /var/log/nginx/blocked.log blocked if=$blocked_log;
}
纵深防御还要求「可观测」。加固之后要能回答三个问题:哪些请求被拦截了?拦得对不对?有没有绕过?这需要把 Nginx 拒绝日志单独落盘,定期分析拒绝样本与误杀比例。安全加固不是一次性动作,而是「加固 → 观测 → 调整 → 再加固」的持续循环。每次调整都先在小流量上验证,确认无误后再全量生效。
8. 总结
| 加固项 | 关键配置 | 作用 |
|---|---|---|
| 指纹隐藏 | server_tokens off、定制错误页、清除组件头 | 提高攻击者信息收集成本 |
| 请求过滤 | limit_except、Host 白名单、URI 规范化 | 拦截非法方法与畸形请求 |
| CC 防护 | limit_req、limit_conn、收紧超时与请求体 | 压制 CC 与慢速攻击 |
| 安全响应头 | CSP、HSTS、X-Frame-Options 等 | 浏览器侧基础防护 |
| 防爬过滤 | UA/Referer/IP 特征 map + geo | 拦截已知爬虫与扫描器 |
| 敏感资源 | 目录列举关闭、敏感路径 404 | 防止源码与密钥泄露 |
| 传输安全 | TLS 1.2/1.3 基线 + HSTS | 保障传输机密性 |
| 纵深防御 | 与 WAF、安全组、日志审计联动 | 多层互补降低整体风险 |
Nginx 安全加固的价值在于把大量低成本的攻击拦截在业务代码之前,让后端的每一分计算都服务于真实业务。加固要遵循「默认拒绝、显式放行」的原则,同时警惕过度加固导致的误伤——安全配置上线前要结合真实流量验证,上线后要持续观测拦截样本。安全不是一堆配置的堆砌,而是「收敛攻击面、持续观测、快速响应」的循环过程。接下来可以把限流、TLS、mTLS 等安全主题串起来,形成完整的网关安全体系,再结合日志与监控把安全事件的证据链补全。
延伸阅读
- Nginx 限流与安全防护 — limit_req 漏桶与 limit_conn 连接限制
- Nginx HTTPS 与 TLS 加固 — 传输层安全基线与 HSTS
- Nginx TLS 进阶与 mTLS — 双向 TLS 与证书轮换
- Nginx 核心配置结构 — location 匹配与指令上下文
- Nginx 日志分析与性能调优 — 安全日志审计与排错
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。