
1. 引言Nginx 以其高性能和模块化设计闻名其对 SSL/TLS 的支持同样体现了这一哲学。本文将深入 Nginx 源码解析其 SSL 处理的两大核心设计模块无感设计Nginx 如何通过抽象层让核心的 HTTP/Stream 模块在几乎不感知 SSL 存在的情况下无缝支持加密通信。客户端 SSL 连接建立从接收到一个 TCP 连接开始到完成 SSL 握手Nginx 内部的事件驱动与状态机是如何协同工作的。通过本文你将理解 Nginx 架构的精妙之处并掌握其 SSL 实现的关键代码路径。2. 核心架构模块无感设计Nginx 实现模块无感的关键在于事件驱动架构和连接抽象层。2.1 连接结构体ngx_connection_tNginx 使用ngx_connection_t结构体表示一个网络连接。这个结构体是理解无感设计的关键。// src/core/ngx_connection.hstructngx_connection_s{// ... 其他成员void*data;// 指向上层模块如 HTTP 请求的上下文ngx_event_t*read;// 读事件ngx_event_t*write;// 写事件ngx_socket_tfd;// 底层 socket 文件描述符// SSL 相关ngx_ssl_connection_t*ssl;// SSL 连接上下文非 NULL 表示 SSL 连接// 回调函数指针ngx_ssl_handshake_pt ssl_handshake;// SSL 握手完成后的回调// ...};关键点在于ssl字段。对于非 SSL 连接该字段为NULL。对于 SSL 连接它指向一个ngx_ssl_connection_t结构体其中封装了 OpenSSL 的SSL*对象、读写缓冲区和握手状态。无感设计的体现上层模块如 HTTP 模块在读写数据时并不直接操作fd而是通过 Nginx 提供的ngx_connection_t的recv和send方法。这些方法内部会判断ssl字段是否为NULL从而决定是直接调用recv()/send()系统调用还是调用 OpenSSL 的SSL_read()/SSL_write()。2.2 读写操作的抽象Nginx 通过函数指针实现了读写操作的动态分发。// src/core/ngx_connection.cngx_int_tngx_connection_recv(ngx_connection_t*c,u_char*buf,size_tsize){if(c-ssl){// 如果是 SSL 连接调用 SSL 专用的接收函数returnngx_ssl_recv(c,buf,size);}// 否则直接调用系统 recvreturnngx_unix_recv(c,buf,size);}ngx_int_tngx_connection_send(ngx_connection_t*c,ngx_chain_t*in){if(c-ssl){// 如果是 SSL 连接调用 SSL 专用的发送函数returnngx_ssl_send_chain(c,in,0);}// 否则直接调用系统 sendreturnngx_unix_send(c,in,0);}这种设计模式被称为策略模式。ngx_connection_recv和ngx_connection_send是统一的接口而ngx_ssl_recv和ngx_ssl_send_chain是 SSL 的具体实现。上层模块如 HTTP 框架的ngx_http_request_handler只需调用c-recv和c-send完全无需关心底层是明文还是加密通道。3. 客户端 SSL 连接建立流程当一个客户端发起 HTTPS 请求时Nginx 的处理流程如下事件循环捕获新连接epoll通知 Nginx 有新的连接到达。接受连接ngx_event_accept函数接受连接创建ngx_connection_t。初始化 SSL如果该监听端口配置了 SSL则调用ngx_ssl_create_connection初始化 SSL 上下文。注册 SSL 握手事件将 SSL 握手过程注册为读事件。事件驱动握手当 socket 可读时触发握手回调函数ngx_ssl_handshake。握手完成握手成功后调用c-ssl_handshake回调将连接交给上层模块如 HTTP 框架处理。3.1 初始化 SSL 连接ngx_ssl_create_connection当 Nginx 的监听套接字ngx_listening_t配置了 SSL 时在ngx_event_accept中会调用此函数。// src/event/ngx_event_accept.cvoidngx_event_accept(ngx_event_t*ev){// ... 接受连接创建 c ...lsc-listening;// 如果监听配置了 SSLif(ls-ssl){// 创建 SSL 连接上下文if(ngx_ssl_create_connection(ls-ssl,c,0)!NGX_OK){ngx_close_connection(c);return;}// 设置 SSL 握手完成后的回调c-ssl_handshakengx_http_ssl_handshake;// 注册读事件准备开始握手rev-handlerngx_ssl_handshake;// 触发一次读事件如果数据已到达if(rev-ready){ngx_ssl_handshake(rev);}}else{// 非 SSL 连接直接交给 HTTP 模块处理rev-handlerngx_http_init_connection;// ...}}ngx_ssl_create_connection函数的核心工作是创建 OpenSSL 的SSL对象并将其与 Nginx 的连接关联起来。// src/event/ngx_event_openssl.cngx_int_tngx_ssl_create_connection(ngx_ssl_t*ssl,ngx_connection_t*c,ngx_uint_tflags){ngx_ssl_connection_t*sc;// 分配 SSL 连接上下文scngx_pcalloc(c-pool,sizeof(ngx_ssl_connection_t));if(scNULL){returnNGX_ERROR;}// 创建 OpenSSL 的 SSL 对象sc-sslSSL_new(ssl-ctx);if(sc-sslNULL){returnNGX_ERROR;}// 将 Nginx 的连接对象绑定到 OpenSSL 的 SSL 对象上// 这样在 OpenSSL 回调中可以通过 SSL_get_app_data 获取到 Nginx 的 connection_tSSL_set_app_data(sc-ssl,c);// 设置 socket fdSSL_set_fd(sc-ssl,c-fd);// 如果是服务端模式flags 0设置为接受模式if(flags0){SSL_set_accept_state(sc-ssl);}// 将 SSL 上下文挂到 Nginx 连接上c-sslsc;returnNGX_OK;}3.2 事件驱动的握手过程ngx_ssl_handshake这是 SSL 握手的核心函数它体现了 Nginx 事件驱动设计的精髓。握手不是阻塞进行的而是通过SSL_do_handshake尝试如果返回SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE则挂起等待 socket 再次可读/可写时重新触发。// src/event/ngx_event_openssl.cvoidngx_ssl_handshake(ngx_event_t*rev){ngx_connection_t*c;ngx_ssl_connection_t*sc;intn,sslerr;crev-data;scc-ssl;// 尝试进行 SSL 握手nSSL_do_handshake(sc-ssl);if(n1){// 握手成功// 设置读写 handler 为上层模块的 handlerrev-handlerc-ssl_handshake;// 调用握手完成回调将连接交给 HTTP 框架c-ssl_handshake(c);return;}// 握手未完成获取错误码sslerrSSL_get_error(sc-ssl,n);if(sslerrSSL_ERROR_WANT_READ){// OpenSSL 希望我们等待 socket 可读// 注册读事件等待 epoll 再次触发ngx_add_event(rev,NGX_READ_EVENT,0);// 设置超时ngx_add_timer(rev,c-listening-post_accept_timeout);return;}if(sslerrSSL_ERROR_WANT_WRITE){// OpenSSL 希望我们等待 socket 可写// 注册写事件ngx_add_event(c-write,NGX_WRITE_EVENT,0);ngx_add_timer(rev,c-listening-post_accept_timeout);return;}// 其他错误关闭连接ngx_ssl_handshake_log(c,sslerr,n);ngx_ssl_handshake_close(c);}流程图解OpenSSL 库Nginx 事件循环客户端OpenSSL 库Nginx 事件循环客户端事件循环等待事件循环等待TCP 连接请求ngx_event_accept 接受连接ngx_ssl_create_connection 初始化 SSL注册 ngx_ssl_handshake 为读事件 handlerClientHello (TLS 握手开始)epoll 触发读事件SSL_do_handshake()SSL_ERROR_WANT_WRITE (需要发送 ServerHello)注册写事件等待 socket 可写ServerHello, Certificate, ServerHelloDoneClientKeyExchange, ChangeCipherSpec, Finishedepoll 触发读事件SSL_do_handshake()1 (握手成功)调用 c-ssl_handshake 回调连接移交给 HTTP 模块处理3.3 握手完成后的移交当SSL_do_handshake返回 1 时握手成功。ngx_ssl_handshake函数会调用c-ssl_handshake回调。对于 HTTP 模块这个回调通常是ngx_http_ssl_handshake。// src/http/ngx_http_request.cvoidngx_http_ssl_handshake(ngx_connection_t*c){// 将连接的处理权交给 HTTP 框架// 设置读事件的 handler 为 ngx_http_process_request_linec-read-handlerngx_http_process_request_line;// 开始处理 HTTP 请求ngx_http_process_request_line(c-read);}至此SSL 连接建立完成后续的 HTTP 请求解析和响应发送都通过ngx_connection_recv和ngx_connection_send进行而这两个函数内部会自动处理 SSL 加解密对 HTTP 模块完全透明。4.1 性能调优与边界条件Nginx 的 SSL 处理虽然设计精良但在高并发场景下SSL/TLS 握手和加密运算仍可能成为性能瓶颈。合理配置关键参数理解边界条件是保障服务稳定性的前提。关键配置参数ssl_buffer_size该参数控制 Nginx 在发送 SSL 记录时使用的缓冲区大小默认 16KB。它直接影响 TLS 记录的分片粒度减小缓冲区如 4KB适合实时性要求高的场景如 WebSocket、SSE可以减少客户端收到完整响应前的延迟因为每个 TLS 记录可以更早地被发送出去。增大缓冲区如 32KB适合大文件下载或视频流场景可以减少 TLS 记录的数量从而降低 OpenSSL 加解密的上下文切换开销提升吞吐量。# 针对实时 API 优化 ssl_buffer_size 4k; # 针对大文件下载优化 ssl_buffer_size 32k;ssl_session_cacheSSL 会话缓存是提升性能最有效的手段之一。它允许客户端在后续连接中复用之前协商的会话密钥从而跳过完整的 TLS 握手从 2-RTT 降为 1-RTT甚至 0-RTT。# 使用共享内存缓存大小为 50MB可存储约 400,000 个会话 ssl_session_cache shared:SSL:50m; # 会话超时时间默认 5 分钟 ssl_session_timeout 1h;shared:SSL:50m所有 worker 进程共享同一块内存缓存避免进程间重复存储。50MB 大约可容纳 40 万个会话。ssl_session_timeout会话的有效期。对于高频短连接场景如 API 网关建议延长到 1 小时以上对于安全性要求极高的场景如网银可缩短到 5 分钟。源码视角Nginx 在ngx_ssl_handshake成功后会调用SSL_set_session将新会话存入缓存。当客户端发送ClientHello中包含 Session ID 或 Session Ticket 时Nginx 会尝试从缓存中查找匹配的会话若命中则直接完成握手不再进行完整的证书交换和密钥协商。ssl_session_ticketsSession Ticket 是另一种会话恢复机制它将加密后的会话状态直接交给客户端保存服务端无需维护缓存。适合大规模分布式部署但需要配置统一的 Ticket Key。# 启用 Session Ticket ssl_session_tickets on; # 设置统一的 Ticket Key多台 Nginx 之间共享 ssl_session_ticket_key /etc/nginx/ticket.key;高并发下的边界条件与缓解思路1. 握手风暴Handshake Storm现象在突发流量或大量短连接场景下所有客户端都需要进行完整的 TLS 握手导致 CPU 使用率瞬间飙升尤其是 RSA 非对称解密操作私钥操作成为瓶颈。缓解思路启用会话缓存/复用如前所述ssl_session_cache和ssl_session_tickets可以将握手开销降低 80% 以上。使用 ECDSA 证书ECDSA椭圆曲线数字签名算法的密钥生成和签名验证速度远快于 RSA。在同等安全强度下ECDSA 的私钥操作性能比 RSA 快 3-5 倍。调整ssl_handshake_timeout默认 60 秒。在极端高并发下可以适当缩短超时时间快速释放无法完成握手的连接资源。硬件加速使用支持 AES-NI 指令集的 CPU 加速对称加密或使用专用 SSL 加速卡如 Intel QAT卸载非对称运算。2. 内存与连接数耗尽现象每个 SSL 连接在握手期间都需要维护 OpenSSL 的SSL对象约 200-300KB 内存以及 Nginx 的ngx_ssl_connection_t结构体。当并发连接数达到数十万时内存压力巨大。缓解思路调整worker_connections和worker_rlimit_nofile确保系统文件描述符和 worker 进程的连接数上限足够。启用ssl_early_dataTLS 1.3 0-RTT允许客户端在第一个数据包中就携带 HTTP 请求减少握手轮次从而缩短连接占用时间。使用连接池对于上游代理场景Nginx 的keepalive连接池可以减少与后端服务器之间的 SSL 握手频率。3. 证书链验证开销现象每次完整握手时服务端需要发送完整的证书链通常 2-4 个证书客户端需要逐级验证。证书链越长握手延迟越高。缓解思路精简证书链只包含必要的中间证书避免将根证书也包含在内根证书已在客户端信任存储中。启用 OCSP Stapling由 Nginx 定期从 CA 获取证书的吊销状态并在握手时直接返回给客户端避免客户端自行去 OCSP 服务器查询减少一次额外的网络延迟。# 启用 OCSP Stapling ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid300s;小结Nginx SSL 的性能调优是一个系统工程需要结合业务场景选择合适的参数组合。核心原则是减少完整握手的次数通过会话复用、降低单次握手的开销通过 ECDSA 证书和 OCSP Stapling、合理分配系统资源通过缓冲区大小和超时控制。理解这些边界条件才能在高并发场景下充分发挥 Nginx 的 SSL 处理性能。4. 总结Nginx 通过精妙的连接抽象和事件驱动设计实现了 SSL 模块的无缝集成无感设计通过ngx_connection_t中的ssl指针和recv/send函数指针将 SSL 的复杂性隐藏在抽象层之下上层模块无需修改即可支持加密通信。非阻塞握手利用 OpenSSL 的非阻塞模式和 Nginx 的事件循环ngx_ssl_handshake函数通过SSL_ERROR_WANT_READ/WRITE机制优雅地处理了握手过程中的 I/O 等待避免了阻塞 worker 进程。理解这些源码设计不仅能帮助你更好地配置和优化 Nginx SSL也能为你自己的高性能网络编程提供宝贵的参考。