3.2 虚拟主机:一台服务器的多重身份


文档摘要

3.2 虚拟主机:一台服务器的多重身份 本节摘要:虚拟主机让一台 httpd 同时伺候几十上百个网站。本节讲三种形态——按域名、按端口、按 IP——各自的判定依据与配置写法,重点拆解基于域名的匹配优先级与默认站点兜底逻辑,以及 HTTPS 时代 SNI 如何让"一个 IP 多张证书"成为可能。读完你能独立配置多站点并解释"敲 IP 为什么打开了个不相干的网站"。 学习目标 阅读完本节,你应当能够: 写出基于域名的虚拟主机完整配置并解释每个字段; 说明匹配优先级与默认站点的兜底行为; 配置基于端口与基于 IP 的形态并说出各自适用场景; 解释 SNI 的工作前提与常见证书错配故障; 用最少停机时间在单机上加一个新站点。 三个问题:找谁、按什么找、找不到给谁 虚拟主机机制回答三个问题。

3.2 虚拟主机:一台服务器的多重身份

本节摘要:虚拟主机让一台 httpd 同时伺候几十上百个网站。本节讲三种形态——按域名、按端口、按 IP——各自的判定依据与配置写法,重点拆解基于域名的匹配优先级与默认站点兜底逻辑,以及 HTTPS 时代 SNI 如何让"一个 IP 多张证书"成为可能。读完你能独立配置多站点并解释"敲 IP 为什么打开了个不相干的网站"。

学习目标

阅读完本节,你应当能够:

  1. 写出基于域名的虚拟主机完整配置并解释每个字段;
  2. 说明匹配优先级与默认站点的兜底行为;
  3. 配置基于端口与基于 IP 的形态并说出各自适用场景;
  4. 解释 SNI 的工作前提与常见证书错配故障;
  5. 用最少停机时间在单机上加一个新站点。

三个问题:找谁、按什么找、找不到给谁

虚拟主机机制回答三个问题。找谁:请求头里的 Host 字段是唯一身份凭证。按什么找:配置里的每个 VirtualHost 容器声明自己认领的地址端口组合与 ServerName(必要时加 ServerAlias)。找不到给谁:该地址端口上第一个定义的虚拟主机充当默认站点,接收所有没人认领的请求——这就是"直接敲服务器 IP 会打开一个不相干网站"的原因:IP 访问没有 Host 头(或 Host 是 IP),无人认领,落到默认站点。

理解了第三个问题,你就理解了一个安全惯例:默认站点应该配置成拒绝或返回一个不含内容的占位页,而不是让它"顺便"展示某个真实站点:

# 第一个虚拟主机 = 默认站点,专门用来"接垃圾" <VirtualHost *:80> ServerName default.invalid <Location /> Require all denied </Location> </VirtualHost> # 真实站点从第二个开始 <VirtualHost *:80> ServerName www.example.com ServerAlias example.com DocumentRoot /var/www/example ErrorLog ${APACHE_LOG_DIR}/example-error.log CustomLog ${APACHE_LOG_DIR}/example-access.log combined </VirtualHost>

基于域名:绝对主流形态

上面就是基于域名的形态,关键点逐个说:

  • VirtualHost *:80:星号表示"任意本机地址"的 80 端口。多网卡机器想绑定特定 IP 时写具体地址;
  • ServerName 是匹配键,ServerAlias 兜住带 www 与不带 www 的变体,一个虚拟主机可以挂多个别名;
  • DocumentRoot 决定旅程第五站映射的起点
  • 每个站点独立的日志文件是后期排错与统计的地基,别让所有站点混写一份日志。

上线前验证匹配(不动 DNS 的本地复现):

apachectl -t -D DUMP_VHOSTS

输出片段:

*:80 default.invalid *:80 www.example.com example.com

列表顺序即优先级顺序,第一个就是默认站点。确认无误后 reload,再用 curl 伪造 Host 头做端到端验证。

新站点上线流程压缩成四步:建目录与权限 → 写虚拟主机片段 → a2ensite(或放入 conf.d)→ apachectl -t && apachectl graceful。全程 graceful,线上已有连接不断。

基于端口与基于 IP:小众但有真实场景

基于端口:同一域名不同端口对应不同站点。8443 跑管理后台、8080 跑测试环境是常见用法。注意两点:必须先 Listen 8080 声明监听;用户侧要显式敲端口,不适合对外业务,适合内部工具。

基于 IP:机器有多个 IP,每个 IP 一个站点,靠目标 IP 而非 Host 判定。它比基于域名更"老",价值在于协议不合作也能区分——某些老客户端不发 Host 头,或需要在 IP 层做隔离(不同安全级别的站点绑不同网卡)。IPv4 地址枯竭后,公网场景基本被基于域名加 SNI 取代,内网多网卡环境偶有使用。

图 3-2 虚拟主机匹配决策树

图 3-2 虚拟主机匹配决策树

HTTPS 时代:SNI 与证书配对

虚拟主机判定的传统依据是 HTTP 层的 Host 头,但 HTTPS 请求进来时,TLS 握手发生在 HTTP 之前——服务器必须先出示证书,才能解密看到 Host。那一个 IP 上多个 HTTPS 站点怎么各自出示正确的证书?

答案是 SNI(服务器名称指示):现代客户端在 TLS 握手的第一条消息里,以明文扩展字段带上目标域名。服务器据此挑选对应虚拟主机的证书完成握手。配置形态:

Listen 443 <VirtualHost *:443> ServerName shop.example.com DocumentRoot /var/www/shop SSLEngine on SSLCertificateFile /etc/ssl/shop/fullchain.pem SSLCertificateKeyFile /etc/ssl/shop/privkey.pem </VirtualHost> <VirtualHost *:443> ServerName blog.example.com DocumentRoot /var/www/blog SSLEngine on SSLCertificateFile /etc/ssl/blog/fullchain.pem SSLCertificateKeyFile /etc/ssl/blog/privkey.pem </VirtualHost>

SNI 的前提是客户端与服务端双向支持(现代浏览器与 2.4 都默认支持,只剩极老的 Windows XP 时代的客户端不支持)。不支持 SNI 的客户端会拿到默认站点的证书,于是出现"证书域名不匹配"警告。

由此衍生一个高频故障:证书与虚拟主机没配对。症状是浏览器访问 A 站弹出 B 站的证书告警。排查顺序:DUMP_VHOSTS 确认站点被认领 → 检查证书文件路径是否张冠李戴 → 用 openssl 直连看实际出示的证书:

echo | openssl s_client -connect 127.0.0.1:443 -servername blog.example.com 2>/dev/null | grep subject # 预期输出 subject=CN = blog.example.com,若出现别的域名即配对失败

-servername 参数就是在模拟 SNI,这个命令等于"扮演浏览器复现握手"。

**注意一个高频坑:虚拟主机改了配置不 reload,或 reload 前忘了 -t 检查——语法错误的配置 graceful 会失败,但失败信息只在终端与错误日志里,"改了没生效"的第一嫌疑永远是自己没重启成功。

规模化:几十个站点怎么管

单机站点数一多,三个工程问题会浮出来。重复:几十个站点配置 90% 相同,用宏模块(mod_macro)定义模板,参数化域名与路径,改一处全站生效。命名:站点文件名与 ServerName 保持一致,排错时文件名即域名。日志切分:每站独立日志已就位,配合第 6 章的日志轮转,避免单文件无限膨胀。

另一个方向的问题是"何时该分机器":单机虚拟主机再多,MPM 的进程与内存也是所有站点共享的水池——一个大流量站点打满水池,邻居全部遭殃。隔离需求(一个站点拖不垮别人)比容量需求更早触发拆分。

本节要点回顾

  • 匹配三问:按 Host 找、逐个 ServerName 与 Alias 比对、无人认领落第一个默认站点;
  • 默认站点的姿势:拒绝或占位,不兜真实业务;
  • 三种形态:域名主流、端口内部工具、IP 老客户端与网络隔离;
  • SNI 机制:TLS 握手时明文带域名,服务器据此选证书,一个 IP 多证书的前提;
  • 证书配对排查:DUMP_VHOSTS → 证书路径 → openssl 带 servername 复现握手;
  • 规模化:模板化、命名一致、独立日志,隔离需求早于容量需求触发拆机。

💡 一句话记住本节:虚拟主机的全部逻辑就是一场认领——谁报上 Host,谁就拿到工单;没人报,第一个站点被迫接锅。

下一节进入旅程第五站的岔路口:重写引擎如何改变请求的行进方向。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U