我只有一台服务器、一个公网 IP,却想同时跑两样东西:一个纯静态的个人站,和一个 Node 写的实时数据面板。最初的做法是让 Node 直接监听 80 端口,结果整个域名都被这个应用占住了,静态站在同一台机器上根本没有入口。
根本约束:一个地址端口只能有一个进程
很多人第一反应是"让两个程序都监听 80",这做不到。绑定 0.0.0.0:80 只能有一个持有者,第二个进程会直接启动失败。要做域名分流,就必须有一个进程先拿到 80,再按请求里的 Host 头决定转发给谁。这个角色交给 nginx 最合适。
第一步:把应用从 80 挪到本机端口
Node 应用改成只监听回环地址,公网扫不到它,只有本机的 nginx 能连:
# /etc/systemd/system/worlds-prob.service.d/10-nginx-proxy.conf
[Service]
Environment=PORT=5173
Environment=HOST=127.0.0.1
注意 HOST 这一项。Node 的 app.listen(PORT) 不传主机名时默认绑 0.0.0.0,也就是所有网卡。如果只改端口不改绑定地址,5173 会直接暴露在公网,等于白折腾。写成 app.listen(PORT, process.env.HOST || '0.0.0.0'),再用环境变量控制,本地开发不受影响。
第二步:两个 server 块 + 一个兜底
upstream app { server 127.0.0.1:5173; keepalive 16; }
server { # 主站:静态文件
listen 80 default_server;
server_name example.com www.example.com;
root /opt/tech-site;
location / { try_files $uri $uri/ /index.html; }
}
server { # 子站:反向代理
listen 80;
server_name worlds.example.com;
location / {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Host $host; # 关键
}
}
三个细节值得单说。
Host 头必须显式透传
不写 proxy_set_header Host $host; 时,nginx 会把 Host 改成 upstream 的名字(这里是 app)。应用内部一旦用 Host 拼绝对链接、做重定向或写日志,拿到的就是错的值。加上这一行,应用看到的就是用户访问的真实域名。
default_server 兜底比直接拒绝友好
留一个 default_server 的块承接"没匹配上任何 server_name"的请求。我把它指向静态首页,这样直接用 IP 访问、或者访问没登记的域名,看到的是正常页面而不是错误码。对备案核查尤其有用——审核人员往往会直接用 IP 或随手输个域名看站点是否真实存在。
发行版自带的默认站点会打架
Ubuntu/Debian 装完 nginx 会带一个 /etc/nginx/sites-enabled/default,里面已经有一个 listen 80 default_server。这时你再写一个 default_server,nginx -t 会直接报"duplicate default server"。做法是把自带的默认站备份移走,不要硬改。
第三步:验证要看 Host 头,不是看浏览器
本地验证时不要用浏览器(改 Host 麻烦),直接用 curl 指定 Host 头,几秒就能把分流逻辑跑一遍:
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: example.com' http://127.0.0.1/
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: worlds.example.com' http://127.0.0.1/
curl -s -H 'Host: worlds.example.com' http://127.0.0.1/api/health
改配置之前先跑 nginx -t,通过再 reload。如果新配置起不来,老进程还在,站点不会中断——这也是把静态站和前端代理放在 nginx、把业务留在 Node 的另一个好处。
顺带一个上线后才发现的坑
用没备案的域名去访问境内服务器的 80 端口,会被拦截并返回一个"未备案"提示页,看起来像是自己配置错了。判断方法很简单:从服务器本机 curl 同一个 Host 头,如果返回正常,说明链路和配置都没问题,问题出在域名备案与接入关系上,跟 nginx 无关。