问题描述

在配置 Nginx 反向代理服务器时,遇到了 TLS 1.2 连接失败的问题。使用 OpenSSL 客户端测试时出现以下错误:

$ openssl s_client -connect example.com:443 -tls1_2
CONNECTED(00000003)
40376B9103770000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:../ssl/record/rec_layer_s3.c:1599:SSL alert number 40
---
no peer certificate available
---
SSL handshake has read 7 bytes and written 218 bytes

问题分析

1. 错误信息解读

  • sslv3 alert handshake failure 表示 SSL/TLS 握手失败
  • SSL alert number 40 是握手失败的具体错误代码
  • 客户端和服务器无法协商出双方都支持的密码套件

2. 配置检查

原始 Nginx SSL 配置:

server {
    listen 443 ssl;
    server_name example.com;
  
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;
  
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
}

3. 问题根因

配置中的密码套件过于严格,只包含了4个高强度的密码套件:

  • ECDHE-RSA-AES256-GCM-SHA512
  • DHE-RSA-AES256-GCM-SHA512
  • ECDHE-RSA-AES256-GCM-SHA384
  • DHE-RSA-AES256-GCM-SHA384

这些密码套件都是 256 位 AES 加密配合 SHA384/512,虽然安全性很高,但兼容性较差。许多客户端(包括一些旧版本的浏览器和 OpenSSL)可能不支持这些密码套件。

解决方案

1. 扩展密码套件列表

采用 Mozilla SSL Configuration Generator 推荐的中级配置:

server {
    listen 443 ssl http2;
    server_name example.com;
  
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;
  
    # 支持 TLS 1.2 和 1.3
    ssl_protocols TLSv1.2 TLSv1.3;
  
    # 使用更广泛兼容的密码套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  
    # 在 TLS 1.3 中,客户端优先;在 TLS 1.2 中,服务器优先
    ssl_prefer_server_ciphers off;
  
    # 其他优化配置
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
  
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
  
    # 添加安全头
    add_header Strict-Transport-Security "max-age=63072000" always;
}

2. 配置说明

密码套件选择原则:

  • 包含 128 位和 256 位 AES 加密
  • 支持 ECDHE 和 DHE 密钥交换
  • 包含 GCM 和 CHACHA20-POLY1305 认证加密
  • 兼顾安全性和兼容性

关键配置项:

  • ssl_prefer_server_ciphers off:让客户端选择偏好的密码套件,提高兼容性
  • ssl_session_cache:启用会话缓存,提高性能
  • ssl_stapling:启用 OCSP 装订,加快证书验证

3. 验证配置

应用配置后,使用以下命令验证:

# 测试配置语法
nginx -t

# 重载配置
nginx -s reload

# 测试 TLS 1.2 连接
openssl s_client -connect example.com:443 -tls1_2

# 测试 TLS 1.3 连接
openssl s_client -connect example.com:443 -tls1_3

# 查看支持的密码套件
nmap --script ssl-enum-ciphers -p 443 example.com

最佳实践建议

1. 定期更新配置

SSL/TLS 标准不断演进,建议:

  • 定期查看 Mozilla SSL Configuration Generator
  • 关注安全公告,及时禁用不安全的协议和密码套件
  • 使用 SSL Labs 等工具定期检测配置

2. 监控和日志

# 启用 SSL 错误日志
error_log /var/log/nginx/ssl_error.log debug;

# 记录 SSL 协议和密码套件
log_format ssl_log '$remote_addr - $remote_user [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   '"$http_referer" "$http_user_agent" '
                   '$ssl_protocol $ssl_cipher';

access_log /var/log/nginx/ssl_access.log ssl_log;

3. 负载均衡配置

对于使用 upstream 的场景,确保 keepalive 配置正确:

upstream backend {
    least_conn;
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        # 其他 proxy 设置...
    }
}