TLS 把一段能被随手读懂的网络通信,变成经过身份验证的加密通道。难点很少在加密算法本身,而在于证明域名对得上、把完整证书链发出去,以及在某个不起眼的日期变成一场故障之前完成续期。
🎙️ 发布并录制于: ·
纯 HTTP 既不保密,也不能证明服务器身份。链路上的任何一方都能读到请求、偷走 session cookie、改掉下载的 JavaScript,或者把一次 API 调用引到别处。「这页没有密码输入框」不是继续用 HTTP 的理由。被改过的脚本可以一直等,等到用户在别的地方输入密码。
# HTTP: readable and modifiable on the path
GET /account HTTP/1.1
Host: example.com
Cookie: session=secret-value
# HTTPS: HTTP travels inside a TLS-protected connection
https://example.com/account先把 80 端口重定向到 HTTPS,等所有必需的子域名都能走 HTTPS 之后再加 HSTS。承载 session 的 cookie 需要 Secure、HttpOnly,还有合适的 SameSite 策略。TLS 保护的是传输中的数据。它修不了有漏洞的应用、恶意的接收端,也救不了已经泄露的服务器私钥。
在 HTTP 开始之前,客户端先报出自己支持的协议版本、加密套件,以及想访问的域名。服务器挑一组双方都支持的参数,发回自己的证书链,并证明自己持有对应的私钥。客户端校验证书链和域名。两边各自算出临时会话密钥,之后应用数据才开始加密。
client server
|--- ClientHello ------------>| versions, cipher suites, SNI
|<-- ServerHello -------------| chosen parameters
|<-- Certificate chain -------| site cert + intermediates
|<-- CertificateVerify -------| proof of private key
|--- Finished --------------->|
|<-- Finished ----------------|
|=== encrypted HTTP =========>|现代 TLS 用的是临时密钥协商,所以即使以后证书私钥被偷,也不该能解开早先抓到的会话。别照着一篇老博客手动拼奇怪的 cipher 列表。用维护良好的服务器或平台自带的现代 TLS 策略,关掉过时的协议版本,把注意力放在证书、私钥的访问控制和续期上。
一张站点证书里有公钥、有效期、签发者,还有 Subject Alternative Name。请求的域名必须出现在这些名字里面。服务器通常会同时发出自己的叶子证书和中间证书,客户端再把这条链接到本地信任库里已有的根证书上。根证书一般不该由服务器发出。
# Inspect names, issuer, and dates from the live service
openssl s_client -connect example.com:443 -servername example.com \
-showcerts </dev/null
# Inspect one saved certificate
openssl x509 -in cert.pem -noout -subject -issuer -dates \
-ext subjectAltName证书没覆盖请求的域名时,Chrome 会报 NET::ERR_CERT_COMMON_NAME_INVALID。一:确认浏览器地址栏里的域名,子域名也要看清。二:用上面的命令检查 Subject Alternative Name。三:签一张包含这个确切域名的证书。四:把它装到真正处理这个域名的虚拟主机上。*.example.com 的通配符能覆盖 api.example.com,但覆盖不了 example.com,也覆盖不了 v2.api.example.com。
一个 IP 上常常挂着很多个 HTTPS 站点。服务器必须在读到加密的 HTTP Host 请求头之前就选好证书。Server Name Indication 的做法是把请求的域名放进 ClientHello。如果 SNI 缺失,或者服务器上的映射配错了,你拿到的可能是默认站点的证书。
# Correct: connect to the IP but send SNI for the hostname
openssl s_client -connect 203.0.113.10:443 \
-servername api.example.com </dev/null
# Test the same routing with curl while preserving hostname
curl -v --resolve api.example.com:443:203.0.113.10 \
https://api.example.com/health直接打开 https://203.0.113.10,是在要求一张对这个 IP 有效的证书,而且很可能命中默认虚拟主机。那复现不了用户访问 api.example.com 的过程。用 --resolve 或 -servername,这样既绕开 DNS,又能让真实域名留在 SNI 和证书校验里。
ACME 让证书颁发机构自动确认你确实控制某个域名,然后签发一张有效期很短的证书。HTTP-01 把一个 token 放在约定好的 HTTP 路径下。DNS-01 发布一条 TXT 记录,通配符证书必须用它。TLS-ALPN-01 则靠 443 端口上一次特殊的 TLS 响应来证明控制权。
# HTTP-01 must be publicly reachable at this exact path
http://example.com/.well-known/acme-challenge/TOKEN
# DNS-01 publishes a temporary TXT value
_acme-challenge.example.com. TXT "challenge-value"
# Check public DNS before blaming the ACME client
dig +short TXT _acme-challenge.example.com客户端可能报 unauthorized: Invalid response from http://example.com/.well-known/acme-challenge/...。一:从服务器外面去请求那个确切的 challenge 地址。二:确认 DNS 指向的正是应答 challenge 的那台机器。三:把这个路径从登录跳转和应用重写规则里排除掉。四:为 HTTP-01 放通入站 80 端口,或者换成 DNS-01,并给一套权限收得很窄的 DNS 凭据。
反向代理常常持有公网证书,再把请求转发给应用。这样续期和现代 TLS 配置就集中在一处。但应用仍然要知道原始请求是 HTTPS,而且只能信任来自已知代理的转发请求头。如果代理到应用这一跳要跨过不可信网络,这一段也得加密。
# Minimal nginx shape
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}如果 TLS 终止之后浏览器报 ERR_TOO_MANY_REDIRECTS,很可能是应用以为每个转发来的请求都是 HTTP。一:在应用侧打印 X-Forwarded-Proto。二:在代理上把它设成原始协议。三:配置应用的可信代理列表,别图省事写成「谁都信」。四:整条链路上只留一个负责 HTTP 到 HTTPS 跳转的地方。
普通 HTTPS 只验证服务器。mTLS 还会向客户端索要证书,适合服务间调用、设备接入,以及管控严格的合作方 API。它给出的传输层身份很强,但运维成本不低。你得签发、轮换、吊销并映射客户端身份,还不能一不小心把所有调用方一起挡在门外。
# Call an mTLS endpoint
curl --cert client.crt --key client.key \
--cacert service-ca.crt https://internal.example.com/health
# nginx client verification
ssl_client_certificate /etc/ssl/client-ca.pem;
ssl_verify_client on;
ssl_verify_depth 2;别拿同一张客户端证书给一百个服务共用。给每个工作负载一个独立身份,出事时才能只吊销一个,日志里也能看清是谁在调用。证书校验通过之后,应用层授权照样要做。一张有效证书能证明「我是库存服务」,但这个服务能不能取消订单,仍然由应用说了算。
先拿到出问题的那个客户端使用的确切域名和端口。要检查线上服务,而不是你以为已经部署上去的那个证书文件。看 SNI、Subject Alternative Name、有效期、服务器实际发出的中间证书,还有客户端时钟。然后比对信任库:浏览器和容器完全可能给出不同结论,因为它们带的根证书不一样。
# Fast HTTP and certificate trace
curl -Iv https://api.example.com/
# Verify what the server actually presents
openssl s_client -connect api.example.com:443 \
-servername api.example.com -verify_return_error </dev/null
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate遇到 unable to get local issuer certificate,一:对线上主机跑 s_client -showcerts。二:确认服务器先发叶子证书,再发必需的中间证书。三:在做 TLS 终止的那一层装上 CA 给的完整链文件,然后重载。四:如果链是完整的、只是客户端信任库太旧,就更新客户端的 CA bundle。别把 verify=False 或 curl -k 带上线,这两个开关关掉的正是 TLS 本该提供的身份校验。
自动签发还不够。续期必须真的跑起来,把新文件放到运行中的进程会去读的位置,并让那个进程重载。监控要盯从外部观察到的证书,而不只是 cron 退出码为零。还有几个月余量的时候就去测续期。第一次失败应该表现成一条告警,而不是客户发来的截图。
# Show expiry from the public endpoint
echo | openssl s_client -connect example.com:443 \
-servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# Generic post-renewal checks
acme-client renew
nginx -t && systemctl reload nginx
curl -fsS https://example.com/health客户端可能显示 certificate has expired。一:查公网端点的 notAfter 日期和服务器时钟。二:续期或重新签发证书。三:把新的完整链和私钥部署到真正做 TLS 终止的那一层。四:重载它,再从外部检查一次公网端点。五:在过期之前就告警;续期之后如果观察到的证书序列号没变,也要告警。
下面这些命令能在不关掉校验的前提下,回答第一时间最常见的那些问题。
# inspect live TLS with correct SNI
openssl s_client -connect HOST:443 -servername HOST -showcerts
curl -Iv https://HOST/
# inspect certificate file
openssl x509 -in cert.pem -noout -subject -issuer -dates \
-ext subjectAltName
# diagnose by symptom
wrong hostname → URL + SAN + SNI + virtual-host mapping
unknown issuer → served intermediates + client CA store
expired → public notAfter + renewal + reload
ACME failure → DNS + challenge path + reachable port
# rules worth keeping
serve leaf + intermediates · protect private key · automate renewal
monitor from outside · never use -k or verify=False in production说得直白一点:出问题的通常不是 TLS 库,而是部署。为请求的域名提供正确的证书,把中间证书链带上,让续期和重载自动完成,再从你自己网络之外测一次这个端点。校验失败时,去修域名、证书链、日期或信任库。关掉证书校验从来不是解决办法,它只是把一个能查的错误,换成了一次悄无声息的冒充。