虚拟主机可以是基于 IP 的,即每个网站有独立的 IP 地址;也可以基于域名的,即每个 IP 地址有多个域名在运行。终端用户对于同一物理服务器上运行多个网站的事实是不知情的。

apache 是最早支持多个基于 IP 的虚拟主机的服务器之一。版本 1.1 之后开始同时支持基于 IP 和基于域名的虚拟主机。

基于域名的虚拟主机有时也叫基于主机的,或非 IP 的虚拟主机。

几乎所有配置指令都能在 <VirtualHost> 中使用,除了控制进程创建的以及其它一些指令。具体可以查看指令速查表

基于域名的虚拟主机

每 IP 地址有多个网站。

基于域名与基于 IP 的区别

基于 IP 的虚拟主机使用连接的 IP 地址来确定正确的虚拟主机。因此需要为每个虚拟主机分配单独的 IP 地址。

基于域名的虚拟主机,服务器依赖于客户端在 HTTP 标头中提交的域名。多个不同的虚拟主机可以使用同一个 IP 地址。

基于域名的虚拟主机通常更简单,因为只需要配置 DNS 服务器把各个域名映射到正确的 IP 地址,然后再配置 apache,令其可以识别不同的虚拟主机就可以了。基于域名的虚拟主机减少了对 IP 地址的需求,因此如果设备不是显式需要基于 IP 的虚拟主机,就可以使用基于域名的。

服务器如何找到正确的虚拟主机

一定要了解一点,基于域名的虚拟主机的解析,其第一步就是基于 IP 的解析。因为首先要通过匹配基于 IP 的虚拟主机来缩小查找范围,之后再进一步选择最合适的基于域名的匹配。如果在所有的虚拟主机段落中,把 IP 地址都用通配符 * 来代替,则与基于 IP 的映射无关了。

当一个请求到达时,服务器会基于该请求使用的 IP 地址和端口号 来寻找最佳匹配的 <VirtualHost> 参数。如果最佳匹配不止一个,apache 会进一步拿 ServerNameServerAlias 指令与请求中的服务器域名进行比较。如果最佳匹配没有找到对应的 ServerNameServerAlias,则使用匹配的虚拟主机列表中的 第一个,即针对该 IP 地址和端口号组合的 默认 主机。。

如果所有基于域名的虚拟主机中都省略了 ServerName 指令,服务器则会从系统主机名来获取。这往往会导致错误。

使用方法

第一步是为每一个不同的主机创建一个 <VirtualHost> 容器,在每个容器中,至少要有 ServerName 来标识主机,以及 DocumentRoot 指定其文件系统根目录。

创建默认虚拟主机

任何与现有 <VirtualHost> 不匹配的请求都会由全局配置来处理,不管其主机名或服务器名是什么。

虚拟主机列表中出现的 第一个主机 通常看看作 默认 的主机,具有最高优先级。这意味着如果某个请求与列表中所有主机中的 ServerName 都不匹配的话,会使用第一个虚拟主机。

在服务器中增加了一个基于域名的虚拟主机之后,如果该主机的参数中的 “IP 地址和端口的组合” 与已有的主机发生了重复,请求则会由更精确匹配的虚拟主机来处理。这种情况下,通常需要创建一个 默认的 虚拟主机,令其 ServerName 匹配最基本的服务器域名。在其之后,可以增加新的域名,虽然是相同的接口和端口号,但是需要单独的配置。

域名的继承

一定要 在每个基于域名的虚拟主机中用 ServerName 显式 指定域名

如果没有指定,服务器的域名会从主配置中继承,如果全局配置中也没有指定,则会使用系统启动时第一个侦听的地址的反向 DNS 解析到的域名。无论哪种情况,这个继承下来的域名都会影响基于域名的的虚拟主机的解析,因此一定要在每个虚拟主机配置中显式指定 ServerName

范例

假设当前服务器已有域名 www.example.com 的虚拟主机,现在想再加一个 other.example.com 的虚拟主机,指向同一个 IP 地址。

<VirtualHost *:80>
    # 这第一个虚拟主机同时也是 *:80 的默认主机
    ServerName www.example.com
    ServerAlias example.com *.example.com
    DocumentRoot "/www/domain"
</VirtualHost>

<VirtualHost *:80>
    ServerName other.example.com
    DocumentRoot "/www/otherdomain"
</VirtualHost>

可以用具体的 IP 地址来替换 <VirtualHost> 中的通配符 *

许多网站可以用多个域名访问,可以用 ServerAlias 为网站指定多个域名,放在 <VirtualHost> 段落中。本例中的 ServerAlias 所起的作用就是,指向 example.com 所有主机的请求都会由 www.example.com 这个虚拟主机来处理。一定要保证所有域名都经过了注册,并进行了 DNS 记录的解析,这样才能保证它们能在互联网上正确解析。

基于 IP 的虚拟主机

基于 IP 的虚拟主机会基于收到请求所在的 IP 地址及端口来应用不同的指令。通常用来在不同的 端口 或不同的 接口 提供不同的网站。

在许多情况下,基于域名的虚拟主机更方便,因为可以多个虚拟主机共享一个地址/端口。

系统需求

服务器必须为每个虚拟主机提供不同的 IP 地址/端口。可以用多个 物理网络接口 来实现,也可以用 虚拟网络接口 来实现,现在大多操作系统都支持,还可以用多个 端口号 来实现。

设置方法

有两种设置方法,一种是为每个域名单独运行一个 httpd 守护进程,另一种是运行一个进程,该进程自己就支持多个虚拟主机。

适合多个守护进程的场景

  • 数据需要隔离。例如公司甲不希望公司乙的人看到他们的数据,此时可以运行两个守护进程,每个使用不同的 UserGroupListenServerRoot
  • 负担得起 “侦听计算机上每个 IP 别名” 所需的内存和文件描述符。

适合单守护进程的场景

  • 允许多个虚拟主机共享 httpd 配置
  • 有大量请求要处理,多个守护进程会损失过多性能

多守护进程的配置

为每个虚拟主机单独安装一个 httpd,在各自的配置文件中用 Listen 指令指定该守护进程所侦听的地址/端口。如:

Listen 192.0.2.100:80

强烈建议在此处使用 IP 地址,而不要使用域名。原因见 DNS caveats

单守护进程的配置

单一的守护进程处理所有对主服务器和虚拟主机的请求。

<VirtualHost> 容器中可以使用 ServerAdminServerNameDocumentRootErrorLogTransferLogCustomLog 指令来配置。

<VirtualHost 172.20.30.40:80>
    ServerAdmin webmaster@www1.example.com
    DocumentRoot "/www/vhosts/www1"
    ServerName www1.example.com
    ErrorLog "/www/logs/www1/error_log"
    CustomLog "/www/logs/www1/access_log" combined
</VirtualHost>

<VirtualHost 172.20.30.50:80>
    ServerAdmin webmaster@www2.example.org
    DocumentRoot "/www/vhosts/www2"
    ServerName www2.example.org
    ErrorLog "/www/logs/www2/error_log"
    CustomLog "/www/logs/www2/access_log" combined
</VirtualHost>

同样,此处也建议只用 IP 地址来侦听,而不要使用域名。

就优先级而言,具体的侦听 IP 地址要比通配符更优先解析。同时,任何匹配的虚拟主机都要比主配置中的服务器优先解析。

## 虚拟主机范例

为简化文字,下文中 “地址” 专指 IP 地址。

一个地址 运行 多个基于域名网站

服务器上有多个域名解析到同一个地址,希望对 www.example.com 和 www.example.org 给予不同的响应。

# 确保侦听 80 端口
Listen 80
<VirtualHost *:80>
    DocumentRoot "/www/example1"
    ServerName www.example.com

    # 其它指令......
</VirtualHost>

<VirtualHost *:80>
    DocumentRoot "/www/example2"
    ServerName www.example.org

    # 其它指令......
</VirtualHost>

侦听表达式中的星号 * 匹配所有地址,因此主配置中的服务器肯定不会匹配。因为 ServerName www.example.com 是配置文件中的第一个虚拟主机,因此它看作 默认主机,具有 最高优先级。意味着收到的请求如果不匹配任何一个虚拟主机,就会用默认主机来处理。

以上配置几乎适用于任何基于域名的虚拟主机配置,唯一不适用的情况就是:基于不同的 IP 地址/端口提供不同的内容时。

可以把通配符换成系统中具体的 IP 地址,这样的虚拟主机只能匹配那些从特定 IP 地址收到的 HTTP 请求。

使用通配符 * 最大的用处就是用在动态 IP 地址的系统中。比如接入 ISP 网络的路由器,然后使用动态 DNS 解析服务。因为 * 会匹配任何地址,所以不管变成什么地址,该配置都能正常工作。

多个地址 运行 多个基于域名的网站

假设服务器有两个 IP 地址,在 172.20.30.40 上,运行主服务器 server.example.com,在 172.20.30.50 上运行多个虚拟主机。

Listen 80

# 此处为运行于 172.20.30.40 的主服务器
ServerName server.example.com
DocumentRoot "/www/mainserver"

<VirtualHost 172.20.30.50>
    DocumentRoot "/www/example1"
    ServerName www.example.com

    # 其它指令......
</VirtualHost>

<VirtualHost 172.20.30.50>
    DocumentRoot "/www/example2"
    ServerName www.example.org

    # 其它指令......
</VirtualHost>

只要不是针对 172.20.30.50 的请求,都由主服务器来服务。对于指向 172.20.30.50 的请求,如果主机名未知,或请求中没有 Host: 标头,则会由默认主机,即 www.example.com 来服务。

多个地址 运行 一个网站

操作系统有两个 IP 地址:192.168.1.1 和 172.20.30.40。该机器位于内网和外网之间。

面对外网,域名 server.example.com 会解析到地址 172.20.30.40;而面对内网,同一个域名则会解析到地址 192.168.1.1。

可以把服务器配置成无论响应内网还是外网的请求,都提供相同的网页内容,只需要一个 <VirtualHost> 容器:

<VirtualHost 192.168.1.1 172.20.30.40>
    DocumentRoot "/www/server1"
    ServerName server.example.com
    ServerAlias server
</VirtualHost>

这样,从哪个方向来的请求都会用这个容器来处理。

另外,在内网中可以直接使用 server 这个服务器别名来访问,无需用完整的域名 server.example.com。

上例中,如果把 IP 地址换成通配符 *,服务器会对所有地址提供相同的网站。

多个端口 运行 多个网站

多个域名使用同一个 IP 地址、不同端口。

以下范例中,先匹配的是服务器的 IP 地址/端口,然后才进一步匹配域名。

Listen 80
Listen 8080

<VirtualHost 172.20.30.40:80>
    ServerName www.example.com
    DocumentRoot "/www/domain-80"
</VirtualHost>

<VirtualHost 172.20.30.40:8080>
    ServerName www.example.com
    DocumentRoot "/www/domain-8080"
</VirtualHost>

<VirtualHost 172.20.30.40:80>
    ServerName www.example.org
    DocumentRoot "/www/otherdomain-80"
</VirtualHost>

<VirtualHost 172.20.30.40:8080>
    ServerName www.example.org
    DocumentRoot "/www/otherdomain-8080"
</VirtualHost>

基于地址的虚拟主机

服务器有两个 IP 地址 172.20.30.40 和 172.20.30.50,分析解析到 www.example.com 和 www.example.org。

Listen 80

<VirtualHost 172.20.30.40>
    DocumentRoot "/www/example1"
    ServerName www.example.com
</VirtualHost>

<VirtualHost 172.20.30.50>
    DocumentRoot "/www/example2"
    ServerName www.example.org
</VirtualHost>

基于端口 和 基于地址 的混合使用

服务器有两个地址 172.20.30.40 和 172.20.30.50,分别解析到 www.example.com 和 www.example.org。对每个地址都会分别使用端口 80 和 8080。这样,就共有四个虚拟主机的配置。

Listen 172.20.30.40:80
Listen 172.20.30.40:8080
Listen 172.20.30.50:80
Listen 172.20.30.50:8080

<VirtualHost 172.20.30.40:80>
    DocumentRoot "/www/example1-80"
    ServerName www.example.com
</VirtualHost>

<VirtualHost 172.20.30.40:8080>
    DocumentRoot "/www/example1-8080"
    ServerName www.example.com
</VirtualHost>

<VirtualHost 172.20.30.50:80>
    DocumentRoot "/www/example2-80"
    ServerName www.example.org
</VirtualHost>

<VirtualHost 172.20.30.50:8080>
    DocumentRoot "/www/example2-8080"
    ServerName www.example.org
</VirtualHost>

基于域名 和 基于地址 混合使用

如果在虚拟主机的参数中出现的地址,再没有出现在其它虚拟主机中,这就是严格意义上的基于 IP 地址的虚拟主机。

Listen 80
<VirtualHost 172.20.30.40>
    DocumentRoot "/www/example1"
    ServerName www.example.com
</VirtualHost>

<VirtualHost 172.20.30.40>
    DocumentRoot "/www/example2"
    ServerName www.example.org
</VirtualHost>

<VirtualHost 172.20.30.40>
    DocumentRoot "/www/example3"
    ServerName www.example.net
</VirtualHost>

# 基于 IP 地址
<VirtualHost 172.20.30.50>
    DocumentRoot "/www/example4"
    ServerName www.example.edu
</VirtualHost>

<VirtualHost 172.20.30.60>
    DocumentRoot "/www/example5"
    ServerName www.example.gov
</VirtualHost>

虚拟主机用做 反向代理

配置虚拟主机作反向代理,需要与 mod_proxy 模块一起使用。

image-center

<VirtualHost *:*>
    ProxyPreserveHost On
    ProxyPass        "/" "http://192.168.111.2/"
    ProxyPassReverse "/" "http://192.168.111.2/"
    ServerName hostname.example.com
</VirtualHost>

上例中所有指令解释如下:

<VirtualHost *:*>

匹配所有地址/端口。

ProxyPreserveHost On

通过该指令,Apache 会将用户请求报文中 原始的 Host: 标头 传递给后端服务器,而不会使用 ProxyPass 指定的服务器地址。这非常有用,因为它让后端服务器了解了客户端用于访问应用程序的原始、真实地址。

ProxyPass

语法:

ProxyPass [path] !|url [key=value [key=value ...]] [nocanon] [interpolate] [noquery]

ProxyPass 是主要的代理配置指令,它的作用是把后端服务器 映射 到本地服务器的空间中。本地服务器扮演的不是传统意义上的代理,而是作为后端服务器的一个 镜像 存在。path 是一个本地虚拟路径,url 是后端服务器 URL 的一部分,不能包含查询字符串。

本例中,"/" 为当前服务器的虚拟路径,"http://192.168.111.2/" 为后端服务器的虚拟路径。即所有访问 hostname.example.com 的请求都会被 apache 转发给 http://192.168.111.2/,注意后端服务器网址中最后的 斜线 / 是必须要有的,因为要与前面的 / 相对应。

ProxyPassReverse

该指令一般和 ProxyPass 指令配合使用,它会让 Apache 调整 HTTP 重定向应答中 LocationContent-LocationURI 标头中的 URL,这样可以避免出现 “重定向到后端服务器导致反向代理被绕过的” 问题。

只有该指令指定的 HTTP 应答标头会被重写。

使用 _default_ 虚拟主机

在所有端口的 _default_

该虚拟主机用于捕捉那些 未定义 IP 地址/端口的请求

<VirtualHost _default_:*>
    DocumentRoot "/www/default"
</VirtualHost>

用这种虚拟主机可以有效避免所有请求去往主服务器。

只要请求中有明确的地址/端口,就不会由这个虚拟主机处理。

如果请求中只是没有域名或没有 Host: 标头,则由匹配的基于域名的第一个虚拟主机处理。

在不同端口的 _default_

服务器运行于多个端口,现在要为 80 端口使用第二个 _default_ 虚拟主机。

<VirtualHost _default_:80>
    DocumentRoot "/www/default80"
    # ...
</VirtualHost>

<VirtualHost _default_:*>
    DocumentRoot "/www/default"
    # ...
</VirtualHost>

匹配 80 端口的默认虚拟主机必须出现在所有带通配符端口的默认虚拟主机之前,它会捕捉所有发送到未指定 IP 地址的请求,永远不会使用主服务器来处理。

在一个端口的 _default_

只希望为 80 端口做一个默认虚拟主机,不要其它默认主机。

<VirtualHost _default_:80>
    DocumentRoot "/www/default"
...
</VirtualHost>

80 端口上未指定地址的请求会由该虚拟主机处理,所有其它的未指定地址和端口的请求会由主服务器处理。

所有使用 * 的虚拟主机声明,其优先级均高于 _default_

基于域名 迁移到 基于 IP

某虚拟主机更换了 新的 IP 地址,希望平滑过渡。

基于域名的虚拟主机 www.example.org 应该有它自己的 IP 地址。为了避免域名解析服务器的问题,或防止代理服务器缓存中保存了旧的 IP 地址,可以在把基于域名 迁移 到基于 IP 期间,直接在原 IP 地址后面加上新地址,过渡期间同时使用两个地址。

Listen 80
ServerName www.example.com
DocumentRoot "/www/example1"

<VirtualHost 172.20.30.40 172.20.30.50>
    DocumentRoot "/www/example2"
    ServerName www.example.org
    # ...
</VirtualHost>

<VirtualHost 172.20.30.40>
    DocumentRoot "/www/example3"
    ServerName www.example.net
    ServerAlias *.example.net
    # ...
</VirtualHost>

ServerPath 指令

服务器现在有两个基于域名的虚拟主机,为了匹配正确的虚拟主机,客户端必须发送正确的 Host: 标头。较早的 HTTP/1.0 的客户端不会发送这个标头,apache 就无从知道它想访问哪个虚拟主机,于是会用主虚拟主机来服务。为了尽可能地 与低版本兼容,可以创建一个 主虚拟主机 ,它会返回一个页面,其中包含指向各基于域名虚拟主机的链接。

<VirtualHost 172.20.30.40>
    # 主虚拟主机
    DocumentRoot "/www/subdomain"
    RewriteEngine On
    RewriteRule "." "/www/subdomain/index.html"
    # ...
</VirtualHost>

<VirtualHost 172.20.30.40>
    DocumentRoot "/www/subdomain/sub1"
    ServerName www.sub1.domain.tld
    ServerPath "/sub1/"
    RewriteEngine On
    RewriteRule "^(/sub1/.*)" "/www/subdomain$1"
    # ...
</VirtualHost>

<VirtualHost 172.20.30.40>
    DocumentRoot "/www/subdomain/sub2"
    ServerName www.sub2.domain.tld
    ServerPath "/sub2/"
    RewriteEngine On
    RewriteRule "^(/sub2/.*)" "/www/subdomain$1"
    # ...
</VirtualHost>

因为设置了 ServerPath 指令,针对 http://www.sub1.domain.tld/sub1/ 的请求始终会由 sub1 这个虚拟主机来服务。

如果请求是针对 http://www.sub1.domain.tld/ 的,在客户端发送了 Host: 标头的情况下,由 sub1 服务;要是没有 Host: 标头,则由主虚拟主机服务。

请注意,比较诡异的是,如果客户端没有发送 Host: 标头,针对 http://www.sub2.domain.tld/sub1/ 的请求也会由 sub1 来服务,

RewriteRule 指令用于确保发送了 Host: 标头的客户端可以同时使用两种 URL,即一种是有 URL 前缀,一种是没有的。

文件描述符的限制

文件描述符有时也称为文件句柄。

当使用了大量虚拟主机,而且每个主机又使用了不同的日志文件时,Apache 可能会遭遇文件描述符耗尽的困境。Apache 使用的文件描述符总数如下:每个不同的错误日志文件一个、每个其他日志文件指令一个、再加 10~20 个作为内部使用。Unix 操作系统限制了每个进程可以使用的文件描述符数量。典型上限是 64 个,但可以进行扩充,直至到达一个很大的硬件限制为止(hard-limi)。

尽管 Apache 会试着增大限制,但如果发生以下情况,则这个机制无法起作用:

  1. 操作系统没有提供 setrlimit() 系统调用
  2. setrlimit(RLIMIT_NOFILE) 调用无法在系统上正常工作(比如 Solaris 2.3)
  3. 文件描述符的需求量已经超出了硬件的限制
  4. 操作系统对文件描述符作出了其他限制。比如说限制了 stdio 流只能使用 256 以下的文件描述符。(Solaris 2)

如果遇到了这样的问题,可以这样解决:

  • 减少日志文件的数量。不在 <VirtualHost> 配置段中指定日志文件,只在主日志文件中进行记录。
  • 如果系统因上述第 1 条或第 2 条原因不能正常工作,可以在启动 Apache 之前,用类似下述的脚本增大文件描述符的限制:
#!/bin/sh
ulimit -S -n 100
exec httpd

分解日志文件

要想把多个虚拟主机的日志记录到同一个日志文件中,可能会想事后把它们分开,以对不同的虚拟主机数据进行统计分析。可用以下方法达到这个目的。

首先,将虚拟主机的信息放入日志中。可以用 LogFormat 指令和 %v 变量达到这个目的。在日志格式串的开头加入 %v

LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost
CustomLog logs/multiple_vhost_log vhost

这将用日志的普通格式来创建一个日志文件。但会在每条记录前加上正式的虚拟主机名,即 ServerName 指令所设定的值。

要想把日志文件分开,每个虚拟主机一个日志文件,可以使用 awk 程序来完成这个工作。

动态配置海量虚拟主机

动机

<VirtualHost 111.22.33.44>
    ServerName                 customer-1.example.com
    DocumentRoot        "/www/hosts/customer-1.example.com/docs"
    ScriptAlias  "/cgi-bin/"  "/www/hosts/customer-1.example.com/cgi-bin"
</VirtualHost>

<VirtualHost 111.22.33.44>
    ServerName                 customer-2.example.com
    DocumentRoot        "/www/hosts/customer-2.example.com/docs"
    ScriptAlias  "/cgi-bin/"  "/www/hosts/customer-2.example.com/cgi-bin"
</VirtualHost>

<VirtualHost 111.22.33.44>
    ServerName                 customer-N.example.com
    DocumentRoot        "/www/hosts/customer-N.example.com/docs"
    ScriptAlias  "/cgi-bin/"  "/www/hosts/customer-N.example.com/cgi-bin"
</VirtualHost>

如果主配置文件中有许多个虚拟主机时,我们可能希望把这么多的容器替换成一种更简单的、动态的算法,这样做有两个好处:

  • 配置文件更小,因此 apache 可以更快速地启动,会占用更少的内存。小的配置文件更容易维护,不易出错。
  • 增加虚拟主机,只需要在文件系统创建对应的目录即可,同时创建 DNS 条目,无需重新配置或重启 apache。

主要的弊端在于,无法为每个虚拟主机分配单独的日志文件。然而,即使真可以的话,这么做也不是个好主意,因为这么多日志需要大量的文件描述符。最好的办法还是把日志记录到管道,在管道另一端把不同虚拟主机的日志拆分开。

如何动态配置

一个虚拟主机是由两条信息定义的:其 IP 地址,以及 HTTP 请求中 Host: 标头的内容。

可以自动把该信息插入返回的文件的路径中,动态海量虚拟主机技术正是基于这个想法实现的。通过使用 mod_vhost_alias 可以轻松做到,或者,也可以使用 mod_rewrite

这两个模块在默认都是禁用的,要想使用,必须在配置和编译 Apache 时启用。

要想动态虚拟主机正常运转,需要从请求中判断一些信息。最重要的是 服务器域名,服务器用来生成自引用的 URL 等。域名是由 ServerName 指令配置的,CGI 要想使用可以通过 SERVER_NAME 环境变量来获取。在运行时真正使用的值是由 UseCanonicalName 控制的,如果是 UseCanonicalName Off,服务器的域名就从请求的 Host: 标头的内容获取;如果是 UseCanonicalName DNS,则通过对虚拟主机的 IP 地址的反向 DNS 解析获取。前者用于基于域名的动态虚拟主机,后者用于基于 IP 的。如果因为没有 Host: 标头而导致 httpd 无法查明服务器域名,或者 DNS 解析失败,则会使用 ServerName 指定的值。

另一个要判断的是 文档根目录,由 DocumentRoot 指定,CGI 可以通过 DOCUMENT_ROOT 环境变量来获取。在普通的配置中,httpd 内核模块通过它来把 URI 映射到文件名,但如果是动态配置,这个工作必须由另一个模块来接管,可以是 mod_vhost_aliasmod_rewrite,它们有自己不同的映射的方法。这两个模块都不会为变量 DOCUMENT_ROOT 赋值,因此 CGI 和 SSI 都无法正确使用。

mod_vhost_alias

上面的范例使用 mod_vhost_alias 抽象化之后:

# 从 Host: 标头获取服务器域名
UseCanonicalName Off

# 可以基于第一个字段把该日志格式按虚拟主机拆分
LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
CustomLog "logs/access_log" vcommon

# 把服务器域名置入文件名中,用来匹配请求
VirtualDocumentRoot "/www/hosts/%0/docs"
VirtualScriptAlias  "/www/hosts/%0/cgi-bin"

该配置可以修改为基于 IP 的,只需把 UseCanonicalNameOff 改成 DNS。服务器域名就不用插入文件名中了,而是由 IP 地址派生出来。变量 %0 将引用请求的服务器域名,从 Host: 标头中获取。

简单的动态虚拟主机

这是对上述系统的调整, 为 ISP 的 web 托管服务器量身定做。

UseCanonicalName Off

LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon
CustomLog "logs/access_log" vcommon

# 在路径中插入域名中部分字段
VirtualDocumentRoot "/home/%2/www"

# 统一的 cgi-bin 目录
ScriptAlias  "/cgi-bin/"  "/www/std-cgi/"

用变量 %2 可以选择域名中的第 2 个字段,用于路径名。因此,www.user.example.com 的内容可对应的路径是 /home/user/www。使用统一的 cgi-bin 目录,而不是每虚拟机一个。

在同一服务器上使用多个虚拟主机系统

更复杂一些的配置,可以用 httpd 的普通的 <VirtualHost> 指令来控制各个虚拟主机配置的作用域。

例如,可以配置一个 IP 地址给普通客户的页面,另一个用于商业客户。

UseCanonicalName Off

LogFormat "%V %h %l %u %t \"%r\" %s %b" vcommon

<Directory "/www/commercial">
    Options FollowSymLinks
    AllowOverride All
</Directory>

<Directory "/www/homepages">
    Options FollowSymLinks
    AllowOverride None
</Directory>

<VirtualHost 111.22.33.44>
    ServerName www.commercial.example.com

    CustomLog "logs/access_log.commercial" vcommon

    VirtualDocumentRoot "/www/commercial/%0/docs"
    VirtualScriptAlias  "/www/commercial/%0/cgi-bin"
</VirtualHost>

<VirtualHost 111.22.33.45>
    ServerName www.homepages.example.com

    CustomLog "logs/access_log.homepages" vcommon

    VirtualDocumentRoot "/www/homepages/%0/docs"
    ScriptAlias         "/cgi-bin/" "/www/std-cgi/"
</VirtualHost>

更高效的基于 IP 虚拟主机

如果要改成基于 IP 的虚拟主机,会需要频繁的 DNS 解析,势必增加开销。为了解决这个问题,文件系统可以配置为针对 IP 地址进行响应,而非域名,不再需要 DNS 解析了。日志格式也需要做适当调整。

# get the server name from the reverse DNS of the IP address
UseCanonicalName DNS

# include the IP address in the logs so they may be split
LogFormat "%A %h %l %u %t \"%r\" %s %b" vcommon
CustomLog "logs/access_log" vcommon

# include the IP address in the filenames
VirtualDocumentRootIP "/www/hosts/%0/docs"
VirtualScriptAliasIP  "/www/hosts/%0/cgi-bin"