
做Web开发和运维这些年我几乎每周都能在群里看到同一个问题HTTP和HTTPS到底有什么区别大多数人能背出一句“一个加密一个不加密”但真要解释清楚为什么HTTPS抓包抓不到明文、为什么首次访问HTTPS比HTTP慢、为什么明明配了证书浏览器还是报错能说明白的人就少很多了。这篇是这个系列的第八篇我会从协议栈、握手过程、性能、证书体系一直讲到实际部署和排障尽量把这两个协议里里外外讲透。适合刚接触前后端的开发也适合正在做HTTPS迁移的运维和后端同学。1. 表面差异很多人能说出来但“S”到底代表什么1.1 端口、URL与协议标识的一一对应先建立第一层直觉。HTTP协议前面的标识是http://HTTPS是https://默认端口分别是80和443。注意这个“S”不是某个英文单词的大写缩写它代表的不是多了一个字母而是HTTP over TLS/SSL也就是把HTTP报文放进TLS安全通道里传输。严谨叫法是HTTP over TLS旧文档里也常写成HTTP over SSL因为SSL是TLS的前身大家现在都习惯说SSL/TLS。端口这个细节值得多说一句。如果一个HTTPS服务没有跑在443端口上URL里就必须显式写出端口号比如https://192.168.1.20:8443/。很多内网管理系统把HTTP服务跑在8080、7080这类端口上日志里常见http://内网IP:7080/cas/login?...这样的地址其实就说明这是一个非标准端口的明文HTTP入口。对外正规站点一般不这么干因为有HTTPS就有证书、有专业端口这是信任体系的一部分。我习惯用下面这张表快速向新人讲差别对比项HTTPHTTPS协议全称HyperText Transfer ProtocolHTTP over TLS/SSL默认端口80443URL前缀http://https://数据传输形式明文TLS加密后的密文服务器身份认证无依赖CA证书校验数据完整性校验无有消息认证码/标签主流标准文档RFC 9110等RFC 2818HTTP over TLS1.2 抓包视角明文HTTP和只看到密文的HTTPS第二个直观区别来自抓包。用Wireshark或tcpdump在链路上抓一次Windows下用HTTP访问普通网站你能直接看到请求行、Host头、Cookie、GET参数POST表单里的账号密码几乎就是这样裸奔在网络里。有人可能会说“都2025年了谁还用明文HTTP”但现实很骨感很多老旧系统、物联网设备、内网后台仍然跑在HTTP上这也是为什么总有新闻说“数据在传输过程中被截获”。换成HTTPS之后同样用Wireshark抓包TCP负载里已经看不到“GET /path HTTP/1.1”这种文本取而代之的是一段一段的TLS记录应用层数据被加密成难以辨认的字节流。能看到的只有连接目的IP、端口以及TLS握手阶段的一些明文参数比如协议版本、加密套件、服务器证书再往下就是加密的密文了。这里必须辟一个常见误区有人看到“HTTPS明文捕获”这种说法以为HTTPS可以直接抓到明文。真实情况是普通抓包工具只能抓到加密后的TLS数据。除非两种情况一是你作为客户端提前信任了一个中间人证书流量被调试工具解密后再重新加密二是服务器私钥泄露。所以“HTTPS能捕获明文”从来不是协议本身的能力而是配置信任链之后的调试手段。你要想在本地复现这个过程就用支持SSLKEYLOGFILE的客户端导出密钥再在Wireshark里导入。2. 协议栈里的排列组合HTTPS不是另一个协议是HTTP套了一层TLS壳2.1 层级关系与“信封装信”模型很多人以为HTTPS是另一个应用层协议这种理解容易造成误解。实际上HTTP本身一点没变方法、状态码、请求头、Cookie、缓存机制通通照旧。HTTPS只是在HTTP和TCP之间插入了一个TLS安全层把原本要裸传给TCP的HTTP报文先交给TLS加密TLS加密后的数据再交给TCP传输。打个比方HTTP是一张明信片TLS是一个带锁的快递箱。HTTP负责把内容写清楚TLS负责把明信片锁进快递箱TCP负责把快递箱运输到目的地。快递运输途中任何人都能看到箱子但看不到里面的明信片内容。接收方用钥匙开箱取出明信片后HTTP又开始正常工作。因为这种层级关系后端的业务代码通常不需要为HTTP和HTTPS做区分。你写一个Spring Boot接口、写一个Nginx转发规则、写一个Go的net/http服务HTTP和HTTPS在应用层看到的报文结构是一致的。差别在哪在于发送端和接收端之间多了一次TLS加密和解密。真正会在代码里感知到差异的是下面几件事重定向协议判断X-Forwarded-Proto、Cookie的Secure属性要不要设置、和各组件协议写死没有。还有一个细节很多人容易忽略TLS加密的是整个HTTP报文不止是请求体。请求行里的URL路径、查询参数、Host头、All headers全都被加密了。这在安全上是好事但也带来一个实际影响如果HTTPS服务挂了跳过TLS去日志里定位请求细节时你只能看到“访问了443端口”看不到具体请求了哪个路径除非在TLS终止层做额外日志记录。2.2 聊聊TLS握手身份认证、密钥协商、完整性校验TLS握手的核心目标通俗讲就是做三件事确认对方确实是自己要通信的人商量出一把双方都知道但对第三人保密的钥匙最后给传输内容打上防篡改的封条。以最常见的TLS 1.2握手为例简化步骤是这样的客户端发出ClientHello带上自己支持的TLS版本、密码套件列表和随机数。服务器回ServerHello选择双方都支持的算法和版本并下发自己的数字证书公钥在证书里。客户端验证证书链是否可信然后生成一个预主密钥用服务器的公钥加密发送过去。双方根据各自的随机数和预主密钥各自算出会话密钥。双方互发Finished确认之后切换到对称加密通信。为什么握手阶段用非对称加密数据传输阶段又换成对称加密因为非对称加密RSA、ECDHE计算量大不适合大量业务数据对称加密AES-GCM、ChaCha20-Poly1305速度快但需要先安全地把密钥分享给对方。握手就是解决“怎么安全地分享密钥”这个问题的。TLS 1.3把这个过程优化得更漂亮正常情况下只需要一次RTT就能完成握手还直接移除了旧的RSA密钥交换和一些不安全算法。对于移动弱网或者跨地区访问的场景这个优化非常明显。我做性能分析时常用openssl s_client -connect 域名:443 -servername 域名这条命令看服务器协商出的版本现在主流服务器基本都能跑到TLS 1.3网上那些“HTTPS慢”的文章很多还停留在2016年TLS 1.2时代的结论早就过时了。3. 性能账本多出的握手真的要多少成本以及怎么抹平3.1 从连接建立到请求响应多出的RTT计算HTTPS比HTTP多出来的最核心成本不是CPU加密开销而是握手带来的网络往返次数。这里引入一个术语RTT也就是数据包从发送方到接收方再返回的延迟比如30ms。距离越远、链路越复杂RTT越大。一次典型的HTTP/1.1请求TCP三次握手需要1个RTT之后客户端发请求、服务器响应通常再加0.5到1个RTT所以从连接建立到收到第一个字节理想情况大约是1.5到2个RTT。HTTPS首次访问在TCP握手之后还要走TLS握手。TLS 1.2流程长完整握手大概需要2个RTTTLS 1.3优化后是1个RTT。所以一次HTTPS首次请求的总成本大约是2到3个RTT比HTTP多了1个RTT的量级。假设RTT是30msHTTP大约45到60msHTTPS大约60到90ms多了二三十毫秒。很多人看到“看一眼网站多几十毫秒”就慌了但要注意几个前提这是首次握手的开销不是每次请求都这么贵而且现在绝大多数服务都开启了连接复用对于普通Web页面几十毫秒的差异远不如图片压缩和缓存策略影响大。真正会爆炸的场景是页面引用了大量不同域名的资源每个域名都要重新握手成本才会叠加起来。3.2 连接复用、TLS会话恢复与HTTP/2的救场实际部署HTTPS时可以通过几个手段把这个成本压到几乎可以忽略。第一个手段是HTTP持久连接即Keep-Alive。HTTP/1.1默认一个TCP连接处理多个请求连接建立后后续请求不再重复TCP握手和TLS握手而是接着用已有连接。只要在一个客户端跟服务器交互过程中连接一直开着第一次握手成本就被摊薄了。第二个手段是TLS会话恢复。服务器可以把会话ID或会话票据缓存下来客户端再次连接时带着票据回来双方直接复用上次协商的密钥不用再走完整握手。TLS 1.3的0-RTT更是允许客户端在首次请求时就带上业务数据当然这是有重放风险的功能适合幂等请求不太建议随便开启。第三个手段是HTTP/2多路复用。一个连接上同时跑几十个并发请求彻底解决了HTTP/1.1的队头阻塞问题。我实测过同一个静态资源站点HTTP/1.1需要开十几个连接才能装满速度切到HTTP/2后一个连接全部跑完延迟反而更稳。所以“HTTPS比HTTP慢”这句话要在后面加一个“首次握手时”。现代协议栈和服务器配置已经把这部分差距压缩得很小用nginx开启HTTP/2和TLS会话缓存后绝大多数业务的HTTPS和HTTP体感差异几乎察觉不到。真正要警惕的不是那1个RTT而是证书链配置错误导致的反复重试和连接重置。4. 证书与信任链HTTPS“可信”的真正来源4.1 没有证书的加密只是给中间人递刀这是全篇最反直觉的一块。很多人的第一反应是“HTTPS加密了所以安全”但加密只是整个安全模型的一半。另一半是身份认证。假设没有证书机制只有加密会怎样攻击者可以在客户端和服务器之间伪装成服务器客户端想连接“bank.com”攻击者替它响应同时攻击者又作为客户端去请求真正的“bank.com”。攻击者和真实服务器之间建立一段HTTPS和受害者之间建立另一段HTTPS两端数据都能解密和重加密而受害者以为自己在跟“bank.com”通信其实是在跟攻击者通信。这就是典型的中间人攻击。所以TLS才需要数字证书服务器必须向客户端证明“我确实是这个域名的合法主机”。证书里包含域名、公钥、有效期还有CA机构的签名。客户端校验流程是看证书里的域名是否和访问的域名一致看有效期是否没过再沿着证书链一路向上查直到找到自己信任的根证书。根证书信任库内置在操作系统或者浏览器里只有链完整且可信客户端才继续握手。这也是为什么自签名证书在公网服务上不被信任。自签名证书技术上也能完成加密但它没有权威CA背书客户端不认识签名者。你可以在测试环境手动导入自签名证书绕过校验但正规对外服务绝对不能用。我这里尤其提醒做内网系统的同学如果你们打算给内部系统上HTTPS建议搭一个内部CA把CA证书统一推送到所有员工电脑而不是每台机器都搞个自签名然后在浏览器点“继续访问”。后者不仅操作繁琐还容易养成“什么都点继续”的坏习惯。4.2 DV/OV/EV证书与部署时最常踩的信任坑证书按验证强度大致分三档DV证书只验证域名所有权几分钟就能签发Lets Encrypt这类免费证书就是DVOV证书会验证企业主体信息适合商业站点EV证书验证更严格历史上浏览器地址栏会显示绿色公司名现在版本浏览器改了UI呈现但EV仍然走更严的审核流程。对于普通Web应用DV证书已经足够满足加密和身份认证需求如果要体现组织身份可信选OV。EV更多是面向金融、政务类高信任场景。我个人的建议是别迷信EV很多团队花大钱上EV但证书管理混乱过期了没人发现反而比免费证书还坑。部署HTTPS最常见的坑在我看来有四个证书过期浏览器报NET::ERR_CERT_DATE_INVALIDcurl报证书有效期错误。防范方法是监控证书剩余天数至少提前30天续期。证书链不完整nginx配置里只挂了叶子证书没挂中间CA证书部分客户端可以解析部分客户端直接报unable to get local issuer certificate。检查方法是用openssl s_client -showcerts完整查看链路。域名不匹配申请的证书只覆盖www.example.com结果用户访问example.com报SSL_ERROR_BAD_CERT_DOMAIN。申请证书时SAN字段要覆盖所有需要的主域名和子域名。TLS版本和算法不兼容老系统的浏览器、嵌入式设备的http库可能只支持TLS 1.0/1.1而服务器已经禁用了这些旧版本导致握手失败。这时候需要评估业务是给老设备单独开兼容端口还是推动设备升级固件。我见过不止一个客户把“无法访问”报成“HTTPS打不开”过来一查是证书过期或者服务器没放行443端口的安全组规则。证书和端口是两回事排查时别混在一起。5. 从HTTP迁到HTTPS的一次完整改造复盘5.1 网关与回源TLS终止在哪一层实际迁移HTTPS第一个要决策的问题是TLS在哪里终止也就是“解密”发生在什么位置。最常见的两种方案方案一网关/负载均衡器终止TLS后端保持HTTP。证书部署在Nginx或者云负载均衡上客户端和网关之间是HTTPS网关把请求解密后用HTTP转发给后端服务。这是绝大多数中小团队的选择。优点是证书集中管理、后端业务代码零改动、性能开销控制在边缘缺点是与后端之间的内网链路是明文在零信任架构下不够好看。方案二全链路HTTPS也就是网关和后端之间也用TLS加密。通常搭配内部CA使用后端每个服务自己管理证书。这种方式更安全但证书管理和服务间调用复杂度都上来了适合对合规要求严格或者跨可用区调用很多的场景。我重点说一下方案一里的常见坑。网关注销TLS后发给后端的请求后端能看到的$_SERVER[HTTPS]或者说X-Forwarded-Proto是从哪来的如果网关没有显式设置X-Forwarded-Proto: https这个头后端会以为请求还是HTTP生成的重定向地址、站内绝对链接可能全是http://然后用户又被指回HTTP入口形成一个非常诡异的循环。我见过一个项目改HTTPS后页面显示正常但登录后跳转一直丢会话排查到最后就是后端的secure cookie和X-Forwarded-Proto配置打架。还有一种日志里很典型的报错unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:某个端口。很多人一看到HTTP就以为是“没加密导致出问题”其实502跟加密关系不大本质是网关或客户端访问上游这个端口时上游服务没起来、超时或者路由错了。排查时先telnet这个地址端口确认连通性再查上游服务进程和监听状态别一上来就折腾TLS配置。5.2 页面资源、重定向与HSTS最容易漏的细节网关证书配好了不等于迁移完成。真正折磨人的是页面资源里的“混合内容”问题。当页面本身是HTTPS加载的页面里却引用了http://的图片、CSS、JS或XHR接口浏览器会拦截或警告。静态图片这类被动资源可能只看到警告脚本和fetch请求这类主动资源会被直接block控制台亮红灯页面上某个区块就是不显示看起来像bug。排查的时候F12看Console往往能看到Mixed Content: The page at https://... was loaded over HTTPS, but requested an insecure resource ... has been blocked。修复办法分两种情况。一是代码里硬编码了http://建议改成协议相对URL//example.com/path或者统一用环境变量管理基础地址。二是靠后端网关层做响应头改写把返回HTML里的http://静态资源URL自动替换成https://。我在改造旧系统时常用后一种省去改一大堆老模板的力气但要注意只替换自己可控的资源别把外链也一起改了。重定向也得站在用户侧体验看。http://example.com应该301跳到https://example.com跳转后URL参数和路径要保持不变。做的时候注意别把云负载均衡的HTTP监听和实例内部的HTTP重定向混在一起否则容易形成http - https - http - https的循环浏览器直接报“重定向过多”。最后是HSTS。配置Strict-Transport-Security响应头让浏览器记住“这个域名只准用HTTPS访问”以后用户再输入http浏览器会自动改写成https省去服务器端重定向的次数。但要在全站确定不用HTTP后再开includeSubDomains更不要随便把域名提交到HSTS preload列表否则一旦你想取消HTTPS支持浏览器那边还会坚持强制安全策略很长时间那种“上了下不来”的滋味很酸爽。6. 排查HTTPS故障的四层军规与几个典型现场6.1 分层查错TCP、TLS、HTTP层逐层定位现在排查协议类问题最重要的原则就是分层。别一上来就看应用日志也别盯着一个现象猜原因。我的排查顺序固定是网络层、TLS层、HTTP层、业务层。第一步确认端口通不通telnet 目标域名 443或nc -vz 目标域名 443。如果不通先查防火墙和安全组。很多时候HTTPS“打不开”根本就没走到TLS这一步端口压根没放行。第二步检查TLS握手本身openssl s_client -connect 目标域名:443 -servername 目标域名 -showcerts。这一步能看清证书链、有效期、协商出的TLS版本。如果报证书相关错误就直接定位到证书问题如果握手在alert handshake failure中断大概率是版本或算法不兼容。第三步用curl做一次真实HTTP请求curl -v https://目标域名/路径看返回的状态码、响应头、是否被重定向。这一步能发现混合内容、HSTS、Cookie安全属性之类应用层问题。第四步再看后端日志。后端日志里往往能看到网关转发的源头IP和实际报错结合业务日志判断是数据库慢还是代码抛异常。常见症状可能原因快速验证connection refused/timeout端口未监听或防火墙拦截telnet/nc测试端口certificate verify failed证书过期/不受信任/域名不匹配openssl s_clienthandshake failureTLS版本或密码套件不兼容抓包看ClientHello/ServerHello502 Bad Gateway上游服务不可用或转发配置错误telnet上游IP/端口查上游进程mixed content页面引用了http资源浏览器Console看network信息empty reply服务端直接断连或端口返回非HTTPcurl -v触发后看服务端日志6.2 真实碰到的几种“学废了”的场景我挑几个真实处理过、且非常容易复现的现场希望能帮有类似问题的读者省点排查时间。场景一前端页面是HTTPS接口调用却是HTTP。这是混合内容里最容易被忽视的。一个Vue项目部署成HTTPS后开发环境的接口地址还写死在http://localhost:8080上线后所有请求全被浏览器拦截。解决思路是用环境变量区分开发/生产API地址并把开发环境的页面也起在HTTPS下或者通过devServer的proxy转发到后端避免页面直接发HTTP请求。场景二网关返回502日志里写着url: http://127.0.0.1:xxxxx。我遇到过好几次都是上游服务没启动或者监听的IP不是127.0.0.1。网关用的本机回环地址但服务实际监听的是0.0.0.0某一个具体网卡网络命名空间不同看起来在同一台机器却连不上。排查时先确认进程监听状态再确认网关和上游配置的地址完全一致。这类问题跟HTTP还是HTTPS没有关系但很多人容易把锅甩给“是不是TLS配置错了”。场景三用JMeter录制HTTPS脚本怎么录都抓不到请求。这里的关键是JMeter作为中间人需要自己的CA证书你要在浏览器或客户端里导入JMeter证书并信任它。录之前记得清理浏览器缓存和旧证书否则可能出现证书信任冲突。原理就是前面说的中间人调试机制证书不信任就看不到明文信任了就全能看到。场景四嵌入式设备通过HTTP库请求服务器报证书校验失败。比如STM32跑的是轻量级TCP协议栈接了一个专用HTTP客户端想把请求升级成HTTPS。这类设备内存小、没有完整CA库最常见做法是把服务器CA证书固话进固件或者用内部根证书签一套设备专用证书。这里有个安全权衡如果设备只访问你自己的专属服务器用固定证书是合理的如果设备需要访问任意公网HTTPS站点就得在当前硬件资源约束下评估引入mbedTLS这类库的成本。IoT设备上的HTTP升级永远是个系统工程不只是在代码里把URL换成https开头。最后一个经验是关于系统时间和证书的。测试环境遇到证书报错先确认设备系统时间是否正确。很多嵌入式板卡没有RTC电池重启后时间回到1970年这会直接导致所有证书校验失败跟证书真过期没两样。我踩过一次排查了半天证书链最后发现是设备时间差了几年。这算是我自己实际运维里最冤的一次经历分享出来给大家绕坑。