NGINX最有用但经常被误解和配置错误的功能之一是速率限制。它允许您限制用户在给定时间内可以发出的HTTP请求数量。请求可以像GET
对网站首页的POST
请求或登录表单上的请求一样简单。
速率限制可用于安全目的,例如,减慢暴力密码猜测攻击的速度。通过将传入请求速率限制为实际用户的典型值,并(使用日志记录)标识目标URL,它可以帮助防御DDoS攻击。更一般而言,它用于防止上游应用程序服务器同时被太多用户请求所淹没。
在此博客中,我们将介绍NGINX的速率限制基础以及更高级的配置。速率限制在NGINX Plus中的工作方式相同。
要了解有关使用NGINX进行速率限制的更多信息,请观看我们的点播网络研讨会。
NGINX Plus R16和更高版本支持“全局速率限制”:群集中的NGINX Plus实例对传入的请求应用一致的速率限制,而不管请求到达群集中的哪个实例。(集群中的状态共享也可用于其他NGINX Plus功能。)有关详细信息,请参见我们的博客和NGINX Plus管理指南。
NGINX速率限制如何工作
NGINX速率限制使用泄漏桶算法,该算法在带宽受限的情况下广泛用于电信和分组交换计算机网络中的突发性处理。类比是一个桶,在桶中顶部注水,底部漏水。如果倒水的速度超过漏水的速度,则水桶会溢出。在请求处理方面,水代表来自客户端的请求,存储桶代表队列,根据先进先出(FIFO)调度算法,请求等待处理。漏水表示退出缓冲区以供服务器处理的请求,溢出表示已丢弃且从未得到服务的请求。
配置基本速率限制
速率限制使用两个主要指令limit_req_zone
和进行配置limit_req
,如本例所示:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /login/ {
limit_req zone=mylimit;
proxy_pass http://my_upstream;
}
}
该limit_req_zone
指令定义了速率限制的参数,同时limit_req
在它出现的上下文中启用速率限制(在此示例中,针对/ login /的所有请求)。
该limit_req_zone
指令通常在http
块中定义,使其可在多个上下文中使用。它采用以下三个参数:
- 键 –定义要应用限制的请求特征。在示例中,它是NGINX变量
$binary_remote_addr
,该变量持有客户端IP地址的二进制表示形式。这意味着我们将每个唯一的IP地址限制为第三个参数定义的请求速率。(我们正在使用此变量,因为它占用的空间少于客户端IP地址的字符串表示形式$remote_addr
)。 - 区域 –定义用于存储每个IP地址状态以及访问请求限制URL的频率的共享内存区域。将信息保存在共享内存中意味着可以在NGINX工作进程之间共享信息。定义分为两部分:由
zone=
关键字标识的区域名称,以及冒号后面的大小。大约16,000个IP地址的状态信息需要1兆字节,因此我们的区域可以存储大约160,000个地址。如果NGINX需要添加新条目时存储空间已用尽,它将删除最旧的条目。如果释放的空间仍然不足以容纳新记录,则NGINX返回状态码。此外,为防止内存耗尽,NGINX每次创建一个新条目时,都会删除最多60秒钟内未使用的两个条目。503
(Service
Temporarily
Unavailable)
- Rate –设置最大请求速率。在该示例中,速率不能超过每秒10个请求。NGINX实际上以毫秒为单位跟踪请求,因此此限制对应于每100毫秒1个请求。因为我们不允许脉冲串(请参阅下一节),所以这意味着如果请求在前一个允许的请求之后不到100毫秒到达,则该请求将被拒绝。
该limit_req_zone
指令设置速率限制和共享内存区域的参数,但实际上并没有限制请求速率。为此,您需要通过在其中包含指令来将限制应用于特定location
或server
块limit_req
。在示例中,我们将对/ login /的请求进行速率限制。
因此,现在每个唯一IP地址每秒只能对/ login /请求10个请求, 或者更确切地说,不能在前一个URL的100毫秒内对该URL发出请求。
处理突发
如果我们在100毫秒之内收到2个请求怎么办?对于第二个请求,NGINX将状态代码返回503
给客户端。这可能不是我们想要的,因为应用程序本质上往往是突发性的。相反,我们希望缓冲任何多余的请求并及时为它们提供服务。在此,我们在此burst
参数中使用参数limit_req
,如以下更新的配置所示:
location /login/ {
limit_req zone=mylimit
burst=20
;
proxy_pass http://my_upstream;
}
该burst
参数定义一个客户端可以发出的请求超出区域指定的速率(对于我们的示例mylimit区域,该速率限制为每秒10个请求,或每100毫秒1个)。一个请求比上一个请求晚100毫秒后到达,在这里,我们将队列大小设置为20。
这意味着,如果同时有21个请求从给定IP地址到达,NGINX将第一个请求立即转发到上游服务器组,并将其余20个请求放入队列。然后,它每100毫秒转发一个排队的请求,并且503
仅在传入请求使排队请求的数量超过20时才返回到客户端。
无延迟排队
具有的配置可burst
带来顺畅的流量,但不是很实用,因为它会使您的网站显示缓慢。在我们的示例中,队列中的第20个数据包等待2秒被转发,这时对它的响应可能不再对客户端有用。要解决这种情况,请在nodelay
参数中添加burst
参数:
location /login/ {
limit_req zone=mylimit
burst=20 nodelay
;
proxy_pass http://my_upstream;
}
使用该nodelay
参数,NGINX仍会根据该burst
参数在队列中分配插槽,并强加配置的速率限制,但不通过间隔排队请求的转发来实现。相反,当请求“过早”到达时,只要队列中有可用的插槽,NGINX就会立即转发该请求。它将该插槽标记为“已占用”,并且直到经过适当的时间(在我们的示例中为100毫秒)后,才释放该插槽供其他请求使用。
像以前一样,假定20插槽队列为空,并且从给定IP地址同时到达21个请求。NGINX立即转发所有21个请求,并将队列中的20个时隙标记为已占用,然后每100毫秒释放1个时隙。(如果改为有25个请求,NGINX将立即转发其中的21个请求,将20个插槽标记为已占用,并拒绝4个状态为的请求 503
。)
现在假设在转发第一组请求后101毫秒,同时有20个请求同时到达。队列中只有1个插槽已被释放,因此NGINX转发1个请求,并拒绝其他19个状态为status的请求 503
。相反,如果在20个新请求到达之前已经过去501毫秒,则5个时隙是空闲的,因此NGINX立即转发5个请求并拒绝15个。
该效果相当于每秒10个请求的速率限制。nodelay
如果要施加速率限制而不限制请求之间允许的间隔,则此选项很有用。
注意:对于大多数部署,我们建议 在指令中包含burst
和nodelay
参数limit_req
。
两阶段限速
使用NGINX Plus R17或NGINX开源1.15.7,您可以配置NGINX以允许突发请求以适应典型的Web浏览器请求模式,然后将额外的过多请求限制到一个点,超过该限制,额外的多余请求将被拒绝。指令的delay
参数启用了两阶段速率限制limit_req
。
为了说明两阶段的速率限制,在这里我们将NGINX配置为通过施加每秒5个请求(r / s)的速率限制来保护网站。该网站通常每页有4–6个资源,并且永远不会超过12个资源。该配置最多允许突发12个请求,其中第一个8个请求将被立即处理。在强制执行5 r / s限制的8个过多请求之后添加了延迟。在12个多余的请求之后,所有其他请求均被拒绝。
limit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;
server {
listen 80;
location / {
limit_req zone=ip burst=12 delay=8;
proxy_pass http://website;
}
}
该delay
参数定义了在突发大小之内限制(延迟)过多请求以符合定义的速率限制的点。使用此配置后,以8 r / s连续发出请求流的客户端将遇到以下行为。
delay
Nginx Plus会立即代理前8个请求(的值)。接下来的4个请求(burst
-
delay
)被延迟,因此不会超过所定义的5 r / s的速率。接下来的3个请求被拒绝,因为超出了总突发大小。后续请求被延迟。
高级配置示例
通过将基本速率限制与其他NGINX功能结合使用,您可以实现更细微的流量限制。
白名单
此示例显示了如何对来自不在“白名单”上的任何人的请求施加速率限制。
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
server {
location / {
limit_req zone=req_zone burst=10 nodelay;
# ...
}
}
本示例同时使用geo
和map
指令。该geo
块为白名单中的IP地址和所有其他IP地址 分配一个0
to 值 。然后,我们使用地图将这些值转换为键,例如:$limit
1
- 如果
$limit
为0
,$limit_key
则设置为空字符串 - 如果
$limit
为1
,$limit_key
则以二进制格式设置为客户端的IP地址
将两者放在一起,$limit_key
将白名单的IP地址设置为空字符串,否则将其设置为客户端的IP地址。当limit_req_zone
目录的第一个参数(键)为空字符串时,不会应用该限制,因此白名单的IP地址(在10.0.0.0/8和192.168.0.0/24子网中)不受限制。所有其他IP地址限制为每秒5个请求。
该limit_req
指令将限制应用于/位置,并允许超过配置的限制的最多10个数据包的突发,转发没有延迟
limit_req
在一个位置包含多个指令
您可以limit_req
在一个位置包含多个指令。将应用与给定请求匹配的所有限制,这意味着将使用限制性最强的限制。例如,如果一个指令强加了一个延迟,则使用最长的延迟。类似地,即使这是任何指令的结果,请求也会被拒绝,即使其他指令允许它们通过。
扩展前面的示例,我们可以对白名单上的IP地址应用速率限制:
http {
# ...
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;
server {
# ...
location / {
limit_req zone=req_zone burst=10 nodelay;
limit_req zone=req_zone_wl burst=20 nodelay;
# ...
}
}
}
白名单上的IP地址不匹配第一个速率限制(req_zone),但是匹配第二个速率限制(req_zone_wl),因此每秒限制为15个请求。不在白名单上的IP地址同时符合两个速率限制,因此适用的限制更为严格:每秒5个请求。
配置相关功能
记录中
默认情况下,NGINX记录由于速率限制而延迟或丢弃的请求,如以下示例所示:
2015/06/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginx.com, request: "GET / HTTP/1.0", host: "nginx.com"
日志条目中的字段包括::
limiting
requests
–指示日志条目记录了速率限制的指示符excess
–超出此请求代表的配置速率的每毫秒请求数zone
–定义施加速率限制的区域client
–发出请求的客户端的IP地址server
–服务器的IP地址或主机名request
–客户端发出的实际HTTP请求host
–Host
HTTP标头的值
默认情况下,NGINX在该error
级别记录拒绝的请求,如上[error]
例所示。(它会将延迟的请求记录在较低的一级,因此warn
默认情况下。)要更改日志记录级别,请使用limit_req_log_level
伪指令。在这里,我们将拒绝请求设置为登录warn
级别:
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_log_level warn
;
proxy_pass http://my_upstream;
}
发送给客户端的错误代码
默认情况下,当客户端超出其速率限制时,NGINX会以状态代码503
(Service
Temporarily
Unavailable
)进行响应。使用limit_req_status
指令设置不同的状态代码(444
在此示例中):
location /login/ {
limit_req zone=mylimit
burst=20 nodelay
;
limit_req_status 444
;
}
拒绝所有到特定位置的请求
如果要拒绝所有对特定URL的请求,而不仅仅是限制它们,请location
为其配置一个块并包含指令:deny
all
location /foo.php {
deny all;
}
结论
我们介绍了NGINX和NGINX Plus提供的速率限制的许多功能,包括为HTTP请求的不同位置设置请求速率,以及配置其他功能以限制速率,例如burst
和nodelay
参数。我们还介绍了高级配置,适用于对列入白名单和列入黑名单的客户端IP地址设置不同的限制,并说明了如何记录拒绝和延迟的请求。
还可以参考 https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-http/