$server_name不能用于limit_conn_zone,因其是请求头解析后才确定的运行时变量,而zone key必须在连接初始化阶段即可访问;可用$host配合map映射实现按虚拟主机限流。

不能直接用 $server_name 在 limit_conn_zone 中做 key 实现“每个虚拟主机独立限流”——Nginx 启动时会报错 "invalid variable name",因为 $server_name 不是允许用于 zone key 的变量。
为什么 $server_name 不能用于 limit_conn_zone
Nginx 要求 limit_conn_zone 的 key 必须在 server 块解析前就能确定(即在 connection 初始化阶段可访问),而 $server_name 是在 HTTP 请求头解析后、匹配到具体 server 块时才确定的。实际可用的变量仅限于:$binary_remote_addr、$remote_addr、$cookie_*、$header_* 等预解析变量。
-
$server_name是运行时变量,不在白名单中,Nginx 会拒绝加载配置 - 即使你写成
limit_conn_zone $server_name zone=sname:10m;,reload 时会失败并提示:invalid variable "$server_name" - 真正生效的替代方案是:用
$host(来自请求 Host 头)或自定义map映射
用 map + $host 模拟按虚拟主机限流
核心思路是:把 Host 头映射为一个稳定、可控的字符串,再作为 limit_conn_zone 的 key。这样既能区分不同域名,又规避了 $server_name 的限制。
map $host $virtual_host_key {
default $host;
~^(www\.)?(?<domain>.+\..+)$ $domain;
example.com "example";
api.example.org "api-example";
}</domain>
然后定义 zone:
limit_conn_zone $virtual_host_key zone=vhost:10m;
在 server 块中启用:
server {
server_name example.com www.example.com;
limit_conn vhost 50;
# …
}
-
map必须定义在http块顶层,不能放在 server 或 location 内 - 正则分支优先级从上到下,
default是兜底,建议显式写出关键域名避免歧义 - 如果 Host 头被篡改(如恶意请求带 fake Host),该方案也会被绕过;生产环境建议配合
underscores_in_headers on和严格server_name匹配使用
limit_conn 放在 http / server / location 的影响差异
并发连接数统计范围取决于 limit_conn 指令所在作用域:
- 放在
http块:对所有 server 共享同一个 zone 计数器 → 不满足“每虚拟主机独立”需求 - 放在
server块:只对该 server 内所有请求生效,但 zone 本身仍是全局的(key 决定分组)→ 正确用法 - 放在
location块:仍基于同一 zone 统计,只是触发位置更细粒度;注意它不会隔离连接数,只是多一层判断条件 - 不要在多个 server 块里重复写
limit_conn vhost 50却期望各自独立限额——限额值是 per-key 的,不是 per-server 的
容易被忽略的两个硬限制
即使 map 和 zone 配置都正确,实际效果还受底层机制制约:
-
limit_conn统计的是 TCP 连接数(非请求数),HTTP/2 多路复用下,一个连接可承载大量请求,此时并发连接数远低于请求数,限流效果会被弱化 - zone 大小(如
vhost:10m)决定了最多能存储多少个不同 key 的计数器;若虚拟主机数量超千级,且 key 字符串较长,可能触发"limit_conn: table is full"警告,需调大 size 或精简 key
真正要压住单域名流量,往往得结合 limit_req(请求速率)+ limit_conn(连接数)双控,且 key 设计必须兼顾稳定性与区分度——$host 看似简单,但没加 map 规范化的话,www 和非 www、大小写、端口等都会导致 key 分裂,让限流失效。











