HTTPS 证书问题排查(SSL/TLS)实战

前言

💡 痛点:浏览器显示"您的连接不是私密连接"?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 问题编写。如有问题欢迎评论区讨论!

相关推荐
zt1985q17 小时前
本地部署源代码管理解决方案 Bitbucket Data Center 并实现外部访问
运维·服务器·数据库·网络协议·postgresql·源代码管理
二营长118 小时前
后端请求https协议接口地址报错
网络协议·http·https
提伯斯64621 小时前
Jetson_Pixhawk局域网UDP连接QGC
linux·网络·嵌入式硬件·网络协议·udp·jetson
hoho_1221 小时前
目标主机使用了不受支持的SSL加密算法【原理扫描】
网络·网络协议·ssl
天启HTTP1 天前
多开账号时,如何避免网络环境暴露异常特征
网络·网络协议·tcp/ip
wapicn991 天前
HTTPS原理详解:从握手到证书链验证,一张SSL证书的完整生命周期
网络协议·https·ssl
bkspiderx1 天前
HTTP协议:Web通信的“通用语言”解析
前端·网络协议·http
2501_915918411 天前
Python如何抓取HTTPS请求包的完整教程与代码示例
android·ios·小程序·https·uni-app·iphone·webview
落叶_Jim1 天前
Chrome提示不安全3步让你的网站变成HTTPS
chrome·安全·https