13 10 月 2026

一台服务器只放一个网站太浪费。Nginx 可以让多个域名共用一台机器、共用一个 80/443 端口,还能把请求转发给后端的不同应用(Node、Java、Docker 容器、游戏面板等)。

这篇讲透虚拟主机(多站点)和反向代理两件事。

一台机器搞定多个站点,配置要够用。财富云计算 的云服务器提供多种规格;做内网服务、测试环境、游戏服务端可选 NAT 型(无独立公网 IP,价格比独立 IP 机型省一半以上,通过端口映射对外提供服务)。

一、Nginx 是怎么区分不同网站的

浏览器访问 a.com 和 b.com,如果解析到同一个 IP,请求包里的 IP 是一样的。区分靠的是 HTTP 请求头里的 Host 字段。

GET / HTTP/1.1
Host: a.com        ← Nginx 靠这个决定用哪个 server 块

所以多站点配置的核心就是:多个 server 块,server_name 不同。

匹配规则(当请求的 Host 匹配不到时):

  1. 精确匹配 server_name a.com
  2. 通配符 *.a.com
  3. 正则 ~^www\d+\.a\.com$
  4. 该端口下的第一个 server 块(默认站点)
  5. 显式指定:listen 80 default_server;

建议显式配一个默认站点兜底,避免意外命中:

server {
    listen 80 default_server;
    server_name _;
    return 444;      # 直接关闭连接,不响应
}

444 是 Nginx 特有的状态码:立即断开,不返回任何内容,很适合对付乱扫的扫描器。


二、多站点配置实战

目录规划

/var/www/
├── site-a/           # a.com
│   └── index.html
├── site-b/           # b.com
│   └── index.html
└── blog/             # blog.c.com

站点 A

mkdir -p /var/www/site-a
echo "<h1>Site A</h1>" > /var/www/site-a/index.html
nano /etc/nginx/sites-available/site-a
server {
    listen 80;
    server_name a.com www.a.com;

    root /var/www/site-a;
    index index.html;

    access_log /var/log/nginx/site-a.access.log;
    error_log  /var/log/nginx/site-a.error.log;

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

站点 B(带 PHP)

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

    root /var/www/site-b;
    index index.php index.html;

    access_log /var/log/nginx/site-b.access.log;
    error_log  /var/log/nginx/site-b.error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\. {
        deny all;
    }
}

启用:

ln -s /etc/nginx/sites-available/site-a /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/site-b /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

拆分配置文件的好处

  • 每个站点独立,改一个不影响其他
  • 出问题容易定位
  • 可以单独给某站点加 HTTPS、限速、访问控制
  • 日志分开,统计方便

三、反向代理:把请求转发给后端应用

反向代理 = 用户访问 Nginx,Nginx 再去找真正的服务要数据,返回给用户。用户不知道后端是谁。

典型用途:

用途说明
隐藏后端后端只监听 127.0.0.1,外部访问不到,更安全
统一入口多个服务共用一个 80/443
HTTPS 卸载Nginx 处理加密,后端跑 HTTP
负载均衡分发到多台后端
缓存加速缓存后端响应
跨域解决前端同源访问

最基础的反代

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;

        # 传递真实信息给后端(非常重要,否则后端看到的都是 127.0.0.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;
    }
}

proxy_pass 的斜杠陷阱(新手高频坑):

location /api/ {
    proxy_pass http://127.0.0.1:3000;      # 保留 /api/ 前缀:/api/user → :3000/api/user
}

location /api/ {
    proxy_pass http://127.0.0.1:3000/;     # 去掉 /api/ 前缀:/api/user → :3000/user
}

有没有末尾的斜杠,决定了 URI 是否被替换。 记住这条就不会踩坑了。

完整反代配置(生产可用)

upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

    location / {
        proxy_pass http://backend;

        proxy_http_version 1.1;
        proxy_set_header Connection "";        # 配合 keepalive
        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;

        # 超时
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 缓冲
        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 16 8k;
    }

    # WebSocket 支持
    location /ws/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

四、负载均衡

一个 upstream 里写多台后端:

upstream app_pool {
    # 默认轮询
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
    server 127.0.0.1:3003;
}

调度策略

upstream app_pool {
    # 1. 轮询(默认)
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;

    # 2. 加权轮询
    server 10.0.0.1:8080 weight=3;      # 3/4 的请求
    server 10.0.0.2:8080 weight=1;      # 1/4 的请求

    # 3. IP 哈希(同一客户端固定到同一台,解决 session 问题)
    ip_hash;

    # 4. 最少连接
    least_conn;
}

健康检查(被动)

upstream app_pool {
    server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.3:8080 backup;        # 备用,其他都挂了才上
}
主动健康检查(定期探测)需要 Nginx Plus 或第三方模块;开源版可以用 max_fails 被动剔除。

会话保持

有状态应用(依赖本地 session)用 ip_hash;更好的做法是把 session 存到 Redis,后端无状态,怎么分发都行。


五、实战:一台服务器跑多个不同类型的应用

场景:

  • blog.com → 静态 + PHP(Typecho)
  • api.com → Node.js 跑在 3000
  • admin.com → Java 跑在 8080
  • game.com → 游戏面板跑在 8081
# blog.com —— PHP 站点
server {
    listen 443 ssl http2;
    server_name blog.com;
    ssl_certificate     /etc/letsencrypt/live/blog.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.com/privkey.pem;

    root /var/www/blog;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}

# api.com —— Node.js
server {
    listen 443 ssl http2;
    server_name api.com;
    ssl_certificate     /etc/letsencrypt/live/api.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

# admin.com —— Java(带长超时)
server {
    listen 443 ssl http2;
    server_name admin.com;
    ssl_certificate     /etc/letsencrypt/live/admin.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/admin.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_read_timeout 120s;

        # 只允许内网访问管理后台
        allow 192.168.1.0/24;
        deny all;
    }
}

# game.com —— 面板
server {
    listen 443 ssl http2;
    server_name game.com;
    ssl_certificate     /etc/letsencrypt/live/game.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/game.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 3600s;
    }
}

# 统一 HTTP 跳转
server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

六、反代场景的几个坑

1)后端拿不到真实 IP

必须设 X-Real-IP / X-Forwarded-For,且后端程序要读这个头:

  • Nginx 层再加:set_real_ip_from 上游IP; real_ip_header X-Forwarded-For;
  • PHP:$_SERVER['HTTP_X_FORWARDED_FOR']
  • Node(Express):app.set('trust proxy', true)
  • 日志格式里改用 $http_x_forwarded_for

2)502 Bad Gateway

tail -50 /var/log/nginx/error.log
# 常见:
# connect() failed (111: Connection refused) → 后端没起
# connect() to 127.0.0.1:3000 failed (13: Permission denied) → SELinux 拦截

SELinux 上允许 Nginx 发起网络连接:

setsebool -P httpd_can_network_connect 1

3)上传大文件失败

client_max_body_size 100m;      # 在 server 或 http 块
proxy_read_timeout 300s;

4)WebSocket 连不上

必须加 Upgrade 和 Connection 头(见上文配置)。

5)重定向循环

后端程序以为自己是 HTTP(因为反代用的是 HTTP),于是返回 HTTP 跳转,浏览器再请求,无限循环。

解决:传 X-Forwarded-Proto $scheme,并让后端识别。WordPress 等程序加:

if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
    $_SERVER['HTTPS'] = 'on';
}

6)响应慢

proxy_buffering off;             # 流式响应(SSE)需要关
proxy_request_buffering off;
proxy_read_timeout 300s;

七、反代 + 缓存:给后端减负

http {
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:100m
                     max_size=1g inactive=60m use_temp_path=off;
}

server {
    location / {
        proxy_cache mycache;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
        proxy_cache_lock on;
        proxy_cache_background_update on;
        add_header X-Cache-Status $upstream_cache_status;

        proxy_pass http://backend;
    }
}

响应头里会显示 X-Cache-Status: HIT / MISS / BYPASS,方便调试。

mkdir -p /var/cache/nginx
nginx -t && systemctl reload nginx

八、用 include 组织配置

配置文件多了以后,把公共部分抽出来:

mkdir -p /etc/nginx/snippets

/etc/nginx/snippets/proxy-headers.conf

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;
proxy_connect_timeout 10s;
proxy_read_timeout 60s;

/etc/nginx/snippets/ssl-params.conf

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Content-Type-Options nosniff always;

使用时:

server {
    listen 443 ssl http2;
    server_name api.com;
    include snippets/ssl-params.conf;
    ssl_certificate ...;
    ssl_certificate_key ...;

    location / {
        proxy_pass http://127.0.0.1:3000;
        include snippets/proxy-headers.conf;
    }
}

改一处,所有站点生效。


九、调试技巧

nginx -T | grep -A 20 "server_name api.com"    # 查看最终生效的完整配置
nginx -t                                        # 语法检查
tail -f /var/log/nginx/error.log

给反代加调试头:

add_header X-Upstream $upstream_addr;
add_header X-Upstream-Time $upstream_response_time;
add_header X-Request-Time $request_time;
curl -I https://api.com
# X-Upstream: 127.0.0.1:3000
# X-Upstream-Time: 0.052
# X-Request-Time: 0.055

一眼看出请求转发到哪台后端、花了多久。


十、一台服务器能放多少个站点

没有硬性限制,取决于:

因素影响
内存每个 PHP-FPM / Java 进程都吃内存,是主要瓶颈
CPU动态站点多的情况下
带宽流量大的站点
磁盘 IO数据库读写频繁时

经验参考(2 核 4G):

  • 静态站点:几十个没问题
  • WordPress/Typecho 类的轻量站点:5~10 个
  • Java/Node 应用:2~4 个(Java 尤其吃内存)

建议:数据库和 Web 分离、静态资源用缓存、给每个站点配 PHP-FPM 独立池并限制进程数,避免一个站点的流量把整机拖垮。


多站点部署对内存要求更高。财富云计算 的云服务器可按业务增长升级配置;若只是跑内网服务、开发测试、游戏服务端,NAT 型云服务器(无独立公网 IP,价格比带独立 IP 机型省一半以上)可以把预算更多留给内存和 CPU。

相关阅读:

  • 《Nginx 安装与配置入门》
  • 《免费 SSL 证书申请与 HTTPS 部署》
  • 《域名解析与 DNS 记录详解》
  • 《网站打开慢的排查流程》
延伸阅读
  1. 网站环境怎么选:LNMP 手动 / 宝塔面板 / Docker / 云开发
  2. 问卷表单系统搭建:SurveyKing / Formbricks,数据自己收
  3. 图床搭建:Lsky Pro / Lychee,自托管图片托管方案
  4. 邮件列表搭建:listmonk 自托管 Newsletter,把读者握在自己手里
  5. 文档站点搭建:MkDocs / VuePress / Docsify,产品文档与技术手册方案
  6. RSS 阅读器搭建:FreshRSS 自托管,把信息流拿回自己手里
  7. 短链接服务搭建:Shlink 自托管,链接数据自己掌握
  8. 网站状态监控页搭建:Uptime Kuma,服务器挂了第一时间知道
更多阅读
  1. 上一篇:免费 SSL 证书申请与 HTTPS 部署:Let's Encrypt 完整流程
  2. 下一篇:域名解析与 DNS 记录详解:A、CNAME、MX、TXT 到底怎么填
发表评论 13

取消回复