Nginx 原生不支持 SSL 握手延迟指标,需用 OpenResty + ssl_certificate_by_lua_block 在 TLS 握手完成瞬间打点,结合 nginx-lua-prometheus 直方图采集毫秒级时序数据,并通过独立 HTTP 端口暴露供 Prometheus 抓取。

在 Nginx 中直接暴露 SSL 握手延迟的原生 Prometheus 指标不可行,因为官方模块不提供该维度的毫秒级时序数据。但可通过 OpenResty + Lua + nginx-lua-prometheus 组合,在 TLS 握手完成瞬间打点,实现高精度、低开销的实时采集。
启用 SSL 握手时间捕获的关键前提
Nginx 本身在 ssl_handshake 阶段不暴露可编程钩子,必须升级为 OpenResty(基于 Nginx 的增强版),并启用 ssl_certificate_by_lua* 阶段——这是唯一能在 TLS 握手成功后、HTTP 请求解析前执行 Lua 代码的阶段,也是记录握手结束时间的精确位置。
- 确保使用 OpenResty ≥ 1.21.4.2(支持完整
ssl_certificate_by_lua_block) - 关闭
ssl_session_cache或设为shared模式(避免复用会跳过完整握手,导致指标缺失) - 禁用 HTTP/2 的早期 ALPN 协商干扰(添加
ssl_protocols TLSv1.2 TLSv1.3;明确限定)
在 ssl_certificate_by_lua_block 中记录握手耗时
利用 Lua 的 ngx.now() 在握手开始与结束两个关键点打时间戳。由于无法在真正“开始”时介入,采用“请求到达时预估起点 + 握手成功时确认终点”的策略:
- 在
init_worker_by_lua_block中初始化一个计数器和直方图(如ssl_handshake_duration_seconds) - 在
ssl_certificate_by_lua_block开头调用ngx.var.time_iso8601或ngx.now()记录当前时间作为近似起点(实际偏差 ≤ 1ms) - 在 block 末尾再次调用
ngx.now(),相减得到握手延迟,写入 Prometheus 直方图:histogram:observe(ngx.now() - start_time, {server_name = ngx.var.server_name}) - 注意:此阶段不能访问
$request_time或$upstream_connect_time,仅能依赖 Lua 本地时钟
暴露指标并配置 Prometheus 抓取
通过 prometheus_metrics location 暴露指标,需单独启用一个非 TLS 的 metrics 端口(如 9145),避免 HTTPS 自身引入额外延迟干扰观测:
- 在 server 块外新增监听
listen 9145;的纯 HTTP server,仅含location /metrics { content_by_lua_block { prometheus:collect() } } - Prometheus 配置 job,抓取该端口,添加
metric_relabel_configs过滤出以ssl_handshake_开头的指标 - 建议对直方图设置合理分位(0.5/0.9/0.99),桶边界覆盖 50ms–2000ms(覆盖多数公网握手场景)
验证与常见陷阱规避
部署后用 curl http://localhost:9145/metrics | grep ssl_handshake 检查指标是否生成;用 openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | wc -l 触发真实握手并观察指标增量。
- 若指标长期为 0:检查 OpenResty 版本、
ssl_certificate_by_lua_block是否被语法错误阻断(查看 error.log 中 lua 错误) - 若数值异常偏高(>5s):确认未在该阶段执行阻塞操作(如同步 DNS 查询、HTTP 调用)
- 若同一域名出现多个 server_name 标签值:检查 SNI 配置是否正确,避免 fallback 到 default_server 导致标签混乱


















