Nginx主动防御配置实战:构建Cloudflare后的第二道安全防线

📅 2026/7/19 5:31:34
Nginx主动防御配置实战:构建Cloudflare后的第二道安全防线
1. 项目概述为什么你的网站需要“双保险”如果你正在使用 Cloudflare 作为 CDN 和防护盾并且后端服务器运行着 Nginx那么恭喜你你已经站在了一个非常稳固的起点上。但很多朋友可能就止步于此了在 Nginx 里配置一下set_real_ip_from和real_ip_header让日志能记录到访客的真实 IP就觉得万事大吉。这其实只完成了安全加固的第一步相当于只给大门装了把锁却没给窗户上栓。Cloudflare 提供了强大的边缘安全能力比如它的 WAFWeb 应用防火墙和速率限制。但安全永远是纵深防御。将一部分关键的安全策略下沉到你的源站 Nginx 上能带来几个核心好处第一减轻对单一服务商的绝对依赖即使 Cloudflare 的某个规则临时失效或配置有误你源站的防线依然在第二应对那些可能绕过 CDN、直接攻击你源站 IP 的“聪明”攻击者第三对于一些精细化的、业务特定的防护需求比如针对某个特定 API 接口的频次限制在 Nginx 层实现往往更灵活、响应更迅速。所以这个“双保险”策略的核心思想是利用 Cloudflare 做第一道、也是覆盖面最广的防线同时用 Nginx 构建第二道、更贴近业务的精细化防线。今天要聊的就是除了获取真实 IP 之外那些能实实在在帮你挡住爬虫、缓解甚至阻止 CCChallenge Collapsar即 HTTP 洪水攻击的 Nginx 配置。这些配置大多不复杂但组合起来效果显著。2. 核心思路拆解从被动记录到主动防御在深入配置之前我们先理清防御的逻辑。攻击和爬虫的行为通常有迹可循我们的配置就是针对这些“痕迹”设置关卡。2.1 识别攻击与爬虫的常见特征高频请求CC攻击的典型特征在极短时间内从一个或少量 IP 向同一个 URL特别是动态页面、登录接口、搜索接口发起大量请求意图耗尽服务器资源CPU、数据库连接、带宽。非常规 User-Agent一些低级爬虫或扫描器会使用明显异常、缺失或默认的 User-Agent 字符串如python-requests/2.28.2,Go-http-client/1.1, 或干脆为空。缺失关键请求头正常的浏览器请求会携带一系列标准的 HTTP 头如Accept、Accept-Language、Accept-Encoding等。而许多自动化工具发起的请求可能会缺失这些头或者其值非常规。针对特定漏洞的扫描路径攻击者会批量请求诸如/wp-admin/,/phpmyadmin/,/config.json,.env等常见的管理后台、配置文件路径试探是否存在未授权访问或已知漏洞。2.2 Nginx 防御策略分层基于以上特征我们可以在 Nginx 构建一个分层的防御体系连接层限制控制单个 IP 的连接数和请求速率这是应对 CC 洪水最直接的手段。请求特征过滤根据 User-Agent、请求头等特征直接拦截掉明显的恶意请求。路径与资源保护屏蔽对敏感路径的访问并对消耗资源的特定接口如登录、搜索实施更严格的频率限制。验证与质询对于可疑但不确认的请求可以返回一个简单的验证如 429 状态码告知稍后重试或者结合更复杂的验证码方案但这通常需要后端应用配合。接下来我们就进入实操环节看看如何用 Nginx 的配置实现这些策略。3. 基础环境与关键配置准备在开始布置“机关”之前确保你的 Nginx 已经正确配置了从 Cloudflare 接收真实 IP这是所有后续 IP 相关限制的基础。3.1 获取并配置 Cloudflare IP 列表Cloudflare 的 IP 段是公开的并且会更新。我们需要告诉 Nginx来自这些 IP 的请求头中的CF-Connecting-IP才是真实用户 IP。 通常你可以在 Nginx 配置的http块内引入这个 IP 列表。最可靠的方式是从 Cloudflare 官方获取。# 进入你的 Nginx 配置目录例如 /etc/nginx cd /etc/nginx # 下载 Cloudflare 的 IPv4 和 IPv6 地址列表 curl -s https://www.cloudflare.com/ips-v4 -o cf-ips-v4.txt curl -s https://www.cloudflare.com/ips-v6 -o cf-ips-v6.txt然后在你的nginx.conf或conf.d/下的某个配置文件如real-ip.conf中进行如下配置# 定义从 Cloudflare IP 来的请求使用 CF-Connecting-IP 头作为真实IP # 包含 IPv4 列表 include /etc/nginx/cf-ips-v4.txt; # 包含 IPv6 列表 include /etc/nginx/cf-ips-v6.txt; # 注意上面两行需要配合一个自定义的配置文件内容。 # 更常见的做法是将下载的 IP 列表内容通过 set_real_ip_from 指令逐个或通过文件包含声明。 # 这里提供一个更实用的方法使用 geo 模块配合 map 来灵活处理 geo $realip_remote_addr $cloudflare_ip { default 0; # 你可以手动添加几个关键段或者用脚本将下载的列表格式化成如下样式 # 103.21.244.0/22 1; # 103.22.200.0/22 1; # ... 所有 Cloudflare IP 段 # 也可以动态包含但 Nginx 原生不支持 include 变量通常需要借助 OpenResty 或定期更新配置文件。 } # 一个更直接、兼容性更好的方法是在 http 块中使用多个 set_real_ip_from # 例如在 http 块中 real_ip_header CF-Connecting-IP; # 然后为每个 Cloudflare IP 段添加 set_real_ip_from set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; # ... 添加所有 IP 段 # 为了维护方便建议使用自动化脚本定期更新这个配置文件。实操心得维护 Cloudflare IP 列表是个小麻烦。我个人的做法是写一个简单的 Shell 脚本用cron每周自动从 Cloudflare 官网拉取最新列表并格式化后重写到 Nginx 的配置片段如/etc/nginx/cloudflare-ips.conf然后在主配置中用include指令引入。这样就能确保 IP 列表始终最新。3.2 确认真实 IP 获取成功配置完成后重载 Nginx (nginx -s reload)然后检查你的访问日志例如/var/log/nginx/access.log。日志格式中$remote_addr现在应该显示的是用户的真实 IP而不是 Cloudflare 的边缘节点 IP。这是后续所有基于 IP 的限流和过滤生效的前提。4. 主动防御配置实战现在我们开始构筑核心防线。以下配置通常放在http块或server块中根据你的需要选择作用域。4.1 连接层与请求速率限制应对CC攻击核心这是最有效的一招利用 Nginx 的limit_conn_zone和limit_req_zone模块。限制同一 IP 并发连接数防止单个 IP 建立大量连接耗尽服务器资源。http { # 定义限制连接数的共享内存区键为真实IP大小10MB该区域可以记录约16万个IP状态 limit_conn_zone $binary_remote_addr zoneperip_conn:10m; server { location / { # 每个 IP 在同一时间最多允许 10 个连接 limit_conn perip_conn 10; # 当超过限制时返回 503 (Service Temporarily Unavailable) 错误 limit_conn_status 503; ... # 其他配置 } } }$binary_remote_addr是经过前面real_ip模块处理后的真实客户端 IP 的二进制格式占用空间更小。zoneperip_conn:10m定义了一个名为perip_conn的共享内存区大小为 10MB。根据经验1MB 大约可以存储 1.6 万个 IP 的状态信息10MB 对于大多数站点足够了。limit_conn perip_conn 10;在location中应用限制。限制同一 IP 请求速率这是防 CC 的关键限制每秒/每分钟能处理的请求数。http { # 定义限制请求速率的共享内存区键为真实IP大小10MB速率限制为每秒10个请求r/s limit_req_zone $binary_remote_addr zoneperip_req:10m rate10r/s; server { location / { # 应用限流使用“漏桶”算法突发请求队列大小为5个 limit_req zoneperip_req burst5 nodelay; limit_req_status 429; # 超过限制时返回 429 (Too Many Requests) ... # 其他配置 } # 对于特别敏感的接口可以设置更严格的限制 location /api/login { limit_req_zone $binary_remote_addr zonelogin_req:10m rate2r/m; limit_req zonelogin_req burst1 nodelay; limit_req_status 429; } } }rate10r/s表示平均每秒允许 10 个请求。你可以根据业务承受能力调整对于纯静态资源可以放宽对于动态接口要收紧。burst5允许处理突发流量。假设速率是 10r/s如果一瞬间来了 15 个请求前 10 个会被立即处理接下来的 5 个会进入队列延迟处理如果设置了nodelay则会立即处理但占用 burst 额度超过 burst 则拒绝。burst设得太高会削弱限流效果太低可能误伤正常用户的短暂高峰。nodelay参数意味着对于突发队列中的请求不延迟处理而是立即处理但会快速消耗掉 burst 额度。这对于用户体验更友好但防护力度稍弱。是否需要nodelay需要根据业务权衡。特别注意limit_req_zone通常定义在http块而limit_req应用在server或location块。可以为不同的路径设置不同的zone和rate。注意事项速率限制的数值需要根据实际业务流量进行压测和调整。设置过严会误伤正常用户尤其在秒杀、抢购场景设置过松则起不到防护作用。建议先在日志中观察正常用户的请求频率分布。4.2 基于请求特征的过滤拦截低阶爬虫与扫描器屏蔽异常或空 User-Agentserver { # 如果 User-Agent 为空、为常见爬虫工具标识则直接返回 444Nginx 直接关闭连接 if ($http_user_agent ~* “^$|python|java|curl|wget|go-http|^java|httpclient|apachebench|^$”) { return 444; } # 更精确的做法是只允许常见的浏览器和良性爬虫如各大搜索引擎 # if ($http_user_agent !~* “(chrome|firefox|safari|msie|edge|bingbot|googlebot|baiduspider|yandex|sogou)” ) { # return 444; # } # 注意白名单策略过于激进可能会挡住一些合法的非浏览器客户端如 API 调用请谨慎使用。 }使用~*进行不区分大小写的正则匹配。return 444;是 Nginx 的一个特殊状态码表示无条件关闭连接不发送任何响应头对于攻击者来说最“省资源”。屏蔽缺失关键请求头的请求server { # 检查 Accept 头正常的浏览器请求都会包含它 if ($http_accept “”) { return 444; } # 可以同时检查多个头但要注意某些场景下如图片 src 请求可能缺失某些头 # if ($http_accept_language “” ) { # return 444; # } }实操心得if指令在 Nginx 的location上下文中有一些性能陷阱和副作用例如可能导致try_files等指令失效但它用于简单的返回操作如return 444,return 403通常是安全且高效的。对于复杂的逻辑判断建议使用map指令。4.3 敏感路径与资源保护屏蔽对常见敏感文件和目录的访问server { location ~* ^/(\.git|\.env|\.svn|\.htaccess|wp-admin|phpmyadmin|admin|backup|config|sql|\.bak)$ { deny all; return 404; # 或者 403 } # 屏蔽对特定后缀文件的直接访问 location ~* \.(log|ini|conf|sql|tar|gz)$ { deny all; return 404; } }使用正则表达式~*匹配这些路径直接deny all。对高消耗接口实施精准限流 如前文所示可以为/api/login,/api/search,/submit_comment等动态接口单独设置更严格的limit_req规则。这比全局限流更有效不影响静态资源的正常访问。4.4 利用map指令创建黑白名单map指令比if更高效适合创建映射关系例如根据 IP 或 User-Agent 设置一个变量。http { # 创建一个 IP 黑名单映射 map $binary_remote_addr $is_blacklisted_ip { default 0; # 将已知恶意 IP 设为 1 123.123.123.123 1; 111.111.111.0/24 1; # 支持 CIDR 格式的网段 # 可以从文件加载但需要 reload 配置 # include /etc/nginx/blocked_ips.conf; } # 创建一个恶意 User-Agent 映射 map $http_user_agent $is_bad_ua { default 0; ~*”scanner|hack|exploit” 1; “~*” 是一个正则匹配操作符 } server { # 在 server 或 location 中判断 if ($is_blacklisted_ip) { return 444; } if ($is_bad_ua) { return 444; } } }维护一个动态的 IP 黑名单是高级玩法。你可以编写一个脚本分析 Nginx 日志将短时间内触发大量 429/503 错误的 IP或者匹配了恶意请求特征的 IP自动追加到黑名单配置文件中然后让 Nginx 重载配置。这实现了简单的自动化封禁。5. 高级策略与日志分析5.1 设置日志格式记录限流信息为了监控限流是否生效以及分析攻击情况我们需要在日志中记录相关变量。http { log_format main ‘$remote_addr - $remote_user [$time_local] “$request” ‘ ‘$status $body_bytes_sent “$http_referer” ‘ ‘“$http_user_agent” “$http_x_forwarded_for” ‘ ‘“$limit_req_status” “$limit_conn_status”‘; # $limit_req_status 记录请求限流状态PASSED, DELAYED, REJECTED, DELAYED_DRY_RUN 等 # $limit_conn_status 记录连接限流状态PASSED, REJECTED 等 access_log /var/log/nginx/access.log main; }这样在日志中就能清晰地看到哪些请求被限流了状态为 REJECTED便于后续分析和调整阈值。5.2 结合ngx_http_geo_module按国家/地区限制如果你的业务只针对特定地区可以屏蔽高风险地区的 IP 访问。 首先你需要一个 IP 地理信息数据库如 MaxMind 的 GeoLite2。安装相应模块和数据库后可以配置http { geoip2 /path/to/GeoLite2-Country.mmdb { $geoip2_country_code country iso_code; } map $geoip2_country_code $allowed_country { default yes; CN yes; # 允许中国 US yes; # 允许美国 RU no; # 禁止俄罗斯 # … 其他地区 } server { if ($allowed_country no) { return 444; } } }5.3 谨慎使用if与性能考量正如之前提到的Nginx 的if指令在其上下文中是“邪恶”的它可能会破坏其他指令的预期行为。最佳实践是对于简单的返回操作return 444,return 403if是安全的。对于设置变量、重写 URI 等复杂逻辑尽量使用map、server块匹配、location嵌套等方式替代。所有基于正则表达式的匹配~,~*都有性能开销规则越多越复杂性能影响越大。应将最可能匹配的、最严格的规则放在前面。6. 常见问题与排查技巧实录6.1 限流规则不生效检查真实 IP 获取确保$binary_remote_addr已经是用户真实 IP。查看日志确认。检查作用域limit_req_zone和limit_conn_zone必须定义在http块内。limit_req和limit_conn应用在server或location块。检查配置语法使用nginx -t测试配置文件语法。检查共享内存区大小如果 IP 数量非常多10m 可能不够观察 Nginx 错误日志是否有limiting connections相关的zone空间不足的警告。6.2 误伤了正常用户怎么办调整阈值这是最直接的方法。观察正常业务高峰期的请求频率将rate和burst值设置得比峰值稍高一些。使用白名单为已知的、可信的 IP 或 API 网关 IP 设置白名单绕过限流规则。可以通过map指令设置一个$is_trusted变量在location中判断if ($is_trusted) { limit_req off; }。分层限流不要全局一刀切。对静态资源如图片、CSS、JS放宽或取消限流只对动态 API 接口进行严格限制。6.3 如何应对分布式 CC 攻击单个 IP 限流对分布式攻击海量不同 IP效果有限。这时需要结合Cloudflare 的防护开启 Cloudflare 的 Under Attack 模式或调整其 WAF 规则利用其全球网络的优势进行清洗。Nginx 层面可以考虑更复杂的策略如限制每个 IP 对特定 URL 的访问速率上文已提或者使用limit_req_zone的键改为$server_name$request_uri来限制针对某个具体页面的总并发请求但这会影响所有用户。更高级的方案需要集成 Lua 模块OpenResty或结合外部防火墙如 Fail2ban进行动态封禁。6.4 配置完成后如何测试可以使用工具如ab(Apache Benchmark) 或wrk从本地机器进行简单的压力测试观察是否会触发 429 或 503 错误。# 示例在10秒内并发100对目标URL发起请求 ab -t 10 -c 100 http://your-website.com/api/test同时监控服务器的load average、nginx进程的 CPU 和内存使用情况以及访问日志中$limit_req_status的变化。6.5 动态黑名单的维护手动维护黑名单不现实。一个简单的自动化思路是定期如每分钟扫描 Nginx 日志找出在短时间内如10秒返回 429/503 状态码超过 N 次如50次的 IP。将这些 IP 写入一个文件如/etc/nginx/blockips.conf格式为deny IP;。在 Nginx 配置的http块中通过include /etc/nginx/blockips.conf;引入。执行nginx -s reload使配置生效注意频繁 reload 有性能损耗生产环境慎用。可以设置一个过期时间比如将封禁 IP 写入文件时记录时间戳另一个定时任务清理超过一定时间如24小时的封禁记录。这套组合拳打下来你的网站从 Nginx 层面就已经具备了相当可观的主动防御能力。它不能替代专业的 WAF 或云防护服务但作为一道成本极低、可控性极高的内部防线能在 Cloudflare 之外为你提供至关重要的冗余安全保障。安全配置永远是一个动态调整的过程持续观察日志、分析攻击模式、微调规则才是长治久安之道。