她说那个网站只能域名访问不能使用IP访问,我说小问题,实际我也不会改……

977 阅读6分钟

知识和能力有限,如有不足之处欢迎大家补充,以后多多改进

引子 · 被拒绝的请求

夜已经深了,办公室里只剩下空调的轻响。我正靠着椅背打盹,一旁的 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地址。客户端请求时必须通过 HTTP Host 头明确指定目标域名,否则服务器无法判断应返回哪个网站内容。

“而你用 IP 访问时,Host 头会变成 Host: 123.123.123.123,除非你配置了 default_server,否则根本不会命中这个虚拟主机。”

当直接使用IP地址访问时,存在致命缺陷

  • 浏览器默认将 Host 头设为 IP(如 Host: 123.123.123.123
  • Nginx 匹配不到任何 server_name,触发默认虚拟主机(default_server)配置
  • 若未定义默认配置,直接返回 403 Forbidden404 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 头的依赖,确保使用域名正确路由请求。


结尾 · 灯光与接口之外

夜渐深,灯光投在窗台上,形成一块不规则的斑。问题不大,却藏着系统背后的规则。

“你说……”鸢鸢忽然问,“为什么这些东西都不在文档里写清楚?”

我想了想:“也许有些知识,是必须绕过一次黑夜,才能真正记住。”

阿辰把杯子放回桌上:“那我们也算是,又翻过了一夜的山。”