一句话

一台服务器、一个 IP、一个端口,靠名字分辨出很多个网站。其中每一个网站的配置,就叫一个 vhost(虚拟主机)。

1. 它解决什么问题

IP 和端口是有限的,机器只有一台。2000 年代之前基本是一个网站一个 IP;现在一台机器要跑几十个站。

分辨靠两个"名字":

  • HTTP(80 端口):看请求头里的 Host
  • HTTPS(443 端口):看 TLS 握手里的 SNI(Server Name Indication,服务端名称指示)—— 加密开始之前就得知道你要找哪个站,才可能给对证书

所以 vhost 的本质是:同一扇门上挂很多块牌子,看牌子决定领你去哪间屋。

2. 长什么样

nginx:一个 server {} 块 = 一个 vhost。

server {
    listen 443 ssl;
    server_name example.com;   # 认领这个名字
    ssl_certificate     /path/fullchain.pem;
    ssl_certificate_key /path/privkey.pem;
    location / { proxy_pass http://127.0.0.1:3000; }
}

宝塔面板:一个域名一个文件,文件名随便,起作用的是里面的 server_name。

/www/server/panel/vhost/nginx/example.com.conf

Apache 里写作 <VirtualHost>,概念完全一样 —— "虚拟主机"这个词就是从 Apache 来的。

3. 匹配顺序(nginx)

server_name 的优先级:

精确名字  >  前缀通配(*.example.com)  >  后缀通配(example.*)  >  正则  >  默认站点

匹配不上不报错。 请求会落到"默认站点"—— 同一个端口上第一个出现的,或者标了 default_server 的那个。这是最容易骗人的地方。

4. 默认站点是最大的坑

没匹配上时,默认站点会安安静静地返回它自己的页面。

宝塔的默认页是「域名未绑定」。这张页面长得特别像"转发没生效",于是人会跑去前端查转发,查半天查不出问题。

配套症状:

现象值
根路径默认站点的占位页
API 路径146 字节的 nginx 404
证书正常(证书是前面那层发的,跟这里无关)

判据:证书对、请求也到了、但内容是占位页 → 先数后端认领了哪些名字,别查转发。

补齐 vhost 后,同一个 API 路径会从 404 变成 401 + JSON。401 才是"找对人了,只是没带钥匙"。

5. 证书和 vhost 是两件事

  • SNI 决定 443 上出哪张证书
  • server_name / Host 决定请求交给哪个 server {}

两者独立出错。所以证书对 ≠ 找对了 vhost:

  • 证书错 → 浏览器报不安全,或客户端直接握手失败
  • vhost 错 → 握手正常,内容是默认站点

前面有转发层时,证书往往是前面那层发的,内容才是后端这台决定的。分开看这两件事,能省很多时间。

6. 怎么查、怎么验

# 这台机器认领了哪些名字
nginx -T | grep server_name
ls /www/server/panel/vhost/nginx/*.conf

# 指定 Host 打本机,绕开 DNS 和所有转发层
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: example.com" http://127.0.0.1/

# 443 上某个 SNI 出哪张证书
echo | openssl s_client -connect 127.0.0.1:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject

第 2 条(-H "Host:" 打 127.0.0.1)是最省事的一步:它只测"这台机器的 vhost 对不对",把 DNS、转发层、证书全部排除在外。

7. 容易踩的地方

  • 前面有转发层时,域名是两关:① 转发层把这个域名转过来 ② 后端有同名 vhost。缺第二关就是第 4 节那个样子。加域名要记得两边都做。
  • 改完必须 reload。宝塔的机器用 nginx -s reload,别用 systemctl reload nginx —— 后者可能返回 0 但不生效。
  • 80 段写 return 301 https://... 是常规做法,但在"前置层终结 TLS 后转发到后端 80 端口"的架构里,它会变成无限跳转。这种场景下 80 段要直接 proxy_pass。
  • server_name 写错不报错,只是静默落默认站。写完拿第 6 节的 curl -H 打一下。

8. 相关


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web