nginx配置拦截请求的核心思路是先明确拦截对象,再选择匹配工具,最后落地为可维护的配置规则。就是通过location、if、limit_req、allow/deny等指令组合,把恶意IP、异常UA、高频访问和非法参数挡在业务逻辑之外,同时保证正常用户不受影响。
识别需要拦截的请求特征
拦截请求的第一步不是写配置,而是搞清楚哪些请求该拦,nginx的access.log是判断依据,用tail -f实时观察日志,结合awk '{print $1}' | sort | uniq -c | sort -rn统计IP出现频率,能快速定位异常来源。
常见需要拦截的请求类型包括:
- 单一IP在短时间内发起大量请求,比如每秒几十次以上
- 请求路径包含
/wp-admin、/admin、.env、..%2f等敏感特征 - User-Agent为空或包含
curl、python-requests、扫描器等标记 - Referer为空且访问的是静态资源接口,或者来源域名明显是垃圾流量
- 请求参数中携带
union select、<script>等SQL注入或XSS特征
业内专家指出,多数攻击流量在到达业务代码之前就会暴露在HTTP头部和请求路径中,nginx层面做拦截刚好能兜住这一层。
从日志里找出攻击IP
先用grep配合正则把可疑请求筛出来,比如查看今天日志里所有包含/admin的请求:
grep "admin" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn
如果某个IP的请求次数明显异常,比如达到了数百次甚至上千次,就可以把它列入拦截名单,注意区分正常爬虫和恶意爬虫,百度和谷歌的爬虫UA有固定格式,且会遵循robots协议,通常不需要拦截。
nginx配置拦截请求IP的三种姿势
拦截IP是常见需求,但不同场景匹配不同做法。
直接deny单个IP或IP段
ngx_http_access_module模块是内置基础能力,几乎零开销,在server块内直接写:
server {
listen 80;
server_name example.com;
deny 192.168.1.10;
deny 203.0.113.0/24;
allow all;
}
allow all必须放在deny之后,否则顺序生效的匹配规则会把前面的deny覆盖掉,这个方案适合IP数量少、变化频率低的场景,比如屏蔽某个机房出口或内部测试IP。

用map变量集中管理封禁名单
IP数量一多,散落在各个server块里就难维护了,用map做一个集中名单:
http {
map $remote_addr $blocked_ip {
default 0;
192.168.1.10 1;
203.0.113.0/24 1;
198.51.100.7 1;
}
server {
if ($blocked_ip = 1) {
return 403;
}
}
}
这种方式把管理逻辑收敛到一处,新增IP只改map段,不用动业务配置,IP段支持CIDR格式,对于封禁某个云厂商的完整网段尤其方便。
针对UA或Referer的拦截
很多扫描器不改UA,直接用默认的爬虫标识,利用map同样能实现UA特征匹配:
map $http_user_agent $bad_ua {
default 0;
~curl 1;
~python-requests 1;
~scrapy 1;
~sqlmap 1;
}
server {
if ($bad_ua = 1) {
return 403;
}
}
表示不区分大小写的正则匹配,同样的思路可以扩展到Referer过滤,比如拦截某些垃圾外链带来的流量。
nginx限制请求频率防止恶意刷接口
IP封禁挡不住换IP的攻击,但限流可以控制整体速率。ngx_http_limit_req_module模块采用漏桶算法,对突发请求做平滑处理。
定义限流区域和速率
在http块里配置:
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5r/s;
}
这行配置的含义是:以客户端IP为key,开辟10MB共享内存区域,平均速率限制为每秒5个请求,10MB大约能存储16万个IP状态,常规业务足够用。
在location中启用限流
location /api/ {
limit_req zone=api_limit burst=10 nodelay;
proxy_pass http://backend;
}
burst是允许的突发队列长度,nodelay表示排队中的请求不额外延迟,直接快速处理,这样配合的效果是:平均每秒不超过5个请求,但允许单秒内最多有15个请求瞬间到达,超过的直接返回503。
如果需要区分接口权重,比如登录接口比查询接口限流更严格,可以定义多个limit_req_zone,在不同location引用不同的zone。
返回429而不是503
业界对限流响应的共识是返回429 Too Many Requests,比503更能准确传达语义,用limit_req_status

指令修改:
location /api/ {
limit_req zone=api_limit burst=5;
limit_req_status 429;
proxy_pass http://backend;
}
同时建议配合error_page返回一个简单的JSON提示:
error_page 429 /429.json;
location = /429.json {
default_type application/json;
return 200 '{"code":429,"msg":"请求过于频繁,请稍后再试"}';
}
拦截非法请求路径和参数
有些请求的特征藏在路径或参数里,用location + 正则就能精准匹配。
屏蔽敏感文件访问
location ~ \.(env|git|svn|bak|sql)$ {
deny all;
return 404;
}
不要直接返回403,返回404可以把文件是否存在的信息隐藏掉,对于.git目录被扫描的情况,这个返回404的策略很有效。
拦截带攻击特征的参数
if配合正则检查$query_string:
if ($query_string ~ "($|%24).\(|union.select|insert.into|delete.from") {
return 403;
}
这段正则覆盖了常见的SQL注入特征,需要注意,if在location内使用时行为有历史坑,建议保持条件简单,并且在nginx -t测试通过后再上线。
处理被拦截请求的后续操作
拦截不只是返回一个状态码,合理的后续处理能提升防护效果。
记录拦截日志
nginx默认把所有请求写入access.log,拦截请求混在里面不方便审计,更好的做法是单独分流:
server {
location /api/ {
limit_req zone=api_limit burst=10;
if ($blocked_ip = 1) {
access_log /var/log/nginx/blocked.log;
return 403;
}
access_log /var/log/nginx/access.log;
}
}
这样被拦截的请求会单独落到blocked.log,方便后续分析攻击来源和频次。
搭配fail2ban自动化封禁
nginx处理不了的动态攻击,比如打一枪换一个IP的行为,可以配合fail2ban,在nginx配置里修改错误日志格式:
log_format blocked '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
然后配置fail2ban去匹配

blocked.log中高频出现的IP,自动添加iptables规则封禁,这个组合能把大量人工操作自动化。
配置验证与常见误区
配置写完后,用nginx -t检查语法正确性,然后nginx -s reload平滑重载,注意,reload不会断开现有连接,是零中断生效的。
顺序匹配带来的坑
nginx的deny/allow是顺序匹配的,先匹配到的生效,配置里如果写了allow all再写deny,那deny永远失效,同理,location内部匹配规则也是先到先得。
不要用if做复杂逻辑
if在location内使用时是rewrite模块的一部分,写法不当会产生意想不到的问题,比如if内使用proxy_pass会报错,改写return才安全。行业共识是尽量少用if,能用map和location表达的场景就不要上if。
拦截后要留出正常流量通道
比如CDN回源IP和真实用户IP混在一起,直接按$remote_addr限流会误伤,这时候需要通过set_real_ip_from配置让nginx识别用户真实IP,再针对真实来源做限制。
nginx配置拦截请求常见问题
nginx拦截请求返回403后如何知道是哪个规则命中的?
在server块中配置日志格式增加$server_name和$request_uri字段,然后按状态码筛选日志,更直接的办法是用curl -I -H "User-Agent: xx"模拟请求,配合nginx -t和nginx -T查看当前生效的完整配置,逐步排查。
封禁IP和限流能否同时用?
能,在server块用deny做黑名单,在location用limit_req做频控,两者是独立的模块链路,互不干扰,先过黑白名单,再过限流,最后进入代理逻辑。
nginx限流会误伤同一出口IP下的正常用户吗?
在办公网或学校等NAT环境下,确实存在这个可能,缓解方式是把limit_req_zone的key从$binary_remote_addr改为$http_x_forwarded_for,并配合real_ip_header信任代理服务器传递的真实IP,同时结合Cookie或Token维度做补充限制。
nginx拦截请求的投入产出比很高,大部分恶意流量在HTTP层就能被过滤掉,配置本身不复杂,重在维护规则文件的整洁和明确识别对象,从日志中定期更新拦截特征,配合自动化封禁工具,即可形成一个持续收敛攻击面的闭环。
