多个域名如何共用一台服务器?虚拟主机、SNI与证书配置指南

手里有一台服务器,却要跑好几个网站——公司官网、商城、博客、还有给客户托管的小站,怎么让它们都跑在同一个 IP 上、互不干扰?这就是"多域名共用一台服务器"的经典需求。它涉及三个层面的知识:虚拟主机(让一个 IP 上跑多个站点)、DNS 解析(把不同域名指到同一台机器),以及 HTTPS 时代的 SNI(让多个域名共用一个 IP 也能各自用证书)。

这篇文章从原理讲到落地:先解释"基于名称的虚拟主机"如何在同一个端口上区分不同域名,再给出 Nginx 和 Apache 两套真实的虚拟主机配置,重点讲清 HTTPS 下多域名共用一个 IP 的关键——SNI 机制,以及它在老客户端上的兼容性问题。然后对比四种证书方案(单域名、多域名 SAN、通配符、多份证书)各自适合什么场景,最后讲目录/权限/日志的隔离和几个高频误区。读完你能在一台服务器上把多个域名稳稳地跑起来。

先建立核心认知:"一个 IP 跑多个域名"靠的是 HTTP 协议里的 Host 头,服务器根据 Host 决定返回哪个站点。HTTP/1.1 默认带 Host,所以同一 80 端口上放任意多个站点都没问题;HTTPS 因为要先握手加密,早期拿不到 Host,才需要引入 SNI 机制。

一、多域名共存的基本原理

同一个 IP、同一个 80 端口,服务器怎么知道该返回哪个网站?答案是基于名称的虚拟主机(name-based virtual host):

用户访问 www.a.com ─┐
用户访问 www.b.com ─┼─> 同一台服务器(同一个IP:80)
用户访问 www.c.com ─┘
                      服务器看请求里的 Host 头 → 匹配对应站点的配置 → 返回对应内容
  • DNS 把 www.a.com、www.b.com、www.c.com 都解析到同一台服务器的 IP。
  • Web 服务器(Nginx/Apache)里为每个域名写一个 server(Nginx)或 (Apache)块。
  • 服务器收到请求后,读取 HTTP 请求头里的 Host 字段,匹配到对应的站点配置,返回对应内容。

所以多域名共存的前提是浏览器发送 Host 头——HTTP/1.1 默认发送,无需额外操作。而 HTTPS 因为要"先加密再传 HTTP(含 Host)",握手时服务器还不知道你要访问哪个域名,这正是 SNI 要解决的问题(后面详述)。

二、DNS 与服务器准备

第一步是让域名都指向这台服务器。在域名解析控制台给每个域名添加 A 记录:

www.a.com    A    203.0.113.10     # 服务器公网 IP
www.b.com    A    203.0.113.10
www.c.com    A    203.0.113.10

验证解析是否生效:

dig +short www.a.com
nslookup www.b.com
# 都应解析到同一台服务器 IP

也要注意服务器的防火墙(安全组)要放行 80/443。不同系统的防火墙命令不同:

# RHEL/CentOS/Rocky(firewalld)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# Debian/Ubuntu(ufw)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

风险提示:修改防火墙规则可能影响所有入站连接。改动前先确认当前 SSH(22)端口已放行,避免把自己锁在外面;改动后用另一个终端验证连接再关闭原会话,保留 firewall-cmd --panic-off 或 ufw disable 作为应急回滚手段。

三、Nginx 虚拟主机配置

Nginx 用 server 块表示一个虚拟主机,server_name 指定该站点响应的域名。可以一个文件一个站点,也可以集中管理。

# /etc/nginx/conf.d/a.conf
server {
    listen 80;
    server_name www.a.com a.com;
    root /var/www/a.com/html;
    index index.html index.php;

    access_log /var/log/nginx/a.access.log;
    error_log  /var/log/nginx/a.error.log;
}

# /etc/nginx/conf.d/b.conf
server {
    listen 80;
    server_name www.b.com b.com;
    root /var/www/b.com/html;
    index index.html;

    access_log /var/log/nginx/b.access.log;
    error_log  /var/log/nginx/b.error.log;
}

要点:

  • server_name 可以写多个,空格分隔;www.a.com 和 a.com 都指向同一站点即可。
  • 匹配优先级:精确匹配 > 以 * 开头的通配 > 以 * 结尾的通配 > 正则(~)> 默认(第一个 server 或显式 default_server)。
  • 一定要配 默认站点,挡掉直接用 IP 访问或 Host 不匹配的请求:
# 显式指定默认站点,返回 444(直接断开)或跳转到主站
server {
    listen 80 default_server;
    server_name _;
    return 444;
}
  • 改完校验并重载(这是多站点最容易出事的一步,务必先 -t):
nginx -t && nginx -s reload

Debian/Ubuntu 习惯用 sites-available + sites-enabled 软链管理:

# 启用一个站点
sudo ln -s /etc/nginx/sites-available/a.com /etc/nginx/sites-enabled/a.com
sudo nginx -t && sudo systemctl reload nginx

四、Apache 虚拟主机配置对比

如果用的是 Apache,写法不同但思路一致。RHEL 系主配置在 /etc/httpd/conf/httpd.conf,Debian 系在 /etc/apache2/sites-available/。

# RHEL/CentOS/Rocky:/etc/httpd/conf.d/a.conf

    ServerName www.a.com
    ServerAlias a.com
    DocumentRoot /var/www/a.com/html
    ErrorLog  /var/log/httpd/a_error.log
    CustomLog /var/log/httpd/a_access.log combined

# Debian/Ubuntu:/etc/apache2/sites-available/b.conf

    ServerName www.b.com
    ServerAlias b.com
    DocumentRoot /var/www/b.com/html
    ErrorLog  ${APACHE_LOG_DIR}/b_error.log
    CustomLog ${APACHE_LOG_DIR}/b_access.log combined

# Debian/Ubuntu 启用站点
sudo a2ensite b.conf && sudo systemctl reload apache2
# RHEL 系直接 reload 即可(conf.d 下的 .conf 自动加载)
sudo systemctl reload httpd

Nginx 与 Apache 虚拟主机的对照:

维度 Nginx Apache
配置单元 server {}
域名指令 server_name ServerName / ServerAlias
站点根 root DocumentRoot
默认站点标记 default_server 第一个
配置目录 /etc/nginx/conf.d/ /etc/httpd/conf.d/ 或 /etc/apache2/sites-*
校验命令 nginx -t apachectl configtest / httpd -t

两套都能很好地支持多域名,选择更多取决于现有技术栈和团队熟悉度。

五、HTTPS 与 SNI:多域名共用一个 IP 的关键

到了 HTTPS,多域名共用一台服务器会遇到一个根本性难题。

问题:TLS 握手发生在 HTTP 之前,握手时服务器还不知道客户端要访问哪个域名(因为加密还没建立、HTTP 请求还没发来)。但在旧时代,一个 IP 上只能配一张证书用于握手——于是同一个 IP 上的多个 HTTPS 站点,客户端只能拿到第一张证书,访问其他域名就会域名不匹配报错。

SNI(Server Name Indication) 就是为此而生:客户端在 TLS 握手的 ClientHello 里提前带上要访问的域名,服务器据此选择对应的证书。

ClientHello (含 SNI: www.a.com) ──> 服务器
                                   服务器按 www.a.com 选证书
ServerHello + www.a.com 的证书 ──> 客户端

所以HTTPS 下多域名共用一个 IP,依赖 SNI。现代浏览器、curl、主流手机都支持 SNI;只有极老的客户端(老版本 IE on XP、很老的 Android、部分老 SDK)不支持,它们会拿到默认站点的证书从而报错。

Nginx 中每个 server 各配各的证书即可:

server {
    listen 443 ssl;
    server_name www.a.com;
    ssl_certificate     /etc/nginx/ssl/a.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/a.com/privkey.pem;
}

server {
    listen 443 ssl;
    server_name www.b.com;
    ssl_certificate     /etc/nginx/ssl/b.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/b.com/privkey.pem;
}

注意:每个绑定了 ssl_certificate 的 HTTPS server 块,都应确保有对应的默认兜底(listen 443 ssl default_server),否则不支持 SNI 的客户端会落到某个随机站点。同一 IP 上多个 SNI 站点的证书由 Nginx 根据 server_name 自动选择,无需手动指定。

六、证书方案对比

多域名下的 HTTPS 证书,有四种常见方案,选择取决于域名数量和结构。

方案 覆盖范围 适用场景 特点
单域名证书 一个具体域名 站点少、域名互不相关 便宜,但每域名一张,管理繁琐
多域名 SAN 证书 一张含多个域名(SAN 列表) 域名数量固定、有限(如 5~10 个) 一张证书管多个域名,续期集中
通配符证书 *.example.com 覆盖同域一级子域 同一主域下子域很多 只需一张,但只覆盖一级子域
多份独立证书 + SNI 各自独立 域名分属不同主体、需独立管理 灵活,但每张都要续期

关键提醒:

  • 通配符只覆盖一级:*.example.com 覆盖 www.example.com、api.example.com,不覆盖 a.b.example.com。且通常不覆盖裸域 example.com(需额外配置)。
  • SAN 证书有域名数上限:不同 CA 不同,常见几十到上百个,超了要换方案。
  • 续期:无论哪种,都要设自动续期(如 certbot 的定时任务)和有效期监控,避免过期导致全站握手失败。

七、目录、权限与日志隔离

多站点共用一个服务器,隔离做不好会互相影响。建议:

# 每个站点独立目录
/var/www/a.com/html
/var/www/b.com/html

# 属主与权限:Web 用户可读
sudo chown -R nginx:nginx /var/www/a.com
# RHEL 系 Web 用户是 nginx,Debian/Ubuntu 是 www-data
sudo chmod -R 755 /var/www/a.com

# 日志按站点拆分(前面配置里已体现),便于分别分析和轮转
ls /var/log/nginx/*.access.log
# SELinux(RHEL 系)下新目录要打上正确的上下文标签,否则 403
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/a.com(/.*)?"
sudo restorecon -Rv /var/www/a.com

风险提示:批量修改目录属主/权限可能影响其他服务或导致权限过松。用 chmod -R 前先确认目录用途,避免把敏感文件设成全局可读;SELinux 变更后用 restorecon 校正,不要用关闭 SELinux 的方式绕过。

八、常见误区

  • 没配默认站点:直接访问 IP 或未知 Host 时,落到第一个 server 块,可能暴露不该暴露的站点。务必加 default_server 兜底。
  • 以为 HTTPS 多域名只靠 DNS 就够:HTTPS 还需要 SNI 和每个域名的证书,DNS 只是第一步。
  • DNS 用了泛解析 *.example.com 但服务器没配对应站点:会落到默认站点,可能返回错误内容。
  • 一个 server 块里混放多个不相关域名的 root:正确做法是一个域名一个 server 块,server_name 各写各的。
  • 忽略证书匹配:SNI 站点要注意,不支持 SNI 的客户端会拿到默认证书,需评估是否有这类客户端。

常见问题

Q:同一个 IP 上能放多少个 HTTPS 站点?
从 SNI 机制上不设数量上限,实际受服务器内存、CPU、证书数量管理和运维复杂度影响。几十个站点共用一个 IP 很常见(经验值);再多建议按业务分组、用多个服务器或多个 IP 分摊。

Q:为什么 HTTPS 访问某个域名拿到了别的域名的证书?
通常是不支持 SNI 的客户端(老设备)访问时,服务器只能用默认站点的证书。也可能是该域名的 server 块缺少 ssl_certificate 配置。检查是否有 listen 443 ssl default_server 兜底,以及目标域名的证书配置是否正确。

Q:server_name 写 www.a.com 和 *.a.com 有什么区别?
前者只精确匹配 www.a.com;后者匹配 a.com 下任意一级子域(www.a.com、api.a.com…)但不含裸域 a.com。要同时覆盖,可写成 server_name a.com *.a.com;。

Q:HTTP 能正常访问,HTTPS 报证书错误,怎么排查?
先用 openssl s_client -connect IP:443 -servername www.a.com 看是否返回了正确证书。若证书不对,检查该域名的 server 块证书路径、是否被默认站点抢先匹配。HTTP 正常说明 DNS 和虚拟主机配置基本没问题,问题在 TLS 层。

Q:多域名各自独立证书,续期很麻烦怎么办?
可以用脚本批量续期并统一 reload,或用 LE 的 DNS 验证方式给通配符证书续期(一张覆盖所有子域)。关键是把"续期 + reload"自动化,并加有效期告警,避免漏续导致握手失败。

总结

多个域名共用一台服务器的核心是基于名称的虚拟主机:DNS 把各域名指向同一 IP,Web 服务器按请求的 Host 头匹配对应 server/VirtualHost 块。

Nginx 用 server 块加 server_name,Apache 用 加 ServerName;务必配置默认站点兜底,避免未知 Host 落到错误站点。

HTTPS 下多域名共用一个 IP 依赖 SNI 机制(握手时携带域名以选择证书);现代客户端都支持,但老客户端可能拿到默认证书报错,需评估兼容性。

证书方案按域名数量选择:单域名证书、多域名 SAN 证书、通配符证书(只覆盖一级子域)或各自独立证书加 SNI,并务必做自动续期与有效期告警。

隔离要做好:每站点独立目录与权限、日志按站点拆分、SELinux 目录打对标签;改配置统一走 nginx -t 校验、reload 生效、备份保底。