这系列运维笔记写到三十二篇的时候,我反而被一个最基础的需求问住了:安全审计那边要一份对外域名TLS配置的评估说明,包括当前还开放了哪些TLS协议版本、被老协议打进来的流量来自哪些IP和客户端、证书链信息能不能集中核查。他们觉得这就是“日志分析系统一查就能出来”的事,但我自己心里清楚,手头别说有没有现成的SSL日志分析工具,连“日志里到底有没有把TLS字段记录下来”这一件事都没办法立刻确认。
后来我把散落在Nginx访问日志、错误日志、系统层TLS探测结果里的信息串起来做了一套处理方案,才真正把“SSL日志分析”从一句口号变成能回答业务问题的数据链路。如果你也在做服务器运维,想弄清楚自己网关上的TLS流量到底长什么样、哪些客户端还在拖后腿、什么时候该关停老协议,这篇内容应该能给你省下不少试错时间。
1. 从审计需求倒推:SSL日志分析到底该回答哪类问题
先别急着学工具,先想一个问题:SSL日志分析要回答的,往往不是“一条日志里写了什么”,而是“整个入口的TLS安全现状如何”。安全审计抛过来的问题看着简单,实际上要做好,得串起一整条证据链。
1.1 审计想要的三件事和对应的日志支撑
审计关注的点通常落在三个方向:入口协议是否过老、被老协议打进来的流量来自哪里、证书资产是否有系统性管理。这三个方向对应到日志侧,其实是三类完全不同的数据。
第一类是访问日志里的TLS协商字段,比如ssl_protocol、ssl_cipher、ssl_server_name。它能回答“当前Nginx对外到底用了TLSv1.2还是TLSv1.3,哪些请求还在用TLSv1.0/1.1”。第二类是错误日志里的握手失败信息。这类日志在Nginx默认配置下不一定完整保留,需要把error_log级别调到info并做好轮转,才能抓到SSL_do_handshake() failed这类记录,包括错误码、失败原因和客户端IP。第三类是证书信息。日志自身的用处有限,通常是通过周期性的TLS探测或者证书文件检查来补全,不然审计问“你这批证书还有几天过期”时,你在日志系统里是搜不到答案的。
每类数据的来源、形态、保留周期都不一样,所以做分析之前一定要先画清楚。否则工具链搭得再漂亮,数据源少一个,结论就是残缺的。
1.2 我当时的环境到底缺了什么
我手头的环境其实很典型:前几年部署Nginx时,访问日志虽然用了统一的log_format,但里面根本没有TLS相关字段,只有普通的$remote_addr、$request和$http_user_agent。这带来的问题是灾难性的——现在想知道某个客户端用的什么协议版本,翻历史日志压根没有,只能靠后续补采集。
错误日志那侧更麻烦。Nginx默认只有error及更高级别会落盘,而大量TLS握手失败其实是记录在info级别的。等于说,过去几年遇到的所有异常握手,我这边基本只有一句泛泛的“could not be established”或者干脆没记下来。真要回溯某个时间段的异常扫描,根本无从下手。
这些坑说明一个问题:SSL日志分析的第一个工作不是写脚本,而是回到采集源头确认字段完整。基于这个结论,我重写了访问日志的log_format、调整了错误日志级别,再把证书探测脚本挂进定时任务,才算把缺口补上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三个关键日志源:访问格式、握手失败记录和主动探测结果
对Nginx环境来说,我最后沉淀下来的日常分析对象就三类:访问日志中记录的TLS协商字段、错误日志中显示的握手失败踪迹、主动探测产生的证书与协议结果。三者各有各的用场,谁也替代不了谁。
2.1 访问日志如何更完整地记录TLS协商字段
如果还在用默认的combined格式,建议现在就停掉。做SSL日志分析,Nginx的log_format至少要包含协议版本、加密套件、会话是否复用、SNI服务器名和连接内的请求序号,否则很多问题只能靠猜。
这是我后来在网关环境长期使用的一个JSON格式配置:
code复制log_format json_ssl escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"server_port":"$server_port",'
'"server_name":"$server_name",'
'"ssl_server_name":"$ssl_server_name",'
'"ssl_protocol":"$ssl_protocol",'
'"ssl_cipher":"$ssl_cipher",'
'"ssl_curve":"$ssl_curve",'
'"ssl_session_reused":"$ssl_session_reused",'
'"connection_requests":"$connection_requests",'
'"status":"$status",'
