HTTPS 实战与排错 — 证书部署、curl 诊断与常见故障

Wireshark 观察加密流量、openssl/curl 证书检查、Nginx+ACME 部署、混合内容与证书链故障排查 / 应用层 / TCP 443

这篇你能学到

  • 用 Wireshark / tcpdump 观察 HTTPS 流量:没密钥时能看到什么、配了 SSLKEYLOGFILE 又能解密到什么。
  • openssl s_clientcurl 做证书检查的第一现场诊断(链是否完整、是否快过期、SAN 对不对)。
  • 一份可直接用的 Nginx + Let’s Encrypt 生产配置,以及证书链不全、混合内容、重定向循环等经典故障的"结论先行"排错法。
  • HTTPS 与 HTTP / SSH / IPSec / WireGuard 的对比表,和 10 道高频面试题。

抓包观察

Wireshark 显示过滤式

目的过滤表达式
全部 443 流量tcp.port == 443
TLS 握手tls.handshake
ClientHello(看 SNI/ALPN)tls.handshake.type == 1
ServerHello(看选定套件)tls.handshake.type == 2
证书消息(TLS 1.2 可直接看到证书)tls.handshake.type == 11
按访问的域名过滤tls.handshake.extensions_server_name == "example.com"
协商的应用协议tls.handshake.extensions_alpn_str == "h2"
握手失败告警tls.alert_message
加密的应用数据tls.record.content_type == 23
HTTP/3(UDP 443)quichttp3
解密后按 HTTP 过滤(需配 keylog)http / http2

典型字段说明

未解密时能看到什么:只有 ClientHello 里的 SNI(访问的域名)、ALPN(h2/http/1.1)、目的 IP 与端口,以及每个加密记录的长度和时间。这正好演示了"HTTPS 保护内容,但不隐藏你访问了谁"。

排查思路:HTTPS 故障 90% 出在 TLS 握手阶段,因此抓包先看这三点:

  1. tls.handshake.type == 1 是否携带了 SNI?缺失会导致服务器返回默认站点证书 → 域名不匹配。
  2. tls.handshake.type == 2 之后是否立即出现 Alert?出现即为握手失败,看告警编号定位(40 无共同参数、45 证书过期、48 CA 不受信)。
  3. 握手完成后是否有 application_data 往返?若握手成功但无数据,问题在 HTTP 层而非 TLS 层。

解密自己的 HTTPS 流量

下面的命令演示如何把自己的 TLS 主密钥导出到日志文件,再喂给 Wireshark 解密——仅用于调试你自己拥有的流量,别用来看别人的。

1
2
3
4
5
6
export SSLKEYLOGFILE=/tmp/keys.log
curl https://example.com # 或用同环境变量启动 Chrome/Firefox

# Wireshark → 首选项 → Protocols → TLS → (Pre)-Master-Secret log filename 填 /tmp/keys.log
# 或命令行:
tshark -r https.pcapng -o tls.keylog_file:/tmp/keys.log -Y 'http2.headers.method'

常用命令 / 配置

证书检查(排查第一站)

排证书问题,第一现场永远是 openssl s_client。下面一组命令分别看"证书主体/签发者/有效期/SAN"“链有几张"“是否快过期"“OCSP 钉没钉”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 一条命令看清证书主体、签发者、有效期、SAN
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

# 查看服务器实际下发了几张证书(判断链是否完整,应 ≥2)
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
  | grep -c 'BEGIN CERTIFICATE'

# 严格校验链,失败即报错退出(适合放进监控脚本)
openssl s_client -connect example.com:443 -servername example.com \
  -verify_return_error -verify_hostname example.com </dev/null

# 检查证书是否将在 30 天内过期(返回码 0 = 不会过期)
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -checkend 2592000 && echo OK || echo "30 天内将过期"

# 查看 OCSP Stapling 是否开启
openssl s_client -connect example.com:443 -servername example.com -status </dev/null 2>&1 \
  | grep -A5 'OCSP Response Status'

# 校验本地证书与私钥是否配对(两行输出必须一致)
openssl x509 -noout -modulus -in fullchain.pem | openssl md5
openssl rsa -noout -modulus -in privkey.pem | openssl md5

# 用本地 CA 校验证书链
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt fullchain.pem

curl 诊断

curl 是验证"证书层"和"HTTP 层"谁出问题的利器。-k 能通而不加不能通,就基本锁定是证书问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 查看握手细节 + 证书 + ALPN
curl -v https://example.com 2>&1 | grep -E 'ALPN|SSL connection|subject|issuer|expire'

# 分阶段耗时:TLS 握手到底占了多久
curl -w 'DNS:%{time_namelookup} TCP:%{time_connect} TLS:%{time_appconnect} TTFB:%{time_starttransfer} 总:%{time_total}\n' \
  -o /dev/null -s https://example.com

# 绕过 DNS 直连某台后端验证其证书(灰度/多机排查必备)
curl -v --resolve example.com:443:10.0.0.7 https://example.com/health

# 临时跳过证书校验,用于确认"是不是证书的问题"
curl -k https://example.com # 若 -k 能通而不加不能通 → 确定是证书问题

# 指定自定义/内网 CA
curl --cacert /path/to/internal-ca.crt https://internal.example.com

# 双向认证(mTLS)
curl --cert client.crt --key client.key --cacert ca.crt https://api.example.com

# 检查 HSTS 与其他安全头
curl -sI https://example.com | grep -iE 'strict-transport|content-security|alt-svc'

# 验证 80 端口是否正确跳转
curl -sI http://example.com | grep -E 'HTTP/|Location'

扫描与合规检查

要评估一个站点的密码套件强度、证书概要、以及有没有被误签过证书,用下面这些工具最快。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 枚举支持的协议版本与密码套件,并给出 A~F 评级
nmap --script ssl-enum-ciphers -p 443 example.com

# 查看证书概要
nmap --script ssl-cert -p 443 example.com

# 全面安全评估(含已知漏洞检测)
./testssl.sh https://example.com

# 查询该域名下被签发过的所有证书(CT 日志,用于发现误签/影子资产)
curl -s 'https://crt.sh/?q=%25.example.com&output=json' | jq -r '.[].name_value' | sort -u

Nginx 生产配置模板

这份模板覆盖了 HTTPS 部署的要点:80 端口只做跳转并保留 ACME 验证路径、443 必须配 fullchain、开启 OCSP Stapling、发 HSTS 头。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 80 端口:仅做跳转,同时保留 ACME 验证路径
server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on; # Nginx 1.25.1+ 写法
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # 必须 fullchain
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; # OCSP Stapling 校验用

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;

    location / { proxy_pass http://backend;
                 proxy_set_header Host $host;
                 proxy_set_header X-Real-IP $remote_addr;
                 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                 proxy_set_header X-Forwarded-Proto $scheme; } # 缺这行会导致重定向循环
}

Let’s Encrypt 自动签发与续期

证书到期导致全站不可用,是 HTTPS 最常见的事故。用 certbot 把签发和续期自动化,是根治之道。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 首次签发(Nginx 插件自动改配置)
certbot --nginx -d example.com -d www.example.com

# 仅签发不改配置
certbot certonly --webroot -w /var/www/certbot -d example.com

# DNS-01 验证(申请通配符证书的唯一方式)
certbot certonly --manual --preferred-challenges dns -d '*.example.com' -d example.com

# 测试续期流程(不真正续期,用于验证自动化是否可用)
certbot renew --dry-run

# 查看已有证书与到期时间
certbot certificates

# 续期后自动重载 Nginx
certbot renew --deploy-hook "systemctl reload nginx"

常见故障与排错

下面每条先给"结论”,再给排查动作——结论先行,排错时先判断是证书问题、数据连接问题还是 HTTP 层问题。

  • NET::ERR_CERT_DATE_INVALID

    • 先记住结论:证书过期了,或者客户端系统时间错了(虚拟机/嵌入式设备时间漂移很常见)。
    • 排查:openssl x509 -noout -dates;同时检查客户端 date
  • NET::ERR_CERT_COMMON_NAME_INVALID

    • 先记住结论:你访问的域名不在证书 SAN 里,或者客户端没发 SNI 命中了默认站点。
    • 排查:openssl x509 -ext subjectAltName;确认服务器上是否有多个 server 块与 default_server
  • NET::ERR_CERT_AUTHORITY_INVALID / unable to get local issuer certificate

    • 先记住结论:证书链没下发全(配了 cert.pem 而非 fullchain.pem),或用了自签/内网 CA。
    • 排查:openssl s_client -showcerts 数返回了几张证书;改用 fullchain;内网需向客户端分发根 CA。
  • 浏览器能访问,curl/移动端/老 Java 报错

    • 先记住结论:浏览器有"AIA 自动补链"能力掩盖了链不全,其他客户端没有。
    • 排查:以 openssl s_client -showcerts 的结果为准,不要以浏览器为准。
  • 混合内容(Mixed Content)

    • 先记住结论:HTTPS 页面里引用了 http:// 的 JS/CSS(被直接阻断)或图片(标记不安全)。
    • 排查:浏览器控制台会明确列出 URL;全站搜索硬编码 http://;临时加 CSP: upgrade-insecure-requests
  • 重定向循环 ERR_TOO_MANY_REDIRECTS

    • 先记住结论:反代没透传 X-Forwarded-Proto,后端以为是 HTTP 又发 301。
    • 排查:加 proxy_set_header X-Forwarded-Proto $scheme;框架侧配置信任代理。
  • 80 端口跳转后 ACME 续期失败

    • 先记住结论:把 /.well-known/acme-challenge/ 也 301 走了,或该路径未放行。
    • 排查:在 80 的 server 块中把该 location 排除在跳转之外(见上文模板)。
  • SSL_ERROR_NO_CYPHER_OVERLAP / handshake_failure

    • 先记住结论:服务器关了 TLS 1.0/1.1 而客户端过老,或密码套件无交集。
    • 排查:nmap --script ssl-enum-ciphers;分别用 -tls1_2 / -tls1_3 测试。
  • 一个 IP 上多个域名,访问其中一个总是拿到另一个的证书

    • 先记住结论:客户端不支持 SNI(老 Android/WinXP),或 Nginx server_name 未匹配。
    • 排查:抓包确认 ClientHello 是否含 SNI;为老客户端准备独立 IP 或多域名 SAN 证书。
  • 首字节延迟明显变高

    • 先记住结论:每次都完整握手(会话复用没生效),或没开 OCSP Stapling 导致客户端外联查询。
    • 排查:curl -w time_appconnect 对比首次与后续;-status 检查 Stapling。
  • mTLS 客户端证书不被接受

    • 先记住结论:客户端证书过期、EKU 缺 clientAuth、或签发 CA 不在服务端信任列表。
    • 排查:抓包看是否有 CertificateRequest;检查 Nginx ssl_client_certificatessl_verify_client
  • 证书续期后仍报旧证书

    • 先记住结论:服务没重载,或多进程/多机部署只更新了一台,或 CDN 侧没同步。
    • 排查:systemctl reload nginx--resolve 逐台校验;检查 CDN 控制台证书。
  • CDN 回源报证书错误

    • 先记住结论:回源用 IP 或内网域名,与源站证书 SAN 不符。
    • 排查:回源配置 Host 头与 SNI;或对回源关闭证书校验(内网链路)并用私有 CA。
  • 内网自签证书全员报警告

    • 先记住结论:根 CA 没分发到终端信任库。
    • 排查:通过组策略/MDM 推送根 CA;或改用内部 ACME + 私有 CA 体系。

通用排查顺序curl -k 能否通(区分证书问题与服务问题)openssl s_client 看握手在哪步断看 Alert 编号检查证书链张数与 SAN核对系统时间确认 SNI看是否 HTTP 层问题(重定向/混合内容)

与其他协议对比

HTTPS vs HTTP

维度HTTPHTTPS
URI 方案http://https://
默认端口TCP 80TCP 443(HTTP/3 为 UDP 443)
加密❌ 全明文✅ TLS AEAD 加密
完整性保护❌ 可被任意篡改注入✅ 认证标签校验
服务器身份认证❌ 无✅ X.509 证书 + CA 信任链
建连开销TCP 1-RTTTCP 1-RTT + TLS 1-RTT(1.3;恢复可 0-RTT)
CPU 开销握手非对称运算 + 数据对称加密(AES-NI 下几乎可忽略)
浏览器标识地址栏标"不安全”正常显示
高级 Web API❌ 大多不可用✅ Service Worker、地理位置、摄像头、Web Push 等
HTTP/2 / HTTP/3浏览器不支持明文 h2c✅ 支持并常反超 HTTP/1.1 性能
SEO降权加分
运维成本证书申请、续期、监控(ACME 已大幅自动化)
语义 / 方法 / 状态码完全相同完全相同

HTTPS vs 其他加密方案

维度HTTPSSSH 隧道IPSec VPNWireGuard
层次应用层(TLS 之上)应用层网络层网络层
保护对象单个 Web 服务的连接转发的特定端口两点之间全部 IP 流量同 IPSec
身份认证CA 签发的公信证书主机指纹 + 用户密钥PSK / 证书 / EAP静态公钥
面向公众✅ 天然适合(信任由 CA 体系提供)❌ 需预先交换指纹❌ 需预配置❌ 需预配置
穿 NAT需 NAT-T
部署难度低(ACME 自动化)

速查表 / 常见面试题

关键数字与文件速查

项目
HTTPS 端口TCP 443;HTTP/3 UDP 443
定义 https URI 的 RFCRFC 9110 §4.2.2(已废止 RFC 2818)
域名校验依据证书 SAN 扩展(CN 已废弃)
服务器必须下发fullchain = 叶子 + 中间 CA(根不发)
Let’s Encrypt 证书有效期90 天(建议 60 天自动续期)
公共 CA 最长有效期398 天
HSTS 推荐值max-age=31536000; includeSubDomains
TLS 1.3 握手 RTT1-RTT(恢复 0-RTT)
常见错误码ERR_CERT_DATE_INVALID / ERR_CERT_COMMON_NAME_INVALID / ERR_CERT_AUTHORITY_INVALID

常见面试题

  1. HTTPS 与 HTTP 的区别是什么? HTTPS 就是 HTTP over TLS:语义、方法、状态码、首部完全相同,区别在于端口(443 vs 80)、URI 方案,以及在 TCP 之上插入了一层 TLS 提供加密、完整性与服务器身份认证。

  2. HTTPS 的加密过程能简述一下吗? 混合加密:TLS 握手阶段用 ECDHE 协商出共享秘密(服务器用证书私钥签名证明身份),派生出对称密钥;之后所有 HTTP 报文用 AES-GCM 等 AEAD 算法加密传输。非对称解决"如何在不安全信道上安全地约定密钥",对称解决"如何高速加密大量数据"。

  3. HTTPS 一定安全吗?有哪些残留风险? 不一定。① SNI 与 DNS 仍明文,访问目标会泄露;② 包长与时序可被流量分析;③ 客户端信任库被植入企业/恶意根 CA 后可被合法解密;④ 证书配置错误(弱算法、过期、链不全);⑤ 应用层漏洞(XSS、SQL 注入)HTTPS 完全防不住;⑥ 用户点击"继续访问"忽略警告(HSTS 可封堵此路)。

  4. 为什么必须配置 fullchain? 客户端信任库里只有根证书。要验证叶子证书,必须能构建 叶子 → 中间 → 根 的完整链,而中间证书只能由服务器下发。浏览器有 AIA 补链能力可能掩盖问题,但 curl、Java、移动端 SDK 通常没有,会直接报错。

  5. HSTS 解决什么问题?有什么风险? 解决 SSLStrip 降级攻击与"用户手输 http 被劫持"的窗口期:浏览器在本地就把 http 改写为 https,且不允许忽略证书错误。风险是配置 includeSubDomains 后所有子域必须有有效证书,preload 一旦提交极难撤回,可能造成站点长期不可访问。

  6. 中间人为什么无法解密 HTTPS?企业防火墙又是怎么做到的? 中间人拿不到目标域名的受信任证书私钥,无法通过客户端的证书校验。企业中间盒的做法是在终端信任库中预置企业根 CA,然后为每个访问的域名实时签发证书——这在技术上是"被授权的中间人",也是为什么在公司网络里查看证书会看到 Issuer 是公司名。

  7. 通配符证书 *.example.com 能覆盖哪些域名? 只能覆盖一级子域,如 www.example.comapi.example.com不覆盖 example.com 本身(需在 SAN 中单独列出),也不覆盖 a.b.example.com

  8. HTTPS 会让网站变慢吗? 单看握手是多了 1 个 RTT 和一些 CPU 开销。但实际部署中,HTTPS 解锁了 HTTP/2 / HTTP/3(多路复用、首部压缩、0-RTT 恢复),配合会话复用与 AES-NI 硬件加速,整体性能通常优于明文 HTTP/1.1。

  9. 从 HTTP 迁移到 HTTPS 的关键步骤? 申请并部署 fullchain 证书 → 验证 443 可用 → 80 端口 301 跳转(保留 ACME 验证路径)→ 治理混合内容 → Cookie 加 Secure/SameSite → 反代透传 X-Forwarded-Proto → 灰度上线 HSTS → 配置证书自动续期与到期监控。

  10. 证书到期导致全站不可用,如何预防? ① 使用 ACME 自动续期并配 --deploy-hook 重载服务;② 监控系统对所有域名做 checkend 巡检(提前 30 天告警);③ 多机/CDN 部署确保证书同步;④ 定期执行 certbot renew --dry-run 验证自动化链路本身是否健康。