个人技术分享

技术笔记

用 nginx 在一台 ECS 上跑主站与子站

· 标签:运维 · nginx

我只有一台服务器、一个公网 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 无关。

← 返回首页