前言
💡 痛点:浏览器显示"您的连接不是私密连接"?SSL 证书过期导致服务不可用?证书链不完整被浏览器拦截?不知道如何配置 HTTPS?
🎯 解决方案 :掌握 HTTPS/SSL/TLS 证书问题排查 --- 从证书原理、常见错误、到问题定位与解决。
HTTPS 问题排查流程图:
#mermaid-svg-5k1EYELu2OsSdzPG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5k1EYELu2OsSdzPG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5k1EYELu2OsSdzPG .error-icon{fill:#552222;}#mermaid-svg-5k1EYELu2OsSdzPG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5k1EYELu2OsSdzPG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5k1EYELu2OsSdzPG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5k1EYELu2OsSdzPG .marker.cross{stroke:#333333;}#mermaid-svg-5k1EYELu2OsSdzPG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5k1EYELu2OsSdzPG p{margin:0;}#mermaid-svg-5k1EYELu2OsSdzPG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5k1EYELu2OsSdzPG .cluster-label text{fill:#333;}#mermaid-svg-5k1EYELu2OsSdzPG .cluster-label span{color:#333;}#mermaid-svg-5k1EYELu2OsSdzPG .cluster-label span p{background-color:transparent;}#mermaid-svg-5k1EYELu2OsSdzPG .label text,#mermaid-svg-5k1EYELu2OsSdzPG span{fill:#333;color:#333;}#mermaid-svg-5k1EYELu2OsSdzPG .node rect,#mermaid-svg-5k1EYELu2OsSdzPG .node circle,#mermaid-svg-5k1EYELu2OsSdzPG .node ellipse,#mermaid-svg-5k1EYELu2OsSdzPG .node polygon,#mermaid-svg-5k1EYELu2OsSdzPG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5k1EYELu2OsSdzPG .rough-node .label text,#mermaid-svg-5k1EYELu2OsSdzPG .node .label text,#mermaid-svg-5k1EYELu2OsSdzPG .image-shape .label,#mermaid-svg-5k1EYELu2OsSdzPG .icon-shape .label{text-anchor:middle;}#mermaid-svg-5k1EYELu2OsSdzPG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5k1EYELu2OsSdzPG .rough-node .label,#mermaid-svg-5k1EYELu2OsSdzPG .node .label,#mermaid-svg-5k1EYELu2OsSdzPG .image-shape .label,#mermaid-svg-5k1EYELu2OsSdzPG .icon-shape .label{text-align:center;}#mermaid-svg-5k1EYELu2OsSdzPG .node.clickable{cursor:pointer;}#mermaid-svg-5k1EYELu2OsSdzPG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5k1EYELu2OsSdzPG .arrowheadPath{fill:#333333;}#mermaid-svg-5k1EYELu2OsSdzPG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5k1EYELu2OsSdzPG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5k1EYELu2OsSdzPG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5k1EYELu2OsSdzPG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5k1EYELu2OsSdzPG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5k1EYELu2OsSdzPG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5k1EYELu2OsSdzPG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5k1EYELu2OsSdzPG .cluster text{fill:#333;}#mermaid-svg-5k1EYELu2OsSdzPG .cluster span{color:#333;}#mermaid-svg-5k1EYELu2OsSdzPG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5k1EYELu2OsSdzPG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5k1EYELu2OsSdzPG rect.text{fill:none;stroke-width:0;}#mermaid-svg-5k1EYELu2OsSdzPG .icon-shape,#mermaid-svg-5k1EYELu2OsSdzPG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5k1EYELu2OsSdzPG .icon-shape p,#mermaid-svg-5k1EYELu2OsSdzPG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5k1EYELu2OsSdzPG .icon-shape .label rect,#mermaid-svg-5k1EYELu2OsSdzPG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5k1EYELu2OsSdzPG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5k1EYELu2OsSdzPG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5k1EYELu2OsSdzPG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
否
否
HTTPS 报错
检查证书状态
证书过期?
证书链完整?
域名匹配?
配置正确?
续期证书
补全中间证书
重新签发
修正配置
验证修复
HTTP vs HTTPS:
| 特性 | HTTP | HTTPS |
|---|---|---|
| 端口 | 80 | 443 |
| 加密 | 无 | TLS/SSL |
| 证书 | 不需要 | 需要 |
| 身份验证 | 无 | 服务器身份验证 |
| 数据完整性 | 无 | MAC 验证 |
一、SSL/TLS 证书基础
1.1 证书类型
#mermaid-svg-2fuAhNo3LItH27VM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2fuAhNo3LItH27VM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2fuAhNo3LItH27VM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2fuAhNo3LItH27VM .error-icon{fill:#552222;}#mermaid-svg-2fuAhNo3LItH27VM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2fuAhNo3LItH27VM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2fuAhNo3LItH27VM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2fuAhNo3LItH27VM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2fuAhNo3LItH27VM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2fuAhNo3LItH27VM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2fuAhNo3LItH27VM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2fuAhNo3LItH27VM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2fuAhNo3LItH27VM .marker.cross{stroke:#333333;}#mermaid-svg-2fuAhNo3LItH27VM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2fuAhNo3LItH27VM p{margin:0;}#mermaid-svg-2fuAhNo3LItH27VM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2fuAhNo3LItH27VM .cluster-label text{fill:#333;}#mermaid-svg-2fuAhNo3LItH27VM .cluster-label span{color:#333;}#mermaid-svg-2fuAhNo3LItH27VM .cluster-label span p{background-color:transparent;}#mermaid-svg-2fuAhNo3LItH27VM .label text,#mermaid-svg-2fuAhNo3LItH27VM span{fill:#333;color:#333;}#mermaid-svg-2fuAhNo3LItH27VM .node rect,#mermaid-svg-2fuAhNo3LItH27VM .node circle,#mermaid-svg-2fuAhNo3LItH27VM .node ellipse,#mermaid-svg-2fuAhNo3LItH27VM .node polygon,#mermaid-svg-2fuAhNo3LItH27VM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2fuAhNo3LItH27VM .rough-node .label text,#mermaid-svg-2fuAhNo3LItH27VM .node .label text,#mermaid-svg-2fuAhNo3LItH27VM .image-shape .label,#mermaid-svg-2fuAhNo3LItH27VM .icon-shape .label{text-anchor:middle;}#mermaid-svg-2fuAhNo3LItH27VM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2fuAhNo3LItH27VM .rough-node .label,#mermaid-svg-2fuAhNo3LItH27VM .node .label,#mermaid-svg-2fuAhNo3LItH27VM .image-shape .label,#mermaid-svg-2fuAhNo3LItH27VM .icon-shape .label{text-align:center;}#mermaid-svg-2fuAhNo3LItH27VM .node.clickable{cursor:pointer;}#mermaid-svg-2fuAhNo3LItH27VM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2fuAhNo3LItH27VM .arrowheadPath{fill:#333333;}#mermaid-svg-2fuAhNo3LItH27VM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2fuAhNo3LItH27VM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2fuAhNo3LItH27VM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2fuAhNo3LItH27VM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2fuAhNo3LItH27VM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2fuAhNo3LItH27VM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2fuAhNo3LItH27VM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2fuAhNo3LItH27VM .cluster text{fill:#333;}#mermaid-svg-2fuAhNo3LItH27VM .cluster span{color:#333;}#mermaid-svg-2fuAhNo3LItH27VM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2fuAhNo3LItH27VM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2fuAhNo3LItH27VM rect.text{fill:none;stroke-width:0;}#mermaid-svg-2fuAhNo3LItH27VM .icon-shape,#mermaid-svg-2fuAhNo3LItH27VM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2fuAhNo3LItH27VM .icon-shape p,#mermaid-svg-2fuAhNo3LItH27VM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2fuAhNo3LItH27VM .icon-shape .label rect,#mermaid-svg-2fuAhNo3LItH27VM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2fuAhNo3LItH27VM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2fuAhNo3LItH27VM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2fuAhNo3LItH27VM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SSL 证书
域名验证型 DV
企业验证型 OV
扩展验证型 EV
只验证域名
绿色小锁
签发快
验证企业
显示企业名
需要文件验证
严格企业验证
绿色地址栏
需要律师函
| 类型 | 验证内容 | 显示信息 | 签发时间 |
|---|---|---|---|
| DV | 域名所有权 | 绿色小锁 | 分钟-小时 |
| OV | 域名+企业信息 | 企业名 | 1-3天 |
| EV | 严格企业验证 | 绿色企业名 | 3-7天 |
1.2 证书格式
bash
# ===== 证书格式说明 =====
# 常见格式
# PEM: Base64 编码,-----BEGIN CERTIFICATE-----
# DER: 二进制格式
# PFX/P12: 包含私钥(PKCS#12)
# JKS: Java 密钥库
# CRT: 证书文件(可以是 PEM 或 DER)
# KEY: 私钥文件
# 格式转换
# PEM 转 DER
openssl x509 -in cert.pem -outform DER -out cert.der
# DER 转 PEM
openssl x509 -in cert.der -inform DER -out cert.pem -outform PEM
# PEM 转 PFX
openssl pkcs12 -export -out cert.pfx -inkey privateKey.key -in certificate.crt -certfile cacert.crt
# PFX 转 PEM
openssl pkcs12 -in cert.pfx -out cert.pem -nodes
1.3 证书链
#mermaid-svg-zYro7O87IOC5oO0v{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zYro7O87IOC5oO0v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zYro7O87IOC5oO0v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zYro7O87IOC5oO0v .error-icon{fill:#552222;}#mermaid-svg-zYro7O87IOC5oO0v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zYro7O87IOC5oO0v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zYro7O87IOC5oO0v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zYro7O87IOC5oO0v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zYro7O87IOC5oO0v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zYro7O87IOC5oO0v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zYro7O87IOC5oO0v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zYro7O87IOC5oO0v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zYro7O87IOC5oO0v .marker.cross{stroke:#333333;}#mermaid-svg-zYro7O87IOC5oO0v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zYro7O87IOC5oO0v p{margin:0;}#mermaid-svg-zYro7O87IOC5oO0v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zYro7O87IOC5oO0v .cluster-label text{fill:#333;}#mermaid-svg-zYro7O87IOC5oO0v .cluster-label span{color:#333;}#mermaid-svg-zYro7O87IOC5oO0v .cluster-label span p{background-color:transparent;}#mermaid-svg-zYro7O87IOC5oO0v .label text,#mermaid-svg-zYro7O87IOC5oO0v span{fill:#333;color:#333;}#mermaid-svg-zYro7O87IOC5oO0v .node rect,#mermaid-svg-zYro7O87IOC5oO0v .node circle,#mermaid-svg-zYro7O87IOC5oO0v .node ellipse,#mermaid-svg-zYro7O87IOC5oO0v .node polygon,#mermaid-svg-zYro7O87IOC5oO0v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zYro7O87IOC5oO0v .rough-node .label text,#mermaid-svg-zYro7O87IOC5oO0v .node .label text,#mermaid-svg-zYro7O87IOC5oO0v .image-shape .label,#mermaid-svg-zYro7O87IOC5oO0v .icon-shape .label{text-anchor:middle;}#mermaid-svg-zYro7O87IOC5oO0v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zYro7O87IOC5oO0v .rough-node .label,#mermaid-svg-zYro7O87IOC5oO0v .node .label,#mermaid-svg-zYro7O87IOC5oO0v .image-shape .label,#mermaid-svg-zYro7O87IOC5oO0v .icon-shape .label{text-align:center;}#mermaid-svg-zYro7O87IOC5oO0v .node.clickable{cursor:pointer;}#mermaid-svg-zYro7O87IOC5oO0v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zYro7O87IOC5oO0v .arrowheadPath{fill:#333333;}#mermaid-svg-zYro7O87IOC5oO0v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zYro7O87IOC5oO0v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zYro7O87IOC5oO0v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zYro7O87IOC5oO0v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zYro7O87IOC5oO0v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zYro7O87IOC5oO0v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zYro7O87IOC5oO0v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zYro7O87IOC5oO0v .cluster text{fill:#333;}#mermaid-svg-zYro7O87IOC5oO0v .cluster span{color:#333;}#mermaid-svg-zYro7O87IOC5oO0v div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zYro7O87IOC5oO0v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zYro7O87IOC5oO0v rect.text{fill:none;stroke-width:0;}#mermaid-svg-zYro7O87IOC5oO0v .icon-shape,#mermaid-svg-zYro7O87IOC5oO0v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zYro7O87IOC5oO0v .icon-shape p,#mermaid-svg-zYro7O87IOC5oO0v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zYro7O87IOC5oO0v .icon-shape .label rect,#mermaid-svg-zYro7O87IOC5oO0v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zYro7O87IOC5oO0v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zYro7O87IOC5oO0v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zYro7O87IOC5oO0v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 根证书 CA
中间证书
服务器证书
客户端
验证服务器证书
信任根证书库
bash
# ===== 证书链说明 =====
# 完整证书链
# 根证书 CA(Root CA)- 自签名,内置于操作系统/浏览器
# 中间证书(Intermediate CA)- 由根 CA 签发
# 服务器证书(Server Certificate)- 由中间 CA 签发
# 查看证书链
openssl s_client -connect example.com:443 -showcerts
# 查看证书信息
openssl x509 -in cert.crt -text -noout
# 查看证书有效期
openssl x509 -in cert.crt -noout -dates
# 查看证书主体
openssl x509 -in cert.crt -noout -subject
# 查看证书签发者
openssl x509 -in cert.crt -noout -issuer
二、常见 HTTPS 错误
2.1 证书过期
#mermaid-svg-a6nky11fRmMpBLrJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a6nky11fRmMpBLrJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a6nky11fRmMpBLrJ .error-icon{fill:#552222;}#mermaid-svg-a6nky11fRmMpBLrJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a6nky11fRmMpBLrJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a6nky11fRmMpBLrJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a6nky11fRmMpBLrJ .marker.cross{stroke:#333333;}#mermaid-svg-a6nky11fRmMpBLrJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a6nky11fRmMpBLrJ p{margin:0;}#mermaid-svg-a6nky11fRmMpBLrJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a6nky11fRmMpBLrJ .cluster-label text{fill:#333;}#mermaid-svg-a6nky11fRmMpBLrJ .cluster-label span{color:#333;}#mermaid-svg-a6nky11fRmMpBLrJ .cluster-label span p{background-color:transparent;}#mermaid-svg-a6nky11fRmMpBLrJ .label text,#mermaid-svg-a6nky11fRmMpBLrJ span{fill:#333;color:#333;}#mermaid-svg-a6nky11fRmMpBLrJ .node rect,#mermaid-svg-a6nky11fRmMpBLrJ .node circle,#mermaid-svg-a6nky11fRmMpBLrJ .node ellipse,#mermaid-svg-a6nky11fRmMpBLrJ .node polygon,#mermaid-svg-a6nky11fRmMpBLrJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a6nky11fRmMpBLrJ .rough-node .label text,#mermaid-svg-a6nky11fRmMpBLrJ .node .label text,#mermaid-svg-a6nky11fRmMpBLrJ .image-shape .label,#mermaid-svg-a6nky11fRmMpBLrJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-a6nky11fRmMpBLrJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a6nky11fRmMpBLrJ .rough-node .label,#mermaid-svg-a6nky11fRmMpBLrJ .node .label,#mermaid-svg-a6nky11fRmMpBLrJ .image-shape .label,#mermaid-svg-a6nky11fRmMpBLrJ .icon-shape .label{text-align:center;}#mermaid-svg-a6nky11fRmMpBLrJ .node.clickable{cursor:pointer;}#mermaid-svg-a6nky11fRmMpBLrJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a6nky11fRmMpBLrJ .arrowheadPath{fill:#333333;}#mermaid-svg-a6nky11fRmMpBLrJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a6nky11fRmMpBLrJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a6nky11fRmMpBLrJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a6nky11fRmMpBLrJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a6nky11fRmMpBLrJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a6nky11fRmMpBLrJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a6nky11fRmMpBLrJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a6nky11fRmMpBLrJ .cluster text{fill:#333;}#mermaid-svg-a6nky11fRmMpBLrJ .cluster span{color:#333;}#mermaid-svg-a6nky11fRmMpBLrJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a6nky11fRmMpBLrJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a6nky11fRmMpBLrJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-a6nky11fRmMpBLrJ .icon-shape,#mermaid-svg-a6nky11fRmMpBLrJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a6nky11fRmMpBLrJ .icon-shape p,#mermaid-svg-a6nky11fRmMpBLrJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a6nky11fRmMpBLrJ .icon-shape .label rect,#mermaid-svg-a6nky11fRmMpBLrJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a6nky11fRmMpBLrJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a6nky11fRmMpBLrJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a6nky11fRmMpBLrJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 证书过期
浏览器拦截
APP 不允许
服务不可用
NET::ERR_CERT_DATE_INVALID
您的连接不是私密连接
| 错误码 | 说明 | 原因 |
|---|---|---|
NET::ERR_CERT_DATE_INVALID |
证书日期无效 | 证书过期或时间错误 |
ERR_CERT_EXPIRED |
证书已过期 | 证书过期 |
ERR_CERT_NOT_YET_VALID |
证书尚未生效 | 系统时间错误 |
bash
# ===== 检查证书过期 =====
# 方法 1: OpenSSL
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# 输出示例:
# notBefore=Jan 15 00:00:00 2024 GMT
# notAfter=Jan 15 00:00:00 2025 GMT
# 方法 2: Python
python3 -c "
import ssl, socket
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.create_connection(('example.com', 443), timeout=5)) as s:
cert = s.getpeercert()
print(cert)
"
# 方法 3: 在线工具
# https://www.sslshopper.com/ssl-checker.html
# https://www.digicert.com/help/
# 方法 4: curl
curl -vI https://example.com 2>&1 | grep -E "expire|SSL"
# 方法 5: Go
go run -ldflags "-s -w" << 'EOF'
package main
import (
"crypto/tls"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("https://example.com")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
for _, cert := range resp.TLS.PeerCertificates {
fmt.Printf("Subject: %s\n", cert.Subject)
fmt.Printf("Issuer: %s\n", cert.Issuer)
fmt.Printf("Expiry: %s\n", cert.NotAfter)
}
}
EOF
2.2 证书链不完整
bash
# ===== 证书链不完整问题 =====
# 错误:SSL certificate problem: unable to get local issuer certificate
# 原因:中间证书缺失
# 检查证书链
openssl s_client -connect example.com:443 -showcerts
# 查看证书链长度
echo | openssl s_client -connect example.com:443 2>/dev/null | grep "depth"
# 输出示例(有 3 层):
# depth=2 C = US, O = DigiCert Inc, CN = DigiCert Global Root CA
# depth=1 C = US, O = DigiCert Inc, CN = DigiCert Global G2 TLS RSA SHA256 2020 CA1
# depth=0 C = US, ST = California, L = Los Angeles, O = "Internet Corporation for Assigned Names and Numbers", CN = www.example.org
# 验证证书链
openssl verify -CAfile root.crt -untrusted intermediate.crt server.crt
# 生成完整证书链文件
cat server.crt intermediate.crt root.crt > full_chain.crt
# Nginx 配置使用完整链
# ssl_certificate /path/to/full_chain.crt;
2.3 域名不匹配
bash
# ===== 域名不匹配问题 =====
# 错误:net::ERR_CERT_COMMON_NAME_INVALID
# 原因:证书 CN/SAN 与访问域名不匹配
# 检查证书域名
openssl x509 -in cert.crt -noout -subject -ext subjectAltName
# 输出示例:
# subject=CN = example.com
# X509v3 Subject Alternative Name:
# DNS:example.com, DNS:www.example.com
# 常见问题
# 1. 证书只包含 www.example.com,但访问 example.com
# 2. 多域名证书缺少某个域名
# 3. 通配符证书 *.example.com 不包含 example.com
# 解决方案
# 1. 在 SAN 中添加缺失的域名
# 2. 使用通配符证书
# 3. 重新签发证书
# 检查访问域名
curl -v https://example.com 2>&1 | grep "Server certificate"
2.4 自签名证书
bash
# ===== 自签名证书问题 =====
# 错误:self signed certificate / certificate is not trusted
# 原因:证书不被系统信任
# 方法 1: 导入系统信任库(Linux)
sudo cp cert.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# 方法 2: 导入系统信任库(macOS)
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.crt
# 方法 3: 导入系统信任库(Windows)
# PowerShell
Import-Certificate -FilePath "cert.crt" -CertStoreLocation Cert:\LocalMachine\Root
# 方法 4: Node.js 忽略验证(仅开发环境)
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
# 方法 5: Python requests 忽略验证
import requests
requests.get('https://example.com', verify=False)
# 方法 6: curl 忽略验证
curl -k https://example.com
# 方法 7: Java 导入证书
keytool -importcert -trustcacerts -file cert.crt -keystore "$JAVA_HOME/lib/security/cacerts" -alias "mycert"
2.5 混合内容问题
#mermaid-svg-YKt8I7Gnv4q4FaDK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YKt8I7Gnv4q4FaDK .error-icon{fill:#552222;}#mermaid-svg-YKt8I7Gnv4q4FaDK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YKt8I7Gnv4q4FaDK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .marker.cross{stroke:#333333;}#mermaid-svg-YKt8I7Gnv4q4FaDK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YKt8I7Gnv4q4FaDK p{margin:0;}#mermaid-svg-YKt8I7Gnv4q4FaDK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .cluster-label text{fill:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .cluster-label span{color:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .cluster-label span p{background-color:transparent;}#mermaid-svg-YKt8I7Gnv4q4FaDK .label text,#mermaid-svg-YKt8I7Gnv4q4FaDK span{fill:#333;color:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .node rect,#mermaid-svg-YKt8I7Gnv4q4FaDK .node circle,#mermaid-svg-YKt8I7Gnv4q4FaDK .node ellipse,#mermaid-svg-YKt8I7Gnv4q4FaDK .node polygon,#mermaid-svg-YKt8I7Gnv4q4FaDK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .rough-node .label text,#mermaid-svg-YKt8I7Gnv4q4FaDK .node .label text,#mermaid-svg-YKt8I7Gnv4q4FaDK .image-shape .label,#mermaid-svg-YKt8I7Gnv4q4FaDK .icon-shape .label{text-anchor:middle;}#mermaid-svg-YKt8I7Gnv4q4FaDK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .rough-node .label,#mermaid-svg-YKt8I7Gnv4q4FaDK .node .label,#mermaid-svg-YKt8I7Gnv4q4FaDK .image-shape .label,#mermaid-svg-YKt8I7Gnv4q4FaDK .icon-shape .label{text-align:center;}#mermaid-svg-YKt8I7Gnv4q4FaDK .node.clickable{cursor:pointer;}#mermaid-svg-YKt8I7Gnv4q4FaDK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .arrowheadPath{fill:#333333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YKt8I7Gnv4q4FaDK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YKt8I7Gnv4q4FaDK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YKt8I7Gnv4q4FaDK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YKt8I7Gnv4q4FaDK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .cluster text{fill:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK .cluster span{color:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YKt8I7Gnv4q4FaDK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YKt8I7Gnv4q4FaDK rect.text{fill:none;stroke-width:0;}#mermaid-svg-YKt8I7Gnv4q4FaDK .icon-shape,#mermaid-svg-YKt8I7Gnv4q4FaDK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YKt8I7Gnv4q4FaDK .icon-shape p,#mermaid-svg-YKt8I7Gnv4q4FaDK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YKt8I7Gnv4q4FaDK .icon-shape .label rect,#mermaid-svg-YKt8I7Gnv4q4FaDK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YKt8I7Gnv4q4FaDK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YKt8I7Gnv4q4FaDK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YKt8I7Gnv4q4FaDK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTPS 页面
HTTP 资源
浏览器阻止
HTTP 资源
CSS/JS
图片
iframe
| 错误 | 说明 | 浏览器行为 |
|---|---|---|
Mixed Content |
HTTPS 页面加载 HTTP 资源 | 降级为不安全 |
Blocked loading mixed active content |
HTTP JS/CSS 被阻止 | 部分阻止 |
Displayed: Yes |
HTTP 图片可显示 | 警告 |
html
<!-- ===== 混合内容修复 ===== -->
<!-- 错误:HTTP 资源 -->
<script src="http://example.com/jquery.js"></script>
<link href="http://example.com/style.css" rel="stylesheet">
<img src="http://example.com/logo.png">
<!-- 正确:HTTPS 资源 -->
<script src="https://example.com/jquery.js"></script>
<link href="https://example.com/style.css" rel="stylesheet">
<img src="https://example.com/logo.png">
<!-- 最佳:协议相对 URL -->
<script src="//example.com/jquery.js"></script>
<!-- 最佳:HSTS 强制跳转 -->
<!-- 在响应头中添加: -->
<!-- Strict-Transport-Security: max-age=31536000; includeSubDomains -->
三、证书部署实战
3.1 Nginx 配置
nginx
# ===== Nginx HTTPS 配置 =====
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# 证书文件
ssl_certificate /etc/ssl/certs/full_chain.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# SSL 协议版本
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';
# 优先使用服务器加密套件
ssl_prefer_server_ciphers on;
# SSL 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 证书链(如果证书文件不包含中间证书)
# ssl_trusted_certificate /etc/ssl/certs/ca-bundle.crt;
root /var/www/html;
index index.html;
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
3.2 Apache 配置
apache
# ===== Apache HTTPS 配置 =====
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
# 证书配置
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCertificateChainFile /etc/ssl/certs/ca-bundle.crt
# SSL 协议版本
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
# 加密套件
SSLCipherSuite 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
SSLHonorCipherOrder on
# HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
Header always set X-XSS-Protection "1; mode=block"
DocumentRoot /var/www/html
DirectoryIndex index.html
</VirtualHost>
# HTTP 重定向
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>
3.3 Docker 配置
yaml
# ===== Docker Compose HTTPS 配置 =====
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
restart: unless-stopped
# 或者使用 Caddy 自动 HTTPS
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./site:/srv:ro
restart: unless-stopped
caddy
# ===== Caddyfile 自动 HTTPS =====
example.com {
root * /srv
encode gzip
# 自动 HTTPS(Let's Encrypt)
# Caddy 自动申请和续期证书
# 或使用自有证书
# tls /etc/ssl/certs/full_chain.crt /etc/ssl/private/server.key
file_server
# PHP-FPM 反向代理
@php {
path *.php
}
reverse_proxy @php php:9000
}
3.4 Kubernetes Ingress 配置
yaml
# ===== Kubernetes TLS 配置 =====
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
# Nginx Ingress
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
tls:
- hosts:
- example.com
- www.example.com
secretName: my-app-tls-secret
---
# 创建 TLS Secret
apiVersion: v1
kind: Secret
metadata:
name: my-app-tls-secret
namespace: default
type: kubernetes.io/tls
data:
# base64 编码的证书
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
# base64 编码的私钥
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
四、证书自动化
4.1 Let's Encrypt + Certbot
bash
# ===== Certbot 自动证书 =====
# 安装 Certbot(Ubuntu/Debian)
sudo apt update
sudo apt install certbot python3-certbot-nginx
# 安装 Certbot(CentOS/RHEL)
sudo yum install epel-release
sudo yum install certbot python3-certbot-nginx
# 申请证书(Nginx)
sudo certbot --nginx -d example.com -d www.example.com
# 申请证书(Apache)
sudo certbot --apache -d example.com -d www.example.com
# 申请证书(仅获取证书,不修改配置)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
# 申请证书(standalone 模式,停止现有服务)
sudo certbot certonly --standalone -d example.com -d www.example.com
# 查看证书
sudo certbot certificates
# 证书位置
# /etc/letsencrypt/live/example.com/fullchain.pem # 完整链
# /etc/letsencrypt/live/example.com/privkey.pem # 私钥
# /etc/letsencrypt/live/example.com/cert.pem # 服务器证书
# /etc/letsencrypt/live/example.com/chain.pem # 中间证书
# 续期证书
sudo certbot renew
# 测试续期
sudo certbot renew --dry-run
# 自动续期(systemd timer)
sudo systemctl status certbot.timer
# 手动创建续期任务
crontab -e
# 0 0,12 * * * certbot renew --quiet
4.2 ACME 自动签发
bash
# ===== ACME 协议自动签发 =====
# ACME (Automated Certificate Management Environment)
# Let's Encrypt 使用 ACME v2
# 安装 acme.sh
curl https://get.acme.sh | sh -s email=my@example.com
# 申请证书(DNS 验证)
~/.acme.sh/acme.sh --issue -d example.com -d www.example.com --dns dns_cf
# dns_cf = Cloudflare
# dns_dp = DNSPod
# dns_ali = Alibaba Cloud
# dns_aws = AWS Route53
# 申请证书(HTTP 验证)
~/.acme.sh/acme.sh --issue -d example.com -d www.example.com --webroot /var/www/html
# 安装证书到 Nginx
~/.acme.sh/acme.sh --install-cert -d example.com \
--key-file /etc/ssl/private/example.com.key \
--fullchain-file /etc/ssl/certs/full_chain.crt \
--reloadcmd "systemctl reload nginx"
# 查看证书
~/.acme.sh/acme.sh --list
# 续期
~/.acme.sh/acme.sh --renew -d example.com
# 强制续期
~/.acme.sh/acme.sh --renew -d example.com --force
# 自动续期(crontab)
crontab -e
# 0 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
4.3 证书监控告警
python
# ===== 证书过期监控脚本 =====
import smtplib
import ssl
from email.mime.text import MIMEText
from datetime import datetime, timedelta
import socket
def check_cert_expiry(hostname, port=443, warning_days=30):
"""检查证书过期时间"""
try:
context = ssl.create_default_context()
with socket.create_connection((hostname, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
not_after = cert['notAfter']
# 解析日期
expiry = datetime.strptime(not_after, '%b %d %H:%M:%S %Y %Z')
now = datetime.utcnow()
days_left = (expiry - now).days
return {
'hostname': hostname,
'days_left': days_left,
'expiry_date': expiry,
'warning': days_left <= warning_days,
'expired': days_left < 0
}
except Exception as e:
return {
'hostname': hostname,
'error': str(e),
'warning': True,
'expired': True
}
def send_alert(email_config, subject, message):
"""发送告警邮件"""
msg = MIMEText(message, 'plain', 'utf-8')
msg['Subject'] = subject
msg['From'] = email_config['from']
msg['To'] = email_config['to']
with smtplib.SMTP(email_config['smtp_host'], email_config['smtp_port']) as server:
server.starttls()
server.login(email_config['username'], email_config['password'])
server.send_message(msg)
def monitor_certs(hosts, email_config):
"""监控证书"""
results = []
expired = []
warning = []
for host in hosts:
result = check_cert_expiry(host)
results.append(result)
if result.get('expired'):
expired.append(result)
elif result.get('warning'):
warning.append(result)
# 发送告警
if expired or warning:
message = "证书状态告警\n\n"
if expired:
message += "【已过期】\n"
for r in expired:
if 'error' in r:
message += f"- {r['hostname']}: {r['error']}\n"
else:
message += f"- {r['hostname']}: 已过期 {abs(r['days_left'])} 天\n"
if warning:
message += "\n【即将过期】\n"
for r in warning:
message += f"- {r['hostname']}: {r['days_left']} 天后过期\n"
subject = "证书告警"
if expired:
subject = "【紧急】证书已过期!"
elif warning:
subject = "【警告】证书即将过期"
send_alert(email_config, subject, message)
return results
# 使用示例
if __name__ == '__main__':
hosts = [
'example.com',
'api.example.com',
'www.example.com',
]
email_config = {
'smtp_host': 'smtp.gmail.com',
'smtp_port': 587,
'username': 'alert@example.com',
'password': 'your-password',
'from': 'alert@example.com',
'to': 'admin@example.com'
}
results = monitor_certs(hosts, email_config)
for r in results:
status = "✓" if r.get('days_left', 0) > 30 else "⚠"
if 'error' in r:
print(f"{r['hostname']}: ❌ {r['error']}")
else:
print(f"{r['hostname']}: {status} {r['days_left']} 天")
五、问题排查工具
5.1 命令行工具
bash
# ===== SSL/TLS 检测工具 =====
# 1. OpenSSL 检测
openssl s_client -connect example.com:443
# 检测支持的协议
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# 检测 cipher 套件
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'
# 2. SSL Labs 在线检测
# https://www.ssllabs.com/ssltest/
# 3. curl 检测
curl -vI https://example.com
curl --cacert ca-bundle.crt https://example.com
curl --cert cert.pem --key key.pem https://example.com
# 4. Python 检测
python3 -c "
import ssl, socket
ctx = ssl.create_default_context()
with ctx.wrap_socket(socket.create_connection(('example.com', 443), timeout=5)) as s:
cert = s.getpeercert(binary_form=True)
print('Certificate:', cert[:50])
"
# 5. Go 检测
go run -ldflags "-s -w" << 'EOF'
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
},
}
client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
resp, err := client.Get("https://example.com")
if err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
for _, cert := range resp.TLS.PeerCertificates {
fmt.Printf("Subject: %s\n", cert.Subject)
fmt.Printf("Issuer: %s\n", cert.Issuer)
fmt.Printf("Expiry: %s\n", cert.NotAfter)
}
io.Copy(os.Stdout, resp.Body)
}
EOF
5.2 在线检测工具
bash
# ===== 在线 SSL 检测工具 =====
# 1. SSL Labs
# https://www.ssllabs.com/ssltest/analyze.html
# 检测内容:协议支持、cipher 套件、证书链、配置评分
# 2. SSL Shopper
# https://www.sslshopper.com/ssl-checker.html
# 检测内容:证书有效期、证书链、域名匹配
# 3. Digicert
# https://www.digicert.com/help/
# 检测内容:证书详情、配置问题
# 4. SSL Decoder
# https://ssldecoder.org/
# 检测内容:证书解析、配置分析
# 5. HTTPS.watch
# https://https.watch/
# 检测内容:HSTS、HTTPS 使用情况
5.3 浏览器开发者工具
javascript
// ===== Chrome 开发者工具检测 =====
// 1. Security 面板
// DevTools -> Security
// 2. 查看证书详情
// Security -> View certificate
// 3. 查看证书链
// Security -> Certificate -> View all
// 4. Console 查看混合内容
// Console -> 查看黄色警告 "Mixed Content"
// 5. Network 面板筛选 HTTPS
// Network -> filter: "protocol:https"
// 6. 查看请求头
// Network -> 选择请求 -> Headers -> Response Headers
// 查找:Strict-Transport-Security
5.4 Prometheus 监控
yaml
# ===== Prometheus SSL 监控 =====
# blackbox_exporter 配置
modules:
http_2xx:
prober: https
insecure_skip_verify: false
# prometheus.yml
scrape_configs:
- job_name: 'ssl-cert-check'
metrics_path: /probe
static_configs:
- targets:
- target: https://example.com
- target: https://api.example.com
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: localhost:9115
# 告警规则
groups:
- name: ssl-alerts
rules:
- alert: SSLCertExpiring
expr: probe_ssl_cert_not_after - time() < 86400 * 30
for: 0m
labels:
severity: warning
annotations:
summary: "证书即将过期"
description: "{{ $labels.instance }} 证书将在 {{ $value | humanizeDuration }} 后过期"
- alert: SSLCertExpired
expr: probe_ssl_cert_not_after - time() < 0
for: 0m
labels:
severity: critical
annotations:
summary: "证书已过期"
description: "{{ $labels.instance }} 证书已过期"
- alert: SSLChainIncomplete
expr: probe_ssl_chain_length < 2
for: 0m
labels:
severity: warning
annotations:
summary: "证书链不完整"
description: "{{ $labels.instance }} 证书链只有 {{ $value }} 层"
六、生产案例
6.1 案例:Let's Encrypt 证书自动续期失败
bash
# ===== 案例:Certbot 续期失败 =====
# 问题:证书过期了,但自动续期没有执行
# 1. 检查 cron 服务
systemctl status cron
# 或
systemctl status systemd-timers
# 2. 检查续期日志
cat /var/log/letsencrypt/letsencrypt.log
# 3. 手动续期测试
certbot renew --dry-run
# 4. 常见失败原因
# - Nginx/Apache 正在运行,占用 80 端口
# - DNS 记录变更,验证失败
# - 磁盘空间不足
# - 权限问题
# 5. 解决方案
# 停止 web 服务
systemctl stop nginx
certbot renew --force-renewal
systemctl start nginx
# 6. 检查续期 crontab
crontab -l
# 0 0,12 * * * certbot renew --quiet
# 7. 设置正确的权限
chmod 755 /etc/letsencrypt
chmod 755 /var/www/html/.well-known/acme-challenge
6.2 案例:多域名证书少了一个域名
bash
# ===== 案例:证书域名不匹配 =====
# 问题:example.com 可以访问,但 api.example.com 报错
# 1. 检查证书域名
openssl x509 -in /etc/ssl/certs/server.crt -noout -text | grep -A1 "Subject Alternative Name"
# 输出:
# X509v3 Subject Alternative Name:
# DNS:example.com, DNS:www.example.com
# 注意:没有 api.example.com
# 2. 解决方案:重新签发包含所有域名
certbot certonly --webroot -w /var/www/html \
-d example.com \
-d www.example.com \
-d api.example.com
# 或添加新域名到现有证书
certbot certonly --webroot -w /var/www/html \
-d example.com \
-d www.example.com \
-d api.example.com \
--expand
# 3. 更新 Nginx 配置
# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 4. 重新加载
nginx -t && nginx -s reload
6.3 案例:自签名证书 APP 不信任
bash
# ===== 案例:APP 不信任自签名证书 =====
# 问题:iOS/Android APP 无法信任自签名证书
# 方案 1:将证书添加到系统信任库(不推荐生产)
# iOS: 设置 -> 通用 -> 关于手机 -> 证书信任设置
# Android: 设置 -> 安全 -> 加密与凭据 -> 安装证书
# 方案 2:使用企业 CA 签发
# 1. 搭建企业内部 CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IT/CN=MyCompany Root CA"
# 2. 用 CA 签发服务器证书
openssl genrsa -out server.key 2048
openssl req -new -key server.key \
-out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=IT/CN=example.com"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 365 -sha256 \
-extfile v3.ext
# 3. 在 APP 中内置 CA 证书
# iOS: 将 ca.crt 放入 App Bundle,并在代码中信任
# Android: 将 ca.crt 放入 raw 资源,使用 KeyStore 加载
# 方案 3:使用 Charles/Fiddler 抓包(开发环境)
# 1. 安装 Charles 根证书到手机
# 2. 开启 SSL Proxying
# 3. APP 信任 Charles 证书
# 方案 4:使用 OkHttp 忽略验证(开发环境)
val client = OkHttpClient.Builder()
.sslSocketFactory(createSSLSocketFactory(), trustManager)
.hostnameVerifier { _, _ -> true }
.build()
// 不推荐用于生产环境!
七、总结
7.1 排查流程
#mermaid-svg-uoomYgm7Dx8jg6wO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uoomYgm7Dx8jg6wO .error-icon{fill:#552222;}#mermaid-svg-uoomYgm7Dx8jg6wO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uoomYgm7Dx8jg6wO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uoomYgm7Dx8jg6wO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uoomYgm7Dx8jg6wO .marker.cross{stroke:#333333;}#mermaid-svg-uoomYgm7Dx8jg6wO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uoomYgm7Dx8jg6wO p{margin:0;}#mermaid-svg-uoomYgm7Dx8jg6wO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO .cluster-label text{fill:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO .cluster-label span{color:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO .cluster-label span p{background-color:transparent;}#mermaid-svg-uoomYgm7Dx8jg6wO .label text,#mermaid-svg-uoomYgm7Dx8jg6wO span{fill:#333;color:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO .node rect,#mermaid-svg-uoomYgm7Dx8jg6wO .node circle,#mermaid-svg-uoomYgm7Dx8jg6wO .node ellipse,#mermaid-svg-uoomYgm7Dx8jg6wO .node polygon,#mermaid-svg-uoomYgm7Dx8jg6wO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uoomYgm7Dx8jg6wO .rough-node .label text,#mermaid-svg-uoomYgm7Dx8jg6wO .node .label text,#mermaid-svg-uoomYgm7Dx8jg6wO .image-shape .label,#mermaid-svg-uoomYgm7Dx8jg6wO .icon-shape .label{text-anchor:middle;}#mermaid-svg-uoomYgm7Dx8jg6wO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uoomYgm7Dx8jg6wO .rough-node .label,#mermaid-svg-uoomYgm7Dx8jg6wO .node .label,#mermaid-svg-uoomYgm7Dx8jg6wO .image-shape .label,#mermaid-svg-uoomYgm7Dx8jg6wO .icon-shape .label{text-align:center;}#mermaid-svg-uoomYgm7Dx8jg6wO .node.clickable{cursor:pointer;}#mermaid-svg-uoomYgm7Dx8jg6wO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uoomYgm7Dx8jg6wO .arrowheadPath{fill:#333333;}#mermaid-svg-uoomYgm7Dx8jg6wO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uoomYgm7Dx8jg6wO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uoomYgm7Dx8jg6wO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uoomYgm7Dx8jg6wO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uoomYgm7Dx8jg6wO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uoomYgm7Dx8jg6wO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uoomYgm7Dx8jg6wO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uoomYgm7Dx8jg6wO .cluster text{fill:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO .cluster span{color:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uoomYgm7Dx8jg6wO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uoomYgm7Dx8jg6wO rect.text{fill:none;stroke-width:0;}#mermaid-svg-uoomYgm7Dx8jg6wO .icon-shape,#mermaid-svg-uoomYgm7Dx8jg6wO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uoomYgm7Dx8jg6wO .icon-shape p,#mermaid-svg-uoomYgm7Dx8jg6wO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uoomYgm7Dx8jg6wO .icon-shape .label rect,#mermaid-svg-uoomYgm7Dx8jg6wO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uoomYgm7Dx8jg6wO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uoomYgm7Dx8jg6wO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uoomYgm7Dx8jg6wO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
否
是
否
是
HTTPS 报错
检查证书有效期
是否过期
续期证书
检查证书链
链完整?
补全中间证书
检查域名
域名匹配?
重新签发
检查配置
修正配置
验证修复
7.2 常见错误速查
| 错误码 | 说明 | 解决 |
|---|---|---|
ERR_CERT_DATE_INVALID |
证书过期 | 续期证书 |
ERR_CERT_COMMON_NAME_INVALID |
域名不匹配 | 添加域名到 SAN |
ERR_CERT_AUTHORITY_INVALID |
证书链不完整 | 添加中间证书 |
ERR_CERT_REVOKED |
证书被吊销 | 重新签发 |
ERR_SSL_PROTOCOL_ERROR |
协议不支持 | 升级 TLS 版本 |
NET::ERR_CERT_WEAK_SIGNATURE |
弱签名算法 | 使用强加密 |
7.3 配置检查清单
| 检查项 | 说明 | 命令 |
|---|---|---|
| 证书有效期 | > 30 天 | openssl x509 -noout -dates |
| 证书链完整 | depth >= 2 | openssl s_client -showcerts |
| 域名匹配 | SAN 包含访问域名 | openssl x509 -noout -text |
| TLS 版本 | >= TLS 1.2 | openssl s_client -tls1_2 |
| Cipher 套件 | 使用强加密 | SSL Labs 检测 |
| HSTS 头 | 已配置 | 检查响应头 |
7.4 最佳实践
| 实践 | 说明 |
|---|---|
| 使用 TLS 1.3 | 最新协议,更快更安全 |
| 配置 HSTS | 强制 HTTPS |
| 添加 HPKP | 公钥固定(可选) |
| 自动化续期 | Certbot/ACME |
| 监控告警 | 证书过期前 30 天预警 |
| 使用 EV/OV | 生产环境优先 |
| 定期检测 | SSL Labs 评分 A+ |
本文基于常见 HTTPS 问题编写。如有问题欢迎评论区讨论!