知识和能力有限,如有不足之处欢迎大家补充,以后多多改进
引子 · 被拒绝的请求
夜已经深了,办公室里只剩下空调的轻响。我正靠着椅背打盹,一旁的 IDE 还开着未保存的代码。鸢鸢推门进来,带着一身夜风,递来一杯还冒着热气的奶茶。
“我刚在测试服务器上调接口,奇了个怪。”她撩起刘海,语气里带着不解,“用域名能访问,用 IP 就报错。”
我迷迷糊糊地睁眼,挠挠脑袋:“不是同一套 Nginx 配的?怎么可能。”
她耸了耸肩,坐到我对面,“要不你看看?”
异常重现 · 同样的请求,不同的结果
我接过她电脑,尝试用 Apipost 发了几次请求。
- 访问
https://api.niuma.com/health:成功。 - 改成
https://123.123.123.123/health:报404 Not Found,有时甚至是 SSL 证书异常。
“这就怪了。”我皱起眉头,“IP 能 ping 通,但就是访问不了接口。”
鸢鸢撑着下巴:“感觉像是被某个 invisible hand 拦住了。”
初步分析 · Nginx 与 Host 的小把戏
这时,阿辰敲着茶杯路过,顺势加入了对话。他听完,微微一笑:“是 Host 头惹的祸吧。”
“啥?”鸢鸢凑过来,“不是发请求都会带 Host 吗?”
阿辰解释:“是的,但关键在于 Host 是谁。”
他打开 Nginx 的配置文件,指着一段代码:
server {
listen 443 ssl;
server_name api.niuma.com;
...
}
“当你用域名访问时,请求里带的是 Host: api.niuma.com,能匹配上 server_name,Nginx 就知道该走哪个配置块。”
一台服务器可通过
server_name配置多个域名,共享同一IP地址。客户端请求时必须通过 HTTPHost头明确指定目标域名,否则服务器无法判断应返回哪个网站内容。
“而你用 IP 访问时,Host 头会变成 Host: 123.123.123.123,除非你配置了 default_server,否则根本不会命中这个虚拟主机。”
当直接使用IP地址访问时,存在致命缺陷:
- 浏览器默认将
Host头设为 IP(如Host: 123.123.123.123)- Nginx 匹配不到任何
server_name,触发默认虚拟主机(default_server)配置- 若未定义默认配置,直接返回
403 Forbidden或404 Not Found
鸢鸢恍然大悟:“所以,要么设置默认虚拟主机,要么别用 IP 访问。这里Nginx 就用了默认的虚拟主机?”
"对啊。"我点点头,"而且你们用的是 HTTPS,证书肯定是绑定域名的,用 IP 访问的话,证书校验肯定过不了。"
更深一层 · HTTPS 的证书陷阱
“对的,这就是另外一个坑。”阿辰补充,“SSL 证书不是绑定 IP 的。”
他在终端敲下一行:
curl https://123.123.123.123/health
错误立刻跳出来:SSL certificate problem: certificate is not valid for '123.123.123.123'
我接着说到:“我们的 SSL 证书是为 api.niuma.com 申请的,客户端用 IP 访问的时候,发现证书的域名和请求目标不匹配,就会拒绝连接。”
“这就是 TLS 的域名验证机制。”阿辰接着说,“生产环境下,当通过 IP 地址访问时,证书的域名与 IP 地址不匹配,导致客户端拒绝连接。这种请求会直接被浏览器或客户端拒绝。”
阿辰在白板上画了个简单的图:"其实这很好理解。你想象一下,你去银行取钱,银行柜员问你身份证,你拿出来一看,身份证上的照片是你,但是名字写的是别人,银行肯定不让你取钱啊。HTTPS 证书就是这个道理,它只认域名,不认 IP。"
“那测试咋整?”鸢鸢嘀咕。
“用 -k 跳过校验,再加 -H 指定 Host。”我说着敲出一行:
curl -k https://123.123.123.123/health -H "Host: api.niuma.com"
请求顺利返回。
阿辰补充道:“不过生产环境不建议这么搞。最好的做法是配置一个默认的虚拟主机,处理那些用 IP 直接访问的请求。”
他走到电脑前,打开了 Nginx 配置文件:
# 默认虚拟主机,处理 IP 直接访问
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
# SSL 配置
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/private.key;
# 重定向到正确的域名
return 301 https://api.niuma.com$request_uri;
}
# 正常的域名虚拟主机
server {
listen 80;
listen 443 ssl;
server_name api.niuma.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/private.key;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
“这样配置的话,用 IP 访问就会自动跳转到域名,既解决了证书问题,也保证了服务的一致性。”阿辰解释道。
我看着配置,觉得思路很清晰:“所以关键是要理解 HTTP 请求的 Host 头机制,还有 HTTPS 证书的域名绑定机制。”
“没错。”鸢鸢站起来伸了个懒腰,“还有一种情况,就是后端服务本身可能也依赖 Host 头做一些路由判断。如果用 IP 访问,缺少了域名信息,可能也会出问题。”
我若有所思:“我明白了。所以我们测试的时候,最好还是用域名,这样更接近真实用户的使用场景。”
“对的。”阿辰点点头,“IP 直接访问在某些调试场景下有用,但是生产环境应该始终通过域名访问。这样既保证了安全性,也避免了各种奇怪的问题。”
觉醒思索 · 真实世界里的坑
我关上显示器,想着刚才学到的知识点。有时候技术问题看起来很复杂,但是理解了背后的原理,解决起来其实并不难。
客户端请求 → 是否包含 Host 头?
↓ 是 ↓ 否
Nginx 匹配虚拟主机 → 是否找到对应配置?
↓ 是 ↓ 否
SSL 证书校验 → 是否通过?
↓ 是 ↓ 否
业务逻辑处理 → 是否依赖域名?
↓ 是 ↓ 否
成功返回响应 ← 结束
问题:Nginx 没有为 IP 地址配置单独的虚拟主机,默认返回空白响应或 404。
解决方案:在 Nginx 配置文件中,为 IP 地址添加默认的
server配置,确保请求有正确的响应。
问题:客户端通过 IP 地址发起 HTTPS 请求时,证书的域名校验失败。
解决方案:在测试时,使用
-k跳过证书校验。在生产时,通过域名访问接口。
问题:后端服务逻辑可能依赖域名,例如使用域名进行路由或权限校验。使用 IP 地址时,缺少相关上下文,导致服务返回错误。
解决方案:确认后端服务逻辑中是否存在对
Host头的依赖,确保使用域名正确路由请求。
结尾 · 灯光与接口之外
夜渐深,灯光投在窗台上,形成一块不规则的斑。问题不大,却藏着系统背后的规则。
“你说……”鸢鸢忽然问,“为什么这些东西都不在文档里写清楚?”
我想了想:“也许有些知识,是必须绕过一次黑夜,才能真正记住。”
阿辰把杯子放回桌上:“那我们也算是,又翻过了一夜的山。”