你在咖啡店连上公共 Wi-Fi,打开一个网站并输入密码。坐在同一网络里的攻击者有机会观察你的流量,他通常能看出你正在和某个 IP 地址通信,有时还能判断你访问了哪个站点,却看不到地址栏后面的完整路径、请求头和密码。
这中间真正起作用的,不是地址栏里那个锁形图标本身,而是浏览器与服务器先完成了一次 TLS 握手。握手成功后,HTTP 请求和响应才进入一条经过加密、完整性保护并验证了服务器身份的通道。我们把运行在这条通道里的 HTTP 称为 HTTPS。
可这句话很容易被理解过头。HTTPS 能阻止网络路径上的人直接偷看或篡改数据,却不能证明网站经营者可信,也不能修复服务器上的越权漏洞。公共 Wi-Fi 仍能看到连接的 IP、时长和流量大小;在没有加密 DNS 或 ECH 的连接中,域名也可能从 DNS 查询或 TLS 的服务器名称信息里暴露出来。
所以这一章不把 HTTPS 简化成“HTTP 加一层加密”。我们要顺着一次真实连接看清楚:哪些信息被锁住了,锁是谁发的,浏览器为什么相信它,以及这把锁在什么时候会失效。

HTTPS 主要给一段传输通道提供三种保证。
第三点里的措辞要很精确。地址栏显示连接安全,意思是“浏览器与证书所代表的域名建立了受保护连接”,不是“这个网站不会诈骗”,也不是“下载的文件一定没有恶意代码”。证书机构通常验证域名控制权,它不会替你审计商家的业务逻辑。
HTTPS 也不保护 TLS 通道两端之外的地方。浏览器扩展可能读取页面,键盘记录器可能在数据加密前拿到密码,服务器会在解密后得到完整请求,服务器日志也可能记录敏感查询参数。如果站点存在 XSS、SQL 注入、越权或弱密码问题,HTTPS 不会自动消除这些漏洞。
还有一个经常被忽略的边界:TLS 可能在 CDN、负载均衡器或反向代理处终止。用户到边缘节点这一段是 HTTPS,不代表边缘节点到应用服务器这一段天然安全。后半段是否再次使用 TLS、由谁验证内部证书、代理能否读取数据,都要由部署架构明确决定。
不要把密码、访问令牌或身份证号放进 URL 查询参数。路径和查询在网络上传输时会被 HTTPS 加密,但它们仍可能出现在浏览器历史、服务器日志、监控系统和错误报告中。
你输入 https://shop.example.com/orders 后,浏览器不会立刻发送 GET /orders。https URL 没有显式端口时默认使用 443。浏览器通常先解析域名并建立到服务器的传输连接:HTTP/1.1 和 HTTP/2 一般使用 TCP,HTTP/3 使用 QUIC。接着,客户端与服务器用 TLS 握手协商算法、建立共享密钥并验证身份。
下面先看最常见的 TLS 1.3 完整握手。为了看清因果关系,我们把它拆成几步;实际报文会把多个字段和消息紧凑地放在一起。
浏览器发送 ClientHello,其中包含自己支持的 TLS 版本、密码套件、密钥交换组、一个临时公钥份额,以及想使用的应用层协议。常见的应用层协议标识有 HTTP/2 的 h2 和 HTTP/1.1 的 http/1.1。
同一个 IP 往往承载多个站点,客户端还会携带目标服务器名称,方便服务器选择正确证书和虚拟主机。传统 SNI 是可见的,所以“页面内容已加密”与“域名一定隐藏”是两件不同的事。支持并成功使用 ECH 时,真实的服务器名称及部分 ClientHello 信息可以得到额外保护;即便如此,目标 IP、连接时序和数据量等元数据仍然存在。
服务器返回 ServerHello,选定 TLS 版本、密码套件和密钥交换参数,并带上自己的临时公钥份额。双方各自使用自己的临时私钥和对方的临时公钥,计算出相同的共享秘密。这个过程通常使用临时椭圆曲线 Diffie-Hellman 密钥协商。
网络上的旁观者能看到两个临时公钥,却算不出共享秘密。ServerHello 完成关键参数选择后,双方就能派生握手密钥;后面的服务器扩展、证书和签名等握手消息会受到加密保护。
服务器随后发送协商后的扩展、证书链、CertificateVerify 和 Finished。这几条消息分别解决不同问题:
CertificateVerify 使用证书对应的私钥给当前握手记录签名,证明对端不是只复制了一张公开证书,而是真的持有私钥。Finished 校验此前握手记录并确认双方派生出了匹配的密钥,攻击者无法在中间偷偷替换协商参数。客户端完成证书验证后也发送自己的 Finished。到这里,客户端与服务器可以使用应用流量密钥传输 HTTP 数据。普通 HTTPS 默认只认证服务器;只有服务器请求客户端证书时,客户端才会发送证书和签名,这就是双向 TLS 的一种用法。

TLS 1.3 的完整握手通常需要一次网络往返,客户端才能安全地发送普通应用数据。高延迟网络里,这次往返仍然能被用户感知;证书验证和密码运算也会消耗 CPU。不过连接复用、会话恢复、硬件加速和现代算法已经把这部分成本压得很低。
如果双方找不到共同的版本、算法或密钥交换组,握手会失败。有时服务器会先发 HelloRetryRequest,要求客户端换一个密钥份额再试,这会增加一次往返。安全配置不是“选最强的单个算法”这么简单,它还要在客户端兼容性、延迟和可维护性之间做选择。
很多入门材料会说:“浏览器用证书公钥加密一个随机密钥,服务器用私钥解开,然后双方开始通信。”这能描述某些旧式 RSA 密钥传输,却不是 TLS 1.3 的工作方式。TLS 1.3 的常见完整握手使用临时密钥协商;证书私钥负责签名认证,不负责逐页解密 HTTP 内容。
这样设计有两个直接后果。
第一,双方不需要在网络上传送最终的对称密钥。它们交换可公开的密钥份额,然后各自在本地计算共享秘密,再通过密钥派生函数生成握手密钥、客户端应用密钥和服务器应用密钥等不同材料。一个方向的密钥泄露,不应被随意拿去充当另一个方向或另一个阶段的密钥。
第二,临时密钥用完就丢。只要临时私钥没有被保留,即使网站的证书私钥以后泄露,攻击者通常也不能拿着过去录下的 TLS 1.3 流量倒推出当时的会话密钥。这叫前向保密。它保护的是历史连接,不能替代眼下的私钥保护和证书撤销。
真正大量加密 HTTP 数据时,双方使用对称的 AEAD 算法,例如 AES-GCM 或 ChaCha20-Poly1305。它们一次完成加密和认证:接收方既能恢复明文,也能发现密文或关联信息是否被篡改。这里不应再把现代 TLS 描述成“每个包额外附一个独立 MAC”的固定结构;具体记录格式和密钥演进由 TLS 版本决定。

假设攻击者控制了公共 Wi-Fi。他可以拦截你发往 shop.example.com 的连接,也可以把自己的公钥发给你。如果浏览器只要求“拿到任意公钥就加密”,你会和攻击者建立一条非常安全的加密通道,然后放心地把密码交给错误的人。
证书要解决的就是“这个公钥到底属于谁”。网站的叶子证书包含公钥、可使用的域名、有效期、用途限制、签发者和签名等信息。服务器一般还会发送一个或多个中间 CA 证书,帮助浏览器把叶子证书连到本地信任库中的根 CA。
浏览器不会因为证书“长得完整”就接受它,至少还要回答这些问题:
api.example.com 的证书不能自然代表 example.net,通配符的覆盖范围也有限。最后一项比“浏览器每次向 CA 发一个 OCSP 请求”复杂。不同客户端会组合使用证书状态装订、CA 发布的吊销列表、浏览器厂商分发的紧急阻断信息或压缩吊销数据;网络失败时是否中止连接也取决于证书和客户端策略。工程上不能把“已申请吊销”误认为所有用户会在同一秒收到阻断。
根 CA 往往是自签名证书。它不是靠自己的签名凭空证明自己可信,而是因为操作系统或浏览器已经通过软件分发和管理策略把它放进信任库。企业代理能检查 HTTPS,通常也是因为受管设备额外信任了企业根证书;代理给目标站点动态签发一张证书,在本机看来就能通过验证。
这也解释了为什么开发环境的自签名证书会报警:加密能力可能完全正常,但浏览器找不到已信任的身份起点。内部系统可以运营自己的私有 CA,并把根证书安全地下发到受管设备;面对不受控的公众客户端,则应使用它们已经信任的证书链。

域名不匹配、证书过期、链缺失、根不受信任或系统时间错误,都可能触发警告。它有时只是运维事故,有时却真的是中间人正在替换证书。应用不能把绕过证书验证当作修复方案;命令行客户端和服务间调用同样应该验证主机名与证书链。
证书固定可以把客户端限制在预期公钥集合里,减少某个受信 CA 错误签发带来的风险,但它也会把正常换证、灾备切换和密钥轮换变成高风险操作。浏览器网站通常依赖公开信任体系和证书透明度机制;原生应用若采用固定策略,也必须预留备用密钥与安全更新路径。

一个站点即使支持 HTTPS,也可能保留 80 端口,把 http://example.com 重定向到 HTTPS。问题出在第一次 HTTP 请求:它还没有进入 TLS,公共 Wi-Fi 上的攻击者可以截住重定向,继续给用户返回伪造的 HTTP 页面。这类降级思路通常被称为 SSL stripping。
HSTS 让站点通过 HTTPS 响应声明:“在一段时间内,这个主机只能使用 HTTPS。”浏览器记住后,即使用户输入 http://,也会在发出网络请求前改用 HTTPS;如果随后遇到无效证书,浏览器也不会允许用户轻易绕过。
一个常见响应头是:
Strict-Transport-Security: max-age=31536000; includeSubDomainsmax-age 表示浏览器记忆规则的秒数,includeSubDomains 把规则扩展到子域。它能减少遗漏,但代价也很直接:任何仍只支持 HTTP、证书不完整或暂时无法续期的子域都会被用户挡在门外。因此,上线时适合先用较短时间验证,再逐步延长;确认所有现有和未来子域都能持续支持 HTTPS 后,才加入 includeSubDomains。
HSTS 头只能在有效 HTTPS 响应中被浏览器接受,否则攻击者可以在 HTTP 中伪造或删除它。于是首次访问仍存在空档。预加载列表可以让浏览器在出厂时就知道某些域名必须使用 HTTPS,但加入之前要确认长期运维能力,因为退出和客户端更新都不会瞬间完成。

生产环境最危险的配置,往往不是算法少写了一个字母,而是证书在凌晨过期、只更新了一台实例、私钥被放进镜像仓库,或者负载均衡器到后端仍在裸奔。HTTPS 部署要围绕“在哪里解密、谁负责续期、失败时谁能发现”来设计。
如果 TLS 在 Nginx、云负载均衡器或 CDN 终止,那里就能看到明文 HTTP,也必须保管私钥。终止点到应用之间可以位于受控内网,也可以再次使用 TLS;安全要求高的内部调用还会使用双向 TLS 验证双方身份。
反向代理传递原始协议和客户端地址时,也要建立明确的信任边界。应用不应无条件相信来自公网的 X-Forwarded-Proto 或 X-Forwarded-For,而应只接受受信代理写入或清洗后的值,否则攻击者可以伪造“这是 HTTPS 请求”等判断依据。
下面的配置展示一个完整的 TLS 终止入口,可以作为当前 Nginx 的站点配置放进 http 上下文。证书路径、域名、上游地址和兼容策略必须按实际环境调整;includeSubDomains 只有在所有子域都准备好时才能保留。较旧版本的 Nginx 启用 HTTP/2 的语法不同,部署前应先用目标版本检查配置。
server {
listen 80;
listen [::]:80;
server_name api.example.com;
return 308 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name api.example.com;
http2 on;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:TLS:20m;
保留 TLS 1.2 常常是兼容旧客户端的现实选择,面向现代客户端的服务应优先协商 TLS 1.3。SSL 2.0、SSL 3.0、TLS 1.0 和 TLS 1.1 都不应继续出现在现代公共入口;日常说的“SSL 证书”多数只是沿用旧称,实际连接使用的是 TLS。不要凭记忆手抄一长串密码套件;服务器软件、密码库和推荐配置会更新,应该基于当前客户端范围生成配置并持续扫描。为了兼容已经停止维护的设备而削弱公共入口,代价通常由所有用户承担;更稳妥的办法是把遗留客户端隔离到权限受限的独立入口。
自动证书管理通常通过 ACME 完成域名控制验证、签发和续期。真正的“自动”至少包括:续期任务定时执行、挑战所需端口或 DNS 权限可用、新证书原子替换、服务平滑重载、失败告警和到期监控。只在日历里记一个续期日,并不算自动化。
私钥应限制读取权限,不应进入 Git、日志或通用镜像。多实例部署要么在统一入口终止 TLS,要么保证每个终止点都能及时得到完整证书链与私钥。更新证书后还要确认进程真的加载了新文件,而不是磁盘上已更新、内存里仍服务旧证书。
下面的脚本可以作为部署后的最小到期检查。它接收主机名和可选端口,验证 SNI 对应的证书是否能建立可信链,并检查证书在未来 14 天内是否会过期。生产监控还应覆盖多个地域和每一个对外域名。
#!/usr/bin/env bash
set -euo pipefail
host="${1:?用法: check-tls.sh <主机名> [端口]}"
port="${2:-443}"
warning_seconds=$((14 * 24 * 60 * 60))
certificate="$({
openssl s_client \
-connect
用户第二次访问同一站点时,如果仍做完整证书握手,会重复一部分工作。TLS 1.3 可以在成功连接后发送会话票据,客户端在后续连接中把它作为预共享密钥身份来请求恢复。服务器接受后,双方从此前建立的秘密继续派生新连接的密钥,不是把旧连接的流量密钥原样拿回来重复使用。
恢复握手通常仍能与新的临时密钥协商结合,从而给新连接的普通应用数据保留前向保密。代价是服务端要安全地管理票据密钥:集群想让任意节点接受票据,就要共享或协调密钥;轮换过快会降低恢复命中率,轮换过慢又会扩大票据密钥泄露的影响范围。
如果客户端手里有合适的恢复信息,它可能在第一个握手报文旁边就发送早期数据,这就是 0-RTT。它减少等待,但早期数据的保证弱于普通 1-RTT 数据:它不具备同等的前向保密,而且攻击者可能复制早期数据,让服务器在另一个连接里再次处理。
因此,“请求用了 HTTPS”不代表它不会被重放。付款、创建订单、修改权限、领取一次性奖励、发送验证码等操作不能直接依赖 TLS 0-RTT 的传输保护来保证只执行一次。服务器应拒绝这类请求的早期数据,或在应用层使用真正的一次性令牌、幂等键和重放检测。就连 GET 也只有在实现确实没有副作用时才适合重放;方法名写成 GET 并不会自动修复错误的业务设计。

HTTPS 描述的是通过受保护连接访问 https 资源,HTTP/1.1、HTTP/2 和 HTTP/3 则定义了 HTTP 消息怎样在连接上传输。它们不是互相替代的一组开关。
HTTP/1.1 和 HTTP/2 通常运行在 TCP 之上,再由 TLS 保护。浏览器访问 HTTPS 时会在 TLS 握手里通过 ALPN 与服务器协商应用协议;双方选中 h2 后才按 HTTP/2 的二进制帧和多路复用规则说话。HTTP/2 标准存在明文连接方式,但现代浏览器的公开 Web 使用几乎都把 HTTP/2 与 HTTPS 结合起来。
HTTP/3 换成了基于 UDP 的 QUIC。QUIC 把 TLS 1.3 握手纳入传输协议,使用 TLS 派生出的密钥保护 QUIC 包;它不是简单地把传统 TLS 记录层再套在 UDP 上。HTTP/3 通过 h3 协商,在 QUIC 的多条流上传输 HTTP 语义。某条流丢包时,其他流不必像共享一条 TCP 字节流那样一起等待缺失字节。
这并不保证 HTTP/3 在所有网络都更快。UDP 可能被企业网络阻断,服务器和中间设备也需要额外支持,客户端通常会在 QUIC 建连失败时退回基于 TCP 的 HTTP/2 或 HTTP/1.1。0-RTT 在 QUIC 中仍然有重放风险,协议升级没有让业务幂等性问题消失。

浏览器显示“无法安全连接”,不一定是证书本身坏了。可以沿着连接建立顺序缩小范围:
下面两条命令分别从 HTTP 客户端和 TLS 层观察连接。curl 适合看重定向、证书结果和协商出的 HTTP 版本;openssl 能显示 SNI 对应的证书链与握手细节。
curl --verbose --head https://api.example.com/health
openssl s_client \
-connect api.example.com:443 \
-servername api.example.com \
-verify_hostname api.example.com \
-verify_return_error \
-showcerts \
</dev/null排查时不要用“关闭证书验证后能访问”作为修复结果。这个测试至多说明网络和应用可能可达,同时也把最重要的身份验证拿掉了。正确做法是继续确认缺的是中间证书、信任根、主机名,还是服务端配置。
回到公共 Wi-Fi 的场景:HTTPS 会把 HTTP 请求和响应变成旁路攻击者无法直接读取、无法静默修改的密文,并用证书把握手密钥绑定到目标域名。它通常不会隐藏目标 IP、流量形态以及所有域名线索,也管不到浏览器和服务器解密后的数据。
TLS 1.3 用临时密钥协商建立共享秘密,用证书私钥的签名证明服务器身份,再用高效的对称 AEAD 算法保护实际流量。HSTS 负责减少用户先落到 HTTP 的降级窗口,自动续期和监控负责让证书不会在某个凌晨突然失效。会话恢复能降低延迟,0-RTT 则把速度换成了更弱的重放保证。
当这些边界都分清后,HTTP/2 的多路复用和 HTTP/3 的 QUIC 就不再是另一套孤立概念:它们是在不同传输结构上继续承载同一套 HTTP 语义,而 TLS 始终负责回答那两个先决问题——这条通道有没有被改过,对面到底是不是我要找的域名。
安全通道建好后,HTTP 才开始真正搬运页面、JSON、图片和文件。下一步要看的,就是这些内容怎样通过类型、长度和编码被描述成接收方能够正确处理的 HTTP 内容。