【VPS 网络极客与负载均衡 04】用 Nginx 反向代理把一个 VPS 变成多服务网关(实战教程)

一台便宜 VPS 如何同时承载博客、API、面板十几个服务?本文从原理到 502 排错,手把手教你用 Nginx 反向代理搭出生产级多服务网关。

很多新手刚买来一台便宜 VPS(比如 RackNerd 年付十几刀那台),第一反应是“装个 Nginx 搭个网站”。但当你手里慢慢攒下好几个服务——一个博客、一个私有 API、一个 NAS 管理面板、一个 Grafana 监控——难道每个都要占一个端口、各自再配一遍 HTTPS?这显然既不优雅也难维护。反向代理的价值正在于此:它让一台 VPS 同时承载十几个服务,对外却只暴露 80 和 443 两个端口,靠域名或路径把流量精准分发到对应后端。本文带你从原理一路走到排错,完整跑通一套生产可用的 Nginx 反向代理网关。

延伸阅读

更多相关攻略推荐:【购买指南 01】新手如何选购第一台 VPS:配置/线路/支付/退款【VPS 网络极客与负载均衡 01】从域名到 IP:DNS 解析全过刚买的 VPS 怎么连上去?SSH 新手教程(Windows / M2026 决策指南:本地模型 vs 云 API 成本对比——你的 V【VPS 硬件选型指南 (存储 篇) 03】主流商家磁盘实情横向盘点

反向代理和“用 Nginx 建站”到底差在哪

先说清楚最常见的误解。所谓“用 Nginx 建站”,通常是指 Nginx 自己就是那个 Web 服务器,直接读取磁盘上的 HTML、PHP 或静态资源返回给用户,Nginx 是内容的生产者。而反向代理里,Nginx 不生产任何内容,它只是个调度员:收到请求后,根据域名或路径把请求转发给背后的真实服务(可能是 Docker 容器、Node 进程、另一个 Nginx 或 tomcat),再把后端的响应原样回传给用户。

这个区别决定了配置思路完全不同。建站时你关心的是 root、index、try_files;做网关时你关心的是 upstream、proxy_pass、各种 proxy_set_header 和超时参数。一句话记忆:建站是 Nginx 直接服务,反向代理是 Nginx 替别人服务。正因为 Nginx 站在最前面,它顺手就能把 SSL 终止、缓存、限流、负载均衡这些脏活全包了。

一张图看懂整体架构

假设你的 VPS 公网 IP 上只跑一个 Nginx,它监听 80/443。后端所有服务都躲在内部网络里,不对外暴露端口。请求进来后由 Nginx 决定去哪:

  • 浏览器请求 blog.example.com → Nginx 转发到博客容器(内网 8080)
  • 浏览器请求 api.example.com → Nginx 转发到 API 容器(内网 3000)
  • 浏览器请求 example.com/grafana/ → Nginx 转发到监控容器(内网 3000)

整条链路对外只有一根入口,每个后端都只在内网互相访问,安全性天然更好。下面的表格对比了各角色的职责边界:

角色对外端口职责是否直接面对公网
Nginx 反向代理80 / 443路由、SSL 终止、缓存、限流
博客容器无(仅内网)提供博客内容
API 容器无(仅内网)提供接口数据
监控容器无(仅内网)展示指标面板

实战一:用不同域名暴露多个 Docker 服务

最常见的场景就是“一个域名一个服务”。第一步,先建一个共享的 Docker 网络,让 Nginx 和后端容器能互相用名字访问(Docker 内置 DNS 会自动把服务名解析成容器 IP):

docker network create proxy-net
docker run -d --name blog --network proxy-net --restart unless-stopped wordpress:latest
docker run -d --name api --network proxy-net --restart unless-stopped myapi:latest

注意这里容器没有用 -p 把端口映射到宿主机,它们只在 proxy-net 内网里可达。接着写 Nginx 配置,把 blog 和 api 两个域名分别转发出去:

upstream blog_backend {
    server blog:8080;
    keepalive 32;
}
upstream api_backend {
    server api:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name blog.example.com;
    location / {
        proxy_pass http://blog_backend;
        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_set_header Connection "";
    }
}

server {
    listen 80;
    server_name api.example.com;
    location / {
        proxy_pass http://api_backend;
        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_set_header Connection "";
    }
}

几个关键点:proxy_http_version 1.1 加上 Connection "" 是 keepalive 生效的硬性前提,少了这两行,keepalive 32 等于白写,高并发下会疯狂建连导致端口耗尽。X-Forwarded-For 和 X-Forwarded-Proto 把真实客户端 IP 和原始协议透传给后端,否则后端日志里看到的永远是 Nginx 的内网地址,而且以为所有人都在用 HTTP。

实战二:同一个域名下按路径分流

如果你只有一个域名,想用路径区分服务,也很简单。比如把 example.com/ 给前端,example.com/api/ 给后端接口:

upstream web_servers {
    server web:3000;
}
upstream api_servers {
    server api:8080;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://web_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /api/ {
        proxy_pass http://api_servers/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这里有个极易踩坑的细节:location /api/ 里的 proxy_pass 末尾带了斜杠,意思是把 /api/ 这个前缀去掉后再转发。也就是说用户访问 example.com/api/users,后端收到的是 /users。如果把斜杠去掉,后端收到的就是 /api/users。两种写法都对,但一定要和后端路由约定一致,否则就是 404 满天飞。

SSL 终止:HTTPS 只在这里终结一次

反向代理最香的一点,就是全站 HTTPS 只由 Nginx 处理一次,后端之间统统走内网明文 HTTP,省去每个服务各自申请证书的麻烦。先用 Certbot 拿到证书(80 端口先放开),再把配置升级成 443:

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

    ssl_certificate /etc/letsencrypt/live/blog.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://blog_backend;
        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_set_header Connection "";
    }
}

server {
    listen 80;
    server_name blog.example.com;
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://$host$request_uri; }
}

配置写完后务必先 nginx -t 校验语法,再 systemctl reload nginx。另外别忘了在域名 DNS 里把各个子域名都 A 记录指向这台 VPS 的公网 IP,否则 Nginx 收不到对应 Host 的请求,证书也签发不下来。廉价 VPS 通常只送 1 个 IPv4,多域名全靠同一个 IP 上的 Host 头区分,这正是反向代理的拿手好戏。

负载均衡与缓存:让廉价 VPS 也扛得住

当某个服务想跑多副本,只要在 upstream 里多写几个 server,Nginx 默认用轮询(round-robin)分发:

upstream api_servers {
    least_conn;
    server api1:8080 weight=3;
    server api2:8080 weight=1;
    server api3:8080;
    keepalive 32;
}

least_conn 会把新请求发给当前连接数最少的节点,适合请求耗时差异大的场景;ip_hash 则让同一客户端固定落到同一节点,能保住会话状态;weight 用于按机器性能分配权重。如果你想让某台机器只在上线故障时顶上,加个 backup 标记即可。

缓存同样能在 Nginx 这一层做,挡掉大量重复请求,对读多写少的博客、接口文档特别有效:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=1g inactive=60m;
server {
    location / {
        proxy_cache mycache;
        proxy_cache_valid 200 10m;
        proxy_cache_key $scheme$host$request_uri;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_pass http://blog_backend;
    }
}

响应头里的 X-Cache-Status 会显示 HIT 或 MISS,排错时一眼就能看出缓存有没有命中。对一台 1 核 1G 的 RackNerd 小机器来说,开一层静态缓存往往比盲目加配置更能扛住突发流量。

502 排错实战:先读错误日志

反向代理运行中最高频的故障就是 502 Bad Gateway。记住铁律:不要瞎猜,先 tail 错误日志

tail -f /var/log/nginx/error.log | grep -i "upstream|connect"

根据日志里的关键报错,502 基本能归到四类根因:

根因一:后端没启动或端口不通

日志出现 connect() failed (111: Connection refused),说明 Nginx 根本连不上后端。先用 curl 直连后端验证它是不是活着:

curl -sf http://127.0.0.1:8080/health && echo OK
ss -tlnp | grep :8080

常见情况是容器崩了、或者后端只监听在 127.0.0.1 而 Nginx 在另一容器里(跨网络访问不到)。把后端改成监听 0.0.0.0 通常就能解决。

根因二:高并发下端口耗尽

只在流量大时才出现的 502,几乎都是缺 keepalive 导致的端口耗尽。用下面命令看 TIME-WAIT 是否在涨:

watch -n 0.5 'ss -s | grep time-wait'

超过几千个还在涨就实锤了。解法就是前面强调的:upstream 加 keepalive,并配合 proxy_http_version 1.1 与 Connection ""。这是生产环境最容易漏、也最伤的一处。

根因三:后端太慢触发超时

日志若显示 upstream timed out (110),说明后端活着但响应慢。临时把超时放宽止血:

proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 30s;

但根子上是要去查后端为什么慢——慢 SQL、下游依赖卡顿都得从源头治。

根因四:用了变量上游却没配 DNS

如果你在 proxy_pass 里拼了变量(比如 proxy_pass http://$backend),必须在配置里写 resolver,否则 Nginx 在请求时解析不了域名也会 502。加上 resolver 8.8.8.8 valid=30s; 即可。

选购与避坑:VPS 配置怎么挑

反向代理本身极轻量,Nginx 吃不了多少资源,真正的瓶颈在后端服务总数和并发量。给几点实在建议:

  • 内存优先:每个 Docker 容器都要占内存,1G 内存的机器跑三四个轻量服务还行,再多就得上 2G。RackNerd 的 $21.99/年(1G/20G/1T)是入门甜点价。
  • 流量要留余量:反向代理会同时消耗客户端侧和上游侧双向流量,实际出口可能是用户访问量的两倍。CloudConeVultr 按时计费套餐适合先小流量试水。
  • IPv4 附加费:2026 年 IPv4 枯竭仍在推高价格,很多商家对额外 IP 收 $2–$3/月的附加费。好在反向代理靠 Host 头区分域名,一个 IP 就能挂几十个域名,这点钱完全能省下。
  • 续费陷阱:首年低价、续费翻倍的套路很普遍,下单前一定看清新老价格,优先选支持 PayPal、口碑稳的老牌厂商。

线路方面,如果你面向大陆用户,CN2 GIA(AS4809)延迟能压到 130–160ms 最稳,但价格也明显更高;普通优化线配合 Nginx 缓存和 CDN 也能把体验做得很顺。选 Vultr 这类按小时计费商家还能随时换机房测试,对调优很有帮助。

💡 延伸阅读:关于架构与基础设施的更多深潜指南,请前往 VPS 主题导读中心 (Hub) 获取全盘策略。