服务器只要暴露在公网上,就会有爬虫扫描、暴力破解、CC 攻击、恶意爬虫。Nginx 默认配置是"能跑就行",完全没有防护。这篇就来聊安全加固,核心三板斧:限速防刷、封禁恶意 IP、防 DDoS 基础。
💡 提示: 安全加固的原则是"最小暴露面"——能不暴露的端口就不暴露,能不给的信息就不给,能不开放的路径就不开放。每减少一个暴露面,攻击面就少一个。
攻击者在攻击之前会先"踩点"——探测服务器版本、模块、操作系统。信息越少,攻击成本越高。Nginx 默认会在错误页面和响应头里暴露版本号:
# 默认的响应头
Server: nginx/1.24.0
# 默认的 404 页面
404 Not Found
nginx/1.24.0攻击者看到 nginx/1.24.0,就能精准查找这个版本的已知漏洞。
# /etc/nginx/nginx.conf 的 http 块里
http {
server_tokens off;
}改完后效果:
| 项目 | server_tokens on(默认) | server_tokens off |
|---|---|---|
| 响应头 | Server: nginx/1.24.0 | Server: nginx |
| 错误页面 | 底部显示 nginx/1.24.0 | 底部只显示 nginx |
| 暴露信息 | 版本号 + 模块信息 | 仅服务器名称 |
⚠️ 注意: server_tokens off 只是隐藏版本号,不是真正的安全措施。它增加的是攻击成本,不是安全性本身。真正的安全是及时更新 Nginx 版本和系统补丁。
server_tokens off 还会暴露 Server: nginx,如果想完全去掉:
| 方法 | 效果 | 需要额外模块 |
|---|---|---|
| server_tokens off | 隐藏版本号,保留 Server: nginx | 不需要 |
| more_clear_headers | 完全去掉 Server 头 | headers-more |
| 编译时修改源码 src/http/ngx_http_header_filter_module.c | 改掉默认 Server 字符串 | 不需要(但需要编译) |
| 安全头 | 作用 | 推荐值 |
|---|---|---|
| X-Content-Type-Options | 防止浏览器猜测文件类型 | nosniff |
| X-Frame-Options | 防止页面被 iframe 嵌套 | SAMEORIGIN |
| X-XSS-Protection | 启用浏览器 XSS 过滤 | 1; mode=block |
| Content-Security-Policy | 限制资源加载来源 | 按需配置 |
| Strict-Transport-Security | 强制 HTTPS(HSTS) | max-age=31536000 |
| Referrer-Policy | 控制 Referer 泄露 | strict-origin-when-cross-origin |
| Permissions-Policy | 禁用不需要的浏览器功能 | 按需配置 |
💡 提示: always 参数很重要。不加 always 时,这些头只在 2xx/3xx 响应中添加;加了 always 后,4xx/5xx 错误页面也会带上安全头。攻击者经常故意请求错误页面来探测信息。
不限速时,一个 IP 一秒发 1000 个请求,Nginx 全部转发到后端,后端直接被打满。限速就是给每个 IP 设一个请求频率上限,超过的请求直接拒绝。
| 攻击类型 | 不限速时 | 限速后 |
|---|---|---|
| CC 攻击 | 每秒 1000 请求打满后端 | 每秒只放 10 个,其余 503 |
| 暴力扫描目录 | 爬虫秒级扫完所有路径 | 每秒 2 个请求,扫描成本翻 500 倍 |
| 恶意爬虫 | 疯狂抓取内容 | 限制到人类正常速度 |
| API 滥用 | 恶意刷接口 | 超出频率直接拒绝 |
💡 提示: $binary_remote_addr 比 $remote_addr 省内存。文本格式的 IPv4 地址占 15 字节(如 192.168.1.100),二进制格式只占 4 字节。10MB 内存约可存 16 万个 IP 的状态。
这是限速配置最容易搞混的部分:
| 参数 | 作用 | 不加的后果 |
|---|---|---|
| burst=20 | 允许突发 20 个请求排队等待 | 超过 rate 的请求直接 503 |
| nodelay | 排队的请求不延迟,立即处理 | 排队请求会延迟处理,用户体验差 |
各种组合的效果:
| 配置 | 行为 |
|---|---|
| rate=10r/s (无 burst) | 严格每秒 10 个,第 11 个立即 503 |
| rate=10r/s burst=20 | 突发 30 个(10+20)能进入队列,但队列中的请求要等,超出 503 |
| rate=10r/s burst=20 nodelay | 突发 30 个立即处理不延迟,超出 503( 推荐 ) |
| rate=10r/s burst=20 delay=10 | 前 10 个立即处理,后 10 个延迟处理,超出 503 |
⚠️ 注意: burst 设太大会让限速形同虚设。一般 API 接口 burst 设 5-20,普通页面设 20-50。登录等敏感接口 burst 设 3-5。简单记忆:burst 是‘突发窗口’,nodelay 是‘不要排队’。两者结合就是‘允许你偶尔超一点,但不让你的请求等待。
默认被限速时返回 503 Service Unavailable,可以改成 429 Too Many Requests(更符合 HTTP 语义):
不同接口限速不同:
| 接口类型 | rate | burst | 说明 |
|---|---|---|---|
| 普通页面 | 10r/s | 20 | 正常浏览 |
| API 接口 | 5r/s | 10 | 前端轮询 |
| 登录接口 | 1r/s | 3 | 防暴力破解 |
| 下载接口 | 2r/s | 5 | 防带宽滥用 |
| 搜索接口 | 3r/s | 5 | 防恶意搜索 |
| 模块 | 限制维度 | 适用场景 |
|---|---|---|
| limit_req | 请求频率(每秒几次) | CC 攻击、API 滥用 |
| limit_conn | 并发连接数(同时几个) | 慢速攻击、连接耗尽 |
limit_req 限制"每秒发多少个请求",limit_conn 限制"同时保持多少个连接"。两者互补,都要开。
慢速攻击(Slowloris)的原理是:攻击者建立大量连接,但每个请求都发得极慢(比如每分钟发一个字节),占满服务器的连接数,正常用户连不上。
| 攻击类型 | 原理 | Nginx 防护 |
|---|---|---|
| Slowloris | 大量半连接,请求头发得极慢 | limit_conn + 超时配置 |
| Slow POST | 发大 body 但传得极慢 | client_body_timeout |
| Slow Read | 读取响应极慢 | client_header_timeout |
| 超时参数 | 默认值 | 推荐值 | 说明 |
|---|---|---|---|
| client_header_timeout | 60s | 30s | 请求头发送超时 |
| client_body_timeout | 60s | 30s | 请求体发送超时 |
| send_timeout | 60s | 30s | 响应发送超时 |
| keepalive_timeout | 75s | 30-65s | 空闲连接超时 |
| client_max_body_size | 1m | 按需设置 | 请求体最大大小 |
⚠️ 注意: allow 和 deny 按从上到下顺序匹配,匹配到就停止。所以 deny all 一定要放在最后,allow all 一定要放在最后(取决于策略)。
| 场景 | 策略 |
|---|---|
| 后台管理页面 | allow 内网 + VPN,deny all |
| 内部 API | allow 内网,deny all |
| 封禁已知恶意 IP | deny 恶意IP,allow all |
| 只允许特定国家访问 | allow 国家IP段,deny all(需要 GeoIP) |
allow/deny 适合封少数 IP。如果要根据 IP 段做不同的策略(比如国内放行、国外限制),手动写 allow/deny 列表会非常长。geo 模块可以给 IP 段打标签,然后在配置里根据标签做不同的处理。
💡 提示: geo 模块的查找是 O(1) 的(基于 radix tree),性能远好于正则匹配。大量 IP 段的封禁建议用 geo,不要用 if + 正则。
| 方法 | 用途 | 是否需要 |
|---|---|---|
| GET | 获取资源 | 普通网站需要 |
| POST | 提交数据 | 表单/API 需要 |
| HEAD | 只取响应头 | 健康检查需要 |
| PUT | 上传/更新资源 | RESTful API |
| DELETE | 删除资源 | RESTful API |
| TRACE | 调试用 | 必须禁止 (XST 攻击) |
| CONNECT | 代理隧道 | 必须禁止 |
| OPTIONS | 预检请求 | CORS 需要 |
| PATCH | 部分更新 | RESTful API |
⚠️ 注意: TRACE 和 CONNECT 方法在生产环境必须禁止。TRACE 会导致跨站追踪(XST)攻击,CONNECT 可能被用来做代理跳板。
| 路径 | 风险 | 处理方式 |
|---|---|---|
| /.git/ | 源码泄露 | deny all |
| /.env | 数据库密码、API Key 泄露 | deny all |
| /.htaccess | Apache 配置泄露 | deny all |
| /.htpasswd | 密码文件泄露 | deny all |
| /wp-admin/ | WordPress 后台 | IP 白名单 |
| /phpmyadmin/ | 数据库管理工具 | IP 白名单 |
| /*.sql | 数据库备份泄露 | deny all |
| /*.bak | 备份文件泄露 | deny all |
| /*.log | 日志文件泄露 | deny all |
🚨 警告: .git 目录泄露是常见的安全事故之一。攻击者通过 /.git/config 可以还原整个源代码仓库,包括数据库密码、API 密钥等。务必确保 /.git 被封禁。
server {
# 关闭目录列表(如果 index 文件不存在,不显示目录内容)
autoindex off;
}默认就是 off,但确认一下没被意外打开。
别人网站直接引用服务器上的图片 URL,用户访问他的网站,图片是从服务器加载的——带宽是你出的,流量是你付的。
| valid_referers 参数 | 含义 |
|---|---|
| none | 允许无 Referer(直接访问) |
| blocked | 允许 Referer 被代理删除的请求 |
| server_names | 允许 Referer 中的域名匹配 server_name |
| *.example.com | 允许特定域名的子域 |
| example.com | 允许特定域名 |
💡 提示: 如果图片需要被第三方 CDN 加载(比如又拍云、七牛云),把 CDN 域名加到 valid_referers 里。valid_referers 是基础防护,不能完全防止盗链,对高价值资源考虑用签名 URL。
| 类型 | 原理 | Nginx 能防吗 |
|---|---|---|
| CC 攻击 | 大量 HTTP 请求打满后端 | ✅ limit_req |
| 连接耗尽 | 大量连接占满 worker | ✅ limit_conn |
| 慢速攻击 | 慢速请求占满连接 | ✅ 超时配置 |
| SYN Flood | TCP 半连接洪泛 | ❌ 内核层防护(sysctl) |
| UDP/ICMP 洪泛 | 带宽耗尽 | ❌ 需要 ISP/高防IP |
| 放大攻击 | 利用 DNS/NTP 放大流量 | ❌ 需要 ISP/高防IP |
⚠️ 注意: Nginx 是应用层(L7)服务器,只能防应用层 DDoS(CC/连接耗尽/慢速攻击)。网络层 DDoS(SYN Flood/UDP 洪泛)需要内核参数调优 + 云服务商高防 IP。Nginx 做不到的事情不要勉强。
第一层:限速 + 限连接
第二层:限制请求头大小和数量
第三层:内核参数调优
# 应用配置
sudo sysctl -p /etc/sysctl.d/99-ddos.confNginx 限速能挡住超速请求,但恶意 IP 还是在不断尝试。fail2ban 可以监控 Nginx 日志,自动把超过阈值的 IP 用 iptables 封掉。如果不想手动维护 IP 黑名单,用 fail2ban 自动封禁是最省心的方案。
创建对应的 filter 文件,或使用 fail2ban 自带的 nginx-limit-req 过滤规则(如果存在)。
| 参数 | 含义 | 推荐值 |
|---|---|---|
| findtime | 统计时间窗口(秒) | 600(10 分钟) |
| maxretry | 窗口内最大触发次数 | 10 |
| bantime | 封禁时长(秒) | 7200(2 小时) |
💡 提示: fail2ban 和 Nginx 限速是互补关系:Nginx 限速挡住超速请求(返回 429),fail2ban 监控到某个 IP 频繁触发限速,直接用 iptables 封掉它的所有连接。两层防护,效果倍增。
| 协议 | 是否安全 | 处理 |
|---|---|---|
| SSLv2 | ❌ 不安全 | 禁用 |
| SSLv3 | ❌ 不安全 | 禁用(POODLE) |
| TLSv1.0 | ❌ 不安全 | 禁用 |
| TLSv1.1 | ❌ 不安全 | 禁用 |
| TLSv1.2 | ✅ 安全 | 保留 |
| TLSv1.3 | ✅ 安全 | 优先使用 |
server {
# HSTS:告诉浏览器一年内只走 HTTPS
add_header
⚠️ 注意: preload 参数意味着域名会被加入浏览器的 HSTS 预加载列表。一旦加入,所有用户访问域名都强制 HTTPS,无法回退到 HTTP。只有在确认所有子域名都支持 HTTPS 后才加 preload。
把以上所有安全措施整合到一份配置里:
上线前对照这张清单逐项检查:
| 检查项 | 命令 | 期望结果 |
|---|---|---|
| 版本号已隐藏 | curl -I https://example.com | Server: nginx (无版本号) |
| 安全头已添加 | curl -I https://example.com | 包含 X-Frame-Options 等 |
| 限速生效 | ab -n 100 -c 20 https://example.com/ | 超出部分返回 429 |
| .git 已封禁 | curl -I https://example.com/.git/config | 403 或 404 |
| .env 已封禁 | curl -I https://example.com/.env | 403 或 404 |
| TRACE 方法已禁止 | curl -X TRACE https://example.com | 405 |
| HSTS 已启用 | curl -I https://example.com | 包含 Strict-Transport-Security |
| HTTPS 强制跳转 | curl -I http://example.com | 301 到 https |
| 后台 IP 白名单 | 非白名单 IP 访问 /admin | 403 |
| fail2ban 运行 | sudo fail2ban-client status | 活跃 jail 列表 |
部署完成后用这些命令跑一遍,确认防护措施都生效了。
| 可能原因 | 检查方法 |
|---|---|
| limit_req_zone 写在 server 块里 | 必须写在 http 块 |
| limit_req 写在了 http 块 | 必须写在 server 或 location 块 |
| 没有 reload | sudo nginx -s reload |
| zone 名不匹配 | 检查 limit_req 的 zone 名和 zone 定义一致 |
| burst 太大 | burst 太大等于没限速 |
| 走了 CDN | CDN 回源时 IP 是 CDN 的,需要用 $http_x_forwarded_for |
如果网站套了 CDN(如阿里云 CDN、腾讯云 CDN),Nginx 看到的 $remote_addr 是 CDN 节点 IP,不是用户真实 IP,限速会变成"对整个 CDN 节点限速"。
⚠️ 注意: set_real_ip_from 必须只包含信任的 CDN/代理 IP。如果设成 0.0.0.0/0,任何人都可以伪造 X-Forwarded-For 头绕过限速。
| 可能原因 | 检查方法 |
|---|---|
| 规则顺序错误 | allow/deny 从上到下匹配,先匹配先生效 |
| 走了反向代理 | $remote_addr 是代理 IP,需配 real_ip |
| location 优先级冲突 | 正则 location 优先级高于前缀 location |
| 可能原因 | 检查方法 |
|---|---|
| 日志路径不对 | 确认 logpath 和 Nginx 实际日志路径一致 |
| filter 正则不匹配 | sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-limit-req.conf |
| findtime 太短 | 调大 findtime |
| maxretry 太大 | 调小 maxretry |
| iptables 规则被覆盖 | sudo iptables -L -n 检查 fail2ban 链 |
| 指令 | 作用 | 推荐值 |
|---|---|---|
| limit_req_zone ... rate= | 定义限速区域和频率 | 10r/s |
| limit_req zone= ... burst= | 启用限速 | burst=20 |
| nodelay | 突发请求不延迟 | 加 |
| limit_req_status | 限速响应码 | 429 |
| 方法 | 适用场景 | 示例 |
|---|---|---|
| allow/deny | 少量 IP 白名单/黑名单 | deny 1.2.3.4; |
| geo 模块 | 大量 IP 段分类 | geo $blocked { ... } |
| fail2ban | 自动封禁高频攻击 IP | 监控日志 + iptables 封禁 |
| iptables | 系统级 IP 封禁 | iptables -A INPUT -s 1.2.3.4 -j DROP |
| 安全头 | 推荐值 |
|---|---|
| X-Content-Type-Options | nosniff |
| X-Frame-Options | SAMEORIGIN |
| Strict-Transport-Security | max-age=31536000; includeSubDomains |
| Referrer-Policy | strict-origin-when-cross-origin |
| Content-Security-Policy | 按需配置 |
| 参数 | 推荐值 | 作用 |
|---|---|---|
| client_header_timeout | 30s | 请求头超时 |
| client_body_timeout | 30s | 请求体超时 |
| send_timeout | 30s | 响应发送超时 |
| keepalive_timeout | 30s | 空闲连接超时 |
| client_max_body_size | 2m | 请求体最大大小 |
| 防护层 | 工具/配置 | 防护内容 |
|---|---|---|
| 应用层 L7 | Nginx limit_req | CC 攻击、API 滥用 |
| 应用层 L7 | Nginx limit_conn | 连接耗尽、慢速攻击 |
| 应用层 L7 | fail2ban | 自动封禁恶意 IP |
| 传输层 L4 | iptables | 端口扫描、IP 封禁 |
| 内核层 | sysctl | SYN Flood、ICMP |
| 网络层 | 云服务商高防 IP / CDN | 大流量 DDoS |
Nginx 安全加固的核心逻辑:
💡 提示: 安全加固不是一次性的工作。新的攻击手法不断出现,定期检查 Nginx 版本更新、审查日志中的异常请求、更新 fail2ban 规则,是保持安全的必要工作。
一个原则:多层防护,纵深防御。不要只靠一层——限速 + 限连接 + IP 封禁 + fail2ban + 内核参数 + 高防 IP,每一层都挡一部分攻击,叠加起来才能扛住真实流量。
# 需要安装 headers-more 模块
sudo apt install nginx-extras
# 包含了 headers-more 模块,或从源码编译时添加 --add-module 参数。
# 在 nginx.conf 的 http 块里
http {
more_clear_headers "Server";
more_clear_headers "X-Powered-By";
more_clear_headers "X-Runtime";
}# /etc/nginx/conf.d/example.conf 的 server 块里
server {
# 防止 MIME 类型嗅探
add_header X-Content-Type-Options "nosniff" always;
# 防止点击劫持(页面不允许被 iframe 嵌套)
add_header X-Frame-Options "SAMEORIGIN" always;
# 浏览器 XSS 过滤(虽然现代浏览器已内置,加上更保险)
add_header X-XSS-Protection "1; mode=block" always;
# 内容安全策略(CSP)——限制资源加载来源
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
# HSTS——强制浏览器以后都用 HTTPS 访问
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Referrer 策略——控制 Referer 头泄露
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 权限策略——控制浏览器功能(摄像头/麦克风/地理位置等)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}# 不要把后端错误暴露给用户
location /api/ {
proxy_pass http://127.0.0.1:3000;
# 后端返回 500 时,显示自定义错误页而不是堆栈信息
proxy_intercept_errors on;
error_page 500 502 503 504 /50x.html;
}
# 自定义错误页面
location = /50x.html {
root /var/www/errors;
internal;
}# /etc/nginx/nginx.conf 的 http 块里
http {
# 定义限速区域
# $binary_remote_addr 以二进制格式存储客户端 IP(比文本省内存)
# zone=req_limit:10m 共享内存区域名和大小,10MB 约可存储十万级 IP 的状态(取决于实际请求模式)
# rate=10r/s 每个 IP 每秒最多 10 个请求
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
}# /etc/nginx/conf.d/example.conf
server {
# 全局限速(所有 location 共享)
limit_req zone=req_limit burst=20 nodelay;
# 只对 API 限速
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}
# 登录接口更严格
location /api/login {
limit_req zone=req_limit burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}# 图解:rate=10r/s burst=20 nodelay 的效果
#
# 时间轴(秒)
# 0s: 30个请求同时来 → 前30个通过(10常规+20突发),第31个503
# 1s: 又来5个 → 全部通过(桶里还有10个额度)
# 2s: 又来15个 → 全部通过(用完突发桶)
# 3s: 又来15个 → 前10个通过,后5个503(突发桶空了)http {
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
limit_req_status 429;
}
server {
limit_req zone=req_limit burst=20 nodelay;
# 自定义 429 响应
error_page 429 = @rate_limited;
location @rate_limited {
default_type application/json;
return 429 '{"error": "Too Many Requests", "retry_after": 60}';
}
}http {
# 全局限速:每秒 10 个请求
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
# API 限速:每秒 5 个请求
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;
# 登录限速:每秒 1 个请求
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# 下载限速:每秒 2 个请求
limit_req_zone $binary_remote_addr zone=download:10m rate=2r/s;
}
server {
# 全局限速
limit_req zone=general burst=20 nodelay;
location /api/ {
limit_req zone=api burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location /api/login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
location /download/ {
limit_req zone=download burst=5 nodelay;
proxy_pass http://127.0.0.1:3000;
}
}# http 块
http {
# 定义连接数限制区域
# 每个IP最多同时保持 10 个连接
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
# server 块
server {
# 每个IP最多 10 个并发连接
limit_conn conn_limit 10;
# 整个虚拟主机最多 1000 个并发连接(防止单站占满所有连接)
limit_conn_zone $server_name zone=perserver:10m;
limit_conn perserver 1000;
limit_conn_status 429;
}# http 块——超时配置
http {
# 客户端请求头超时(60秒没发完请求头就断开)
client_header_timeout 60s;
# 客户端请求体超时(60秒没发完 body 就断开)
client_body_timeout 60s;
# 客户端响应超时(发送响应后60秒客户端没读完就断开)
send_timeout 60s;
# keepalive 超时(空闲连接65秒后断开)
keepalive_timeout 65s;
# 限制请求体大小(防止超大 POST 攻击)
client_max_body_size 10m;
# 限制请求头大小
large_client_header_buffers 4 8k;
}# 允许特定 IP,拒绝其他所有
location /admin {
allow 192.168.1.0/24; # 允许内网
allow 10.0.0.0/8; # 允许 VPN
allow 123.45.67.89; # 允许特定公网 IP
deny all; # 拒绝其他所有
proxy_pass http://127.0.0.1:3000;
}
# 拒绝特定 IP,允许其他所有
location / {
deny 1.2.3.4; # 拒绝单个 IP
deny 5.6.0.0/16; # 拒绝一个网段
allow all; # 允许其他所有
proxy_pass http://127.0.0.1:3000;
}# /etc/nginx/conf.d/blocklist.conf
# 封禁列表(可以单独维护一个文件,方便更新)
deny 1.2.3.4; # 扫描器
deny 5.6.7.8; # 暴力破解
deny 10.20.30.0/24; # 某机房段
# 在 server 块里引用
server {
include /etc/nginx/conf.d/blocklist.conf;
# ... 其他配置
}# http 块
http {
# 定义 IP 分类
geo $allowed_ip {
default 0; # 默认不允许
192.168.0.0/16 1; # 内网允许
10.0.0.0/8 1; # VPN允许
123.45.67.89 1; # 特定IP允许
}
}
# server 块
server {
location /admin {
if ($allowed_ip = 0) {
return 403;
}
proxy_pass http://127.0.0.1:3000;
}
}http {
geo $blocked_ip {
default 0; # 默认不封禁
# 已知攻击源
1.2.3.0/24 1;
5.6.0.0/16 1;
10.20.30.0/24 1;
# 某云扫描器段
100.200.0.0/16 1;
}
}
server {
if ($blocked_ip = 1) {
return 403;
}
}http {
# 定义封禁列表
geo $is_blocked {
default 0;
1.2.3.0/24 1;
5.6.0.0/16 1;
}
# 用 map 定义不同 location 的封禁行为
map $is_blocked$uri $block_action {
default 0; # 不封禁
1/api/login 1; # 封禁访问登录接口
1/admin 1; # 封禁访问后台
}
}
server {
location / {
if ($block_action = 1) {
return 403;
}
proxy_pass http://127.0.0.1:3000;
}
}server {
# 只允许 GET、POST、HEAD
if ($request_method !~ ^(GET|POST|HEAD)$ ) {
return 405;
}
# API 允许 GET、POST、PUT、DELETE、HEAD
location /api/ {
if ($request_method !~ ^(GET|POST|PUT|DELETE|HEAD)$ ) {
return 405;
}
proxy_pass http://127.0.0.1:3000;
}
}server {
# 禁止访问以 . 开头的隐藏文件(.git、.env、.htaccess 等)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问备份文件
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问 .git 目录(源码泄露)
location ~ /\.git {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问 .env 文件(数据库密码等)
location ~ /\.env {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问配置文件
location ~ \.(sql|bak|backup|old|swp|tmp|log)$ {
deny all;
access_log off;
log_not_found off;
}
}server {
# 图片防盗链
location ~ \.(jpg|jpeg|png|gif|webp|svg|mp4|mp3|woff|woff2)$ {
# valid_referers:合法的来源
# none:直接访问(浏览器地址栏输入URL)
# blocked:Referer 被代理删除了
# server_names:本站域名
valid_referers none blocked server_names
*.example.com example.com
*.google.com; # 允许搜索引擎
if ($invalid_referer) {
# 返回一张替代图片(或直接 403)
return 403;
# 或者返回一张"禁止盗链"的图片
# rewrite ^/ /hotlink-denied.png last;
}
root /var/www/example;
}
}http {
# 限速
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# 限连接
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 限制每 IP 请求体大小
client_body_buffer_size 8k;
client_max_body_size 2m;
# 超时
client_header_timeout 30s;
client_body_timeout 30s;
send_timeout 30s;
}
server {
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 10;
limit_req_status 429;
limit_conn_status 429;
}http {
# 限制请求头缓冲区(防止超大请求头攻击)
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# 限制请求体缓冲区
client_body_buffer_size 8k;
}# /etc/sysctl.d/99-ddos.conf
# SYN Flood 防护
net.ipv4.tcp_syncookies = 1 # 开启 SYN Cookie
net.ipv4.tcp_max_syn_backlog = 65535 # SYN 队列长度
net.ipv4.tcp_synack_retries = 2 # SYN-ACK 重试次数(默认5,减少可防 SYN Flood)
# 连接复用
net.ipv4.tcp_tw_reuse = 1 # 复用 TIME_WAIT
net.ipv4.tcp_fin_timeout = 15 # FIN-WAIT-2 超时
# 连接追踪
net.netfilter.nf_conntrack_max = 1000000 # 连接追踪表大小
net.netfilter.nf_conntrack_tcp_timeout_established = 1800 # 已建立连接超时
# ICMP 防护
net.ipv4.icmp_echo_ignore_broadcasts = 1 # 忽略广播 ICMP
net.ipv4.icmp_ignore_bogus_error_responses = 1
# 反向路径过滤
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1# 安装 fail2ban
sudo apt install fail2ban
# 创建 Nginx 限速 jail 配置
sudo tee /etc/fail2ban/jail.d/nginx-limit.conf << 'EOF'
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/error.log
findtime = 600
bantime = 7200
maxretry = 10
[nginx-botsearch]
enabled = true
filter = nginx-botsearch
action = iptables-multiport[name=BotSearch, port="http,https", protocol=tcp]
logpath = /var/log/nginx/access.log
findtime = 600
bantime = 7200
maxretry = 2
EOF
# 启动 fail2ban
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban# 查看 fail2ban 状态
sudo fail2ban-client status nginx-limit-req
# 输出示例:
# Status for the jail: nginx-limit-req
# |- Filter
# | |- Currently failed: 3
# | |- Total failed: 156
# | `- File list: /var/log/nginx/error.log
# `- Actions
# |- Currently banned: 5
# |- Total banned: 23
# `- Banned IP list: 1.2.3.4 5.6.7.8 9.10.11.12 13.14.15.16 17.18.19.20
# 手动解封某个 IP
sudo fail2ban-client set nginx-limit-req unbanip 1.2.3.4server {
listen 443 ssl http2;
# 只允许 TLS 1.2 和 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# 安全的加密套件
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# 服务器优先选择加密套件
ssl_prefer_server_ciphers on;
# SSL 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling(让客户端不需要单独查询证书状态)
ssl_stapling on;
ssl_stapling_verify on;
resolver 223.5.5.5 223.6.6.6 valid=300s;
resolver_timeout 5s;
}# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
# ===== 基础安全 =====
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 30s;
# ===== 限制请求大小 =====
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
client_body_buffer_size 8k;
client_max_body_size 2m;
# ===== 超时配置 =====
client_header_timeout 30s;
client_body_timeout 30s;
send_timeout 30s;
# ===== 限速 =====
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
limit_req_status 429;
# ===== 限连接 =====
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_conn_status 429;
# ===== IP 封禁 =====
geo $blocked_ip {
default 0;
# 在这里添加要封禁的 IP 段
# 1.2.3.0/24 1;
# 5.6.0.0/16 1;
}
# ===== Gzip =====
gzip on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml;
gzip_vary on;
# ===== SSL 全局 =====
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
}# /etc/nginx/conf.d/example.conf
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# ===== 安全响应头 =====
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
# ===== 限速 + 限连接 =====
limit_req zone=req_limit burst=20 nodelay;
limit_conn conn_limit 10;
# ===== IP 封禁 =====
if ($blocked_ip = 1) {
return 403;
}
# ===== 只允许必要 HTTP 方法 =====
if ($request_method !~ ^(GET|POST|HEAD|OPTIONS)$ ) {
return 405;
}
# ===== 隐藏敏感文件 =====
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
location ~ \.(sql|bak|backup|old|swp|tmp|log|env)$ {
deny all;
access_log off;
log_not_found off;
}
# ===== 后台管理——IP 白名单 =====
location /admin {
allow 192.168.0.0/16;
allow 10.0.0.0/8;
allow 123.45.67.89;
deny all;
proxy_pass http://127.0.0.1:3000;
}
# ===== 登录接口——更严格限速 =====
location /api/login {
limit_req zone=req_limit burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
# ===== API =====
location /api/ {
limit_req zone=req_limit burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
}
# ===== 图片防盗链 =====
location ~ \.(jpg|jpeg|png|gif|webp|svg|mp4)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
root /var/www/example;
expires 30d;
}
# ===== 静态文件 =====
location / {
root /var/www/example;
index index.html;
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example.access.log;
error_log /var/log/nginx/example.error.log warn;
}
# HTTP → HTTPS 重定向
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}# 方案:用 real_ip 模块获取真实 IP
http {
# 信任的 CDN IP 段(以阿里云为例)
set_real_ip_from 47.0.0.0/8;
set_real_ip_from 39.0.0.0/8;
set_real_ip_from 120.0.0.0/8;
# 从哪个头取真实 IP
real_ip_header X-Forwarded-For;
# 递归去除信任的代理 IP
real_ip_recursive on;
}