一台 VPS 同时跑几个服务时,不要让每个服务各自去处理域名和证书。更省事的做法是:Nginx 监听 80/443 做统一入口,应用只监听本机端口。Nginx 负责 TLS、域名分发和转发,应用只管自己的业务。
浏览器 ──HTTPS──> Nginx :443 ──> 127.0.0.1:3000 (站点 A)
└──────> 127.0.0.1:8080 (站点 B)
下面以 Ubuntu 24.04、Nginx 1.24 为例,两个域名分别是 a.example.com 和 b.example.com。
一、先让应用只监听本机
假设站点 A 是 Node 服务,站点 B 是一个监听 8080 的程序。启动时把它们绑定到 127.0.0.1,而不是 0.0.0.0:
# 示例:Node 服务
HOST=127.0.0.1 PORT=3000 node server.js
好处是外网无法直连这些端口,所有流量必须经过 Nginx,少了绕过入口和暴露端口的风险。应用若必须监听 0.0.0.0,至少在防火墙里只放行 80/443。
二、写站点 A 的 server 块
新建 /etc/nginx/sites-available/a.example.com:
server {
listen 80;
listen [::]:80;
server_name a.example.com;
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;
}
}
四个头各有用途:
Host $host:把用户访问的域名传给后端,很多应用靠它判断站点;X-Real-IP:客户端真实 IP;X-Forwarded-For:代理链,后端取第一个非内网地址;X-Forwarded-Proto:告诉后端原始请求是 http 还是 https,生成链接时用得上。
Nginx 官方文档对 Host 的说明是:默认不透传原始请求的 Host 和 Connection,需要显式设置;推荐用 $host,因为它在请求没有 Host 头时也有值。
三、站点 B 照抄一份
/etc/nginx/sites-available/b.example.com:
server {
listen 80;
listen [::]:80;
server_name b.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
}
启用两个站点:
ln -s /etc/nginx/sites-available/a.example.com /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/b.example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
此时用 HTTP 访问两个域名,应该分别落到不同后端。
四、proxy_pass 的斜杠别乱加
这是最容易踩的坑。官方原文:
If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive…
If proxy_pass is specified without a URI, the request URI is passed to the server in the same form as sent by a client…
举例:
| 配置 | 请求 /api/users 时后端收到 |
|---|---|
proxy_pass http://127.0.0.1:3000; |
/api/users |
proxy_pass http://127.0.0.1:3000/; |
/api/users(location 为 / 时替换后相同) |
location /api/ { proxy_pass http://127.0.0.1:3000/; } |
/users |
第三种是把 /api/ 前缀剥掉。要保留前缀就别加末尾斜杠,要剥掉就加。改完必须 nginx -t 再 reload。
五、签发证书
Certbot 官方推荐用 snap 安装:
apt install -y snapd
snap install --classic certbot
ln -s /snap/bin/certbot /usr/local/bin/certbot
给两个域名各签一张证书:
certbot --nginx -d a.example.com
certbot --nginx -d b.example.com
certbot --nginx 会自动改 Nginx 配置、加上 443 的 server 块和证书路径。想自己控制配置,就用 certbot certonly --nginx 只拿证书,然后手动写:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name a.example.com;
ssl_certificate /etc/letsencrypt/live/a.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/a.example.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;
}
}
多个域名共用一个 IP 靠的是 SNI。Nginx 官方文档写得很清楚:SNI 允许浏览器在握手阶段带上要访问的域名,这是在单 IP 上跑多个 HTTPS 站点的通用方案;但 SNI 只能传域名,用 IP 直接访问时不要依赖它。
证书续期不用手动:
certbot renew --dry-run
能跑通就说明定时续期配置正常。
六、强制跳转到 HTTPS
certbot --nginx 在签发时会问是否重定向,选是就会自动加。要自己写,就在 80 的 server 块里加:
server {
listen 80;
server_name a.example.com;
return 301 https://$host$request_uri;
}
注意跳转后后端拿到的 X-Forwarded-Proto 应该是 https;如果应用生成的回调地址还是 http,检查这个头有没有传。
七、WebSocket 要额外两行
反代普通网页不需要,但如果后端有 WebSocket(实时通知、终端类界面),在 location 里补:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
缺这两行,握手会失败,浏览器控制台一般能看到 WebSocket 连接被断开。
八、验证顺序
nginx -t通过;curl -I http://a.example.com返回 301 或 200;curl -I https://a.example.com返回 200,证书域名正确;- 后端日志里能看到真实客户端 IP 和
https; certbot renew --dry-run通过。
常见问题对照
| 现象 | 优先检查 |
|---|---|
| 502 Bad Gateway | 后端是否在跑、端口是否对、是否只绑了 127.0.0.1 |
| 一直重定向循环 | 80/443 两个 server 块的 server_name 是否写错 |
| 页面能开但样式丢失 | 应用生成的是 http 链接,检查 X-Forwarded-Proto |
| 后端日志 IP 全是 127.0.0.1 | 少了 X-Real-IP / X-Forwarded-For,或应用没读这两个头 |
| 证书提示域名不匹配 | 该 server 块用的证书是否属于这个域名 |
| WebSocket 连不上 | 缺 Upgrade 与 Connection 两个头 |
一句话总结
入口统一交给 Nginx,应用只监听 127.0.0.1:一个域名一个 server 块,转发时补齐 Host 与三个转发头,证书交给 certbot,改完先 nginx -t 再 reload。