🚀 第02章 · 虚拟主机 server/server_name

本服务器 nginx 配置解析

虚拟主机 server/server_name

一台 Nginx 往往挂好几个域名,靠"一个 server 块代表一个站点"来区分。本章讲清 server 块的含义、server_name 的匹配规则,并用本机真实的四个站点配置做解析。学会它,你就能在没有独立服务器的情况下,用一台机器托管任意多个网站。

什么是虚拟主机

虚拟主机(Virtual Host)指:同一台物理服务器、同一个 IP(一个 443 端口),却能根据访问的域名把请求交给不同的站点处理。Nginx 的实现方式就是每个站点写一个 server {} 块。

本机四个站点,共用一个 443 端口,靠 server_name 区分:

域名 外部访问(443) 后端转发到
bangdream.xyz Nginx 终止 TLS 127.0.0.1:3000
lovelive.top Nginx 终止 TLS 127.0.0.1:3010
dsh.bangdream.xyz Nginx 终止 TLS 127.0.0.1:3081
gitlab.bangdream.xyz Nginx 终止 TLS 127.0.0.1:8090

理解 浏览器发来的是明文 HTTP/HTTPS 请求,Nginx 拿到后读 Host 请求头(HTTP/1.1 必须携带),再拿这个值和各个 server_name 比对,决定交给哪个 server 块。

server_name 怎么匹配

server_name 后面可以跟多个域名和通配符,匹配优先级从高到低:

server {
    server_name bangdream.xyz www.bangdream.xyz;   # 精确名(最优先)
    server_name *.bangdream.xyz;                   # 通配符(前/后)
    server_name ~^www\.\d+\.example\.com$;         # 正则(优先级最低)
}

本机用的都是精确匹配,简单直接。匹配规则汇总:

写法 匹配类型 优先级
a.example.com 完全精确 1 最高,逐字匹配
*.example.com 前缀或后缀通配 2,从左到右前缀优先
www.* 前缀通配
~^www\..*$ 正则 3 最低,按书写顺序

易错 如果写了多个 server,且某个请求的 Host 谁都匹配不上,就会落到 default_server(没有则第一个出现的 server)上。本机 bangdream.xyz 的 80 端口块标注了 default_server,处理所有"不认识的" HTTP 请求并返回 404。

本机一个 server 块拆解

以 lovelive.top.conf 为例:

# Love Live! - lovelive.top
server {
    server_name lovelive.top www.lovelive.top;

    access_log /var/log/nginx/lovelive.access.log;
    error_log  /var/log/nginx/lovelive.error.log;

    # 阻止敏感路径被代理访问
    location ~ ^/(node_modules|scripts)/ { return 403; }
    location = /package.json { return 403; }
    location = /package-lock.json { return 403; }

    location / {
        proxy_pass http://127.0.0.1:3010;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    listen [::]:443 ssl;
    listen 443 ssl;
    ssl_certificate     /etc/letsencrypt/live/lovelive.top/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/lovelive.top/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

拆成几层看:

  • server_name —— 这个 server 为哪些域名服务;两个域名写一起,www 和非 www 都能到。
  • access_log / error_log —— 每个站点有独立的日志文件,方便按站点排查。
  • location / { ... } —— 根路径交给后端 3010,即落到后端的 Node 服务。
  • listen 443 ssl; —— 监听 443 端口并开启 TLS。
  • ssl_certificate —— 指向 certbot 签发的证书文件路径。

listen 与端口

listen 决定"监听哪个 socket"。常见形态:

写法 含义
listen 443 ssl; 仅 IPV4 的 443 端口,启用 TLS
listen [::]:443 ssl; 仅 IPv6 的 443 端口
listen 443 ssl; + listen [::]:443 ssl; IPv4 + IPv6 都监听
listen 80; HTTP 80 端口
listen 80 default_server; 80 端口的兜底虚拟主机

本机四个 HTTPS 站点都同时写了 IPv4 和 IPv6,所以双栈都能访问。[::] 是 IPv6 的"任意地址"写法,对应 IPv4 的 0.0.0.0。

80 → 443 跳转块

certbot 会在每个 domain 配置里生成一个 80 端口的 server 块:只要 Host 是这个域名,就 301 跳到 HTTPS。

server {
    if ($host = lovelive.top) {
        return 301 https://$host$request_uri;
    }
    if ($host = www.lovelive.top) {
        return 301 https://$host$request_uri;
    }

    listen 80;
    listen [::]:80;
    server_name lovelive.top www.lovelive.top;
    return 404;
}

易错 这里的 if ($host = ...) { return 301 } 是几乎唯一"安全使用 if"的模式——只做 return。Nginx 的 if 在其他场景(比如在 if 里用 proxy_pass)行为诡异,属于文档里明令避免的坑。只把它当条件跳转用,别在里面做转发。

从零新建一个虚拟主机

接到"帮我挂一个 mydemo.com"的需求,完整流程:

# 1. 写配置到 available
sudo nano /etc/nginx/sites-available/mydemo.conf

# 2. 启用(软链接到 enabled)
sudo ln -s /etc/nginx/sites-available/mydemo.conf /etc/nginx/sites-enabled/mydemo.conf

# 3. 先测试语法
sudo nginx -t

# 4. 优雅重载
sudo systemctl reload nginx

mydemo.conf 最小内容(纯静态网站):

server {
    listen 80;
    server_name mydemo.com www.mydemo.com;

    root /var/www/mydemo;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

易错 配置写好后必须 nginx -t 通过再 reload,否则语法错误会导致整份配置被拒、reload 失败、仍跑旧配置,而且可能 syntax error 和 test failed 一起出现让人摸不着头脑。这一步也验证了:改错一处,全部站点重启失败——所以永远先测后载。

小结

server 块 = 一个虚拟主机;Nginx 用 Host 头匹配 server_name 把请求分发给对应站点;listen 定端口、server_name 定域名;sites-available 写配置、sites-enabled 软链启用;改完 nginx -t + reload。现在你已经能发起一次完整的多站点托管了。下一章讲 location / 里那个 proxy_pass 到底做了什么——反向代理。