Nginx无法直接记录TLS版本协商失败,但可通过error_log中“unsupported protocol”等错误信号与stream模块$ssl_preread_protocol预检日志关联,结合stub_status的Accepts数计算阻断率,并通过配置收敛和日志结构化实现可观测性。

Nginx 本身不直接提供 TLS 版本协商失败的原生变量(如 $ssl_client_protocol 在握手失败时不可用),因此无法像统计 HTTP 状态码那样,用 log_format + $status 直接记录“因客户端 TLS 版本过低被拒”的连接。但可通过组合日志分析、协议探测与配置约束,间接实现对这类无效安全连接的监控与阻断率统计。核心思路是:把“握手失败”转化为可观测、可聚合的日志信号,并排除其他干扰因素。
从 error_log 中提取有效阻断信号
TLS 握手失败发生在 SSL 层,不生成 access_log 记录,但会稳定写入 error_log,且错误信息具有强特征:
-
SSL_do_handshake() failed (SSL: error:14876181:SSL routines:SSL13_GET_CLIENT_HELLO:unsupported protocol) -
SSL_do_handshake() failed (SSL: error:1409442E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version) -
no protocols available或SSL: client TLS version: TLSv1.0(需 debug 日志)
这些日志行明确指向服务端拒绝了客户端提出的 TLS 协议版本,即“无效安全连接”已发生。
建议操作:
- 将
error_log级别临时设为warn(默认即可捕获上述错误) - 使用结构化方式集中收集(如
rsyslog+filebeat→ ELK / Loki) - 提取字段:时间戳、客户端 IP、server_name(若多域名)、错误码关键词(如
unsupported protocol)
构建阻断率统计模型
阻断率 =
(因 TLS 版本不兼容被拒的连接数) ÷ (所有 TLS 握手尝试总数) × 100%
其中:
- 分子:从
error_log中匹配unsupported protocol或tlsv1 alert protocol version的行数(按小时/天聚合) - 分母:需估算——Nginx 没有原生握手计数器,但可用以下代理指标:
-
nginx_stub_status中的Accepts(接受的 TCP 连接数)≈ 握手尝试总数(因绝大多数 HTTPS 请求都会发起 TLS 握手) - 或更精确:启用
ssl_preread模块 +stream块,在 TLS 握手前读取 ClientHello(需 OpenSSL ≥ 1.1.1),用$ssl_preread_protocol判断客户端声明的版本(如TLSv1.0),再结合log_format记录到独立日志
-
示例(stream 模块预检):
stream {
log_format tls_preread '$remote_addr [$time_local] "$ssl_preread_protocol" "$ssl_preread_server_name"';
access_log /var/log/nginx/tls-preread.log tls_preread;
upstream https_backend {
server 127.0.0.1:443;
}
server {
listen 443 ssl preread;
ssl_preread on;
proxy_pass https_backend;
}
}该日志可统计所有进入的 TLS 握手请求及其声明版本,再与 error_log 中的拒绝日志做 IP+时间窗口关联,即可精准计算阻断率。
配置层强化可观测性与归因能力
避免误判的关键是确认失败确实源于客户端版本低,而非服务端配置错、证书问题或中间设备干扰:
- 确保每个
server块显式声明ssl_protocols TLSv1.2 TLSv1.3;(禁用 TLSv1.0/1.1) - 关闭
ssl_prefer_server_ciphers off;(默认值,不影响) - 不在
http块全局设ssl_protocols,防止遗漏 server 导致策略失效 - 使用
openssl s_client -connect example.com:443 -tls1主动验证:应返回SSL routines::wrong version number,而非成功握手
同时,在 error_log 行中加入上下文:
error_log /var/log/nginx/error.log warn; # 若使用 debug 日志定位,仅限短时:error_log /var/log/nginx/ssl-debug.log debug;
自动化统计脚本建议(每日执行)
# 统计昨日阻断数(error_log)
BLOCKED=$(zgrep -h "unsupported protocol\|tlsv1 alert protocol version" /var/log/nginx/error.log* 2>/dev/null | \
awk -v d="$(date -d 'yesterday' +%Y/%m/%d)" '$0 ~ d {print}' | wc -l)
# 统计昨日总 Accepts(需开启 stub_status 并 curl 获取)
ACCEPTS=$(curl -s http://127.0.0.1/nginx_status 2>/dev/null | grep "^Accepts" | awk '{print $2}')
if [ "$ACCEPTS" -gt 0 ]; then
RATE=$(echo "scale=2; $BLOCKED * 100 / $ACCEPTS" | bc)
echo "$(date -d 'yesterday' +%Y-%m-%d), $BLOCKED, $ACCEPTS, ${RATE}%"
fi输出可导入 Grafana 或 CSV 归档,形成趋势图。
本质上,这不是一个“开箱即用”的变量调用,而是通过日志语义 + 流量前置探测 + 配置收敛,构建出一套可落地的 TLS 兼容性健康度监控体系。


















