$ssl_server_name仅在TLS握手完成且SNI解析成功后可用,须在ssl_certificate_by_lua_block或access_by_lua_block中使用;推荐在前者中结合动态证书加载与SNI限流,后者叠加URI细粒度控制,并规避init_by_lua等错误上下文。

直接利用 $ssl_server_name 进行多证书场景下的 Lua 限流,核心在于:该变量在 TLS 握手完成、SNI 解析成功后才可用,且仅存在于 HTTPS 请求的 server 块或 location 块上下文中(需启用 ssl_preread on 的预读模式除外,但此时该变量不可用)。因此,高级限流必须结合 Nginx 的 SSL 阶段与 Lua 执行时机精准协同。
确认 $ssl_server_name 可用的前提条件
该变量不是“随时可取”的普通变量。它依赖于:
- Nginx 已配置多个
server块或使用ssl_certificate_by_lua*指令,并启用 TLS 1.2+ 和 SNI 支持; - 请求实际携带了有效的 SNI 扩展(绝大多数现代客户端都支持);
- 未启用
ssl_preread on—— 因为该指令会跳过 SSL 握手,导致$ssl_server_name为空,此时应改用$ssl_preread_server_name; - 脚本运行在
ssl_certificate_by_lua_block或access_by_lua_block中(后者需确保 SSL 已完成握手)。
在 ssl_certificate_by_lua_block 中做动态证书选择 + 限流决策
这是最推荐的阶段:既可读取 SNI,又能干预证书加载,还能提前拒绝非法域名请求。例如:
(Nginx 配置片段)
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
server {
listen 443 ssl;
ssl_certificate /var/ssl/default.crt;
ssl_certificate_key /var/ssl/default.key;
<pre class="brush:php;toolbar:false;">ssl_certificate_by_lua_block {
local sni = ngx.var.ssl_server_name
if not sni or sni == "" then
return ngx.exit(421) -- Misdirected Request
end
-- 按域名查白名单/配额策略(可对接 Redis 或本地 LRU)
local limit_key = "limit:" .. sni
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)
red:connect("127.0.0.1", 6379)
-- 使用令牌桶或漏桶(此处简化为计数器 + 过期)
local curr, err = red:incr(limit_key)
if not curr then
ngx.log(ngx.ERR, "redis incr failed: ", err)
return ngx.exit(500)
end
if curr == 1 then
red:expire(limit_key, 60) -- 60秒窗口
end
if curr > 100 then -- 每分钟最多100次握手尝试(防 SNI 扫描)
ngx.log(ngx.WARN, "SNI rate limit exceeded for: ", sni)
return ngx.exit(429)
end
-- 动态加载对应证书(需提前按域名组织好 PEM 文件)
local cert_path = "/var/ssl/certs/" .. sni .. ".crt"
local key_path = "/var/ssl/keys/" .. sni .. ".key"
if ngx.ssl.set_der_cert(ngx.readFile(cert_path)) ~= ngx.OK then
ngx.log(ngx.ERR, "failed to set cert for ", sni)
end
if ngx.ssl.set_der_priv_key(ngx.readFile(key_path)) ~= ngx.OK then
ngx.log(ngx.ERR, "failed to set key for ", sni)
end
}}
在 access_by_lua_block 中叠加应用层限流(基于 SNI + URI 组合)
当需要更细粒度控制(如每个域名下不同 path 的 QPS 不同),可在 access 阶段结合 $ssl_server_name 和 $request_uri 构造复合限流键:
- 使用
resty.limit.count或resty.limit.traffic模块,以sni:uri为 key 做每秒/每分钟限制; - 对高价值域名(如 api.example.com)启用更严策略,对静态资源域名(cdn.example.com)放宽并发;
- 配合
ngx.var.upstream_http_x_ratelimit_remaining等头透传剩余配额,供上游服务感知。
规避常见陷阱
几个关键细节容易出错:
- 不要在 init_by_lua* 中访问 $ssl_server_name —— 此时连接尚未建立,变量为空;
- 避免在 http 块顶层使用该变量 —— 它只在 SSL server 上下文有效;
-
注意大小写和通配符处理:SNI 是区分大小写的纯字符串,
example.com≠EXAMPLE.COM,通配符证书(*.example.com)不会影响$ssl_server_name的值(它始终是客户端发送的原始 host); -
HTTP/2 多路复用不影响该变量取值 —— 每个 stream 共享同一个 TLS 连接,
$ssl_server_name来自该连接的 SNI,对所有后续请求一致。










