nginx到底做了些啥?

zijieLeo
约 78 分钟
10 次浏览
nginx到底做了些啥?

nginx到底做了些啥?

我最初只有一台服务器、一个前端服务 A。A 的部署里自带了一份 Nginx,并直接占了服务器的 80/443。当时项目能跑,我没有觉得这有什么问题;直到第二个前端服务 B 要部署,我才发现:公开入口已经被 A 持有了,B 没有自己的入口。

后来再看,这个结构不仅会影响“第二个服务怎么部署”,还会把入口层、前端版本、静态资源和发布过程绑在一起。部署时出现的白屏、旧页面卡住、登录成功但页面不跳转,都和这层耦合有关。

浏览器默认 HTTP 端口是 80,HTTPS 端口是 443。如果服务监听其他端口,例如 82,访问者就要显式输入 http://domain:82。本文统一按常见的 80/443 讲。

1)先看两个真实现象

现象一:B 服务进不来

A 里的 Nginx 已经监听了 80/443。B 再想绑定同样的端口,就会冲突。即使 B 自己能启动,也没有办法代表这台服务器接收公网请求。

现象二:A 更新时,用户可能停留在旧页面

假设用户正停在 A 的登录页。此时 A 部署更新,内置 Nginx 随容器重启,旧的静态资源目录也被替换。

用户点击登录后,可能出现:

登录接口返回成功,token 保存成功,页面没有跳转;刷新后再试,又恢复正常。

这不是登录接口“半成功”,而是前后端流程中断了:

  1. 浏览器里还运行着旧版 HTML / 旧版 JS;
  2. 旧页面引用的是旧 hash 资源,例如 app.a1b2c3.js;
  3. 部署后服务器上只剩新版 app.d4e5f6.js;
  4. 登录接口成功,token 保存成功;
  5. 路由跳转时需要懒加载旧页面 chunk;
  6. 旧 chunk 请求失败,跳转流程中断;
  7. 刷新后拿到新版 HTML 和 JS,问题消失。

也可能出现更直接的白屏:

  • 前端容器重启,入口 Nginx 一起消失,请求返回 502;
  • 浏览器拿到旧 HTML,旧 JS 已不存在,首屏无法挂载;
  • SPA fallback 把 index.html 当成 JS 返回,触发 MIME / module load error。

所以问题不只是一句“Nginx 没配好”。更准确地说:

应用发布时,公开入口、HTML、hash 静态资源、服务进程同时发生变化,浏览器里的旧页面和服务器上的新版本之间出现了断层。

早期结构:服务 A 直接持有大门口 用户 80 / 443 服务器 / 主机网络 服务 A80 / 443 服务 B进不来 边界错位 A 不只是应用 还成了全网入口 路由规则散在 A 里 只有一个服务时能跑;第二个服务出现后就不够用了

2)调整结构:Nginx 独立持有大门口

A 的内置 Nginx 不是完全不能用。它的问题在于位置太靠前、职责太混:既是 A 的静态文件服务,又是整台服务器的公开入口,还参与 A 的发布生命周期。

更稳定的结构是:

调整后:统一入口,应用只监听内部端口 用户 HTTPS Nginx 80 / 443 · TLS · server_name · location 代理 · 静态文件 · 缓存 · 日志 前端 A127.0.0.1:3001 前端 B127.0.0.1:3002 后端 API127.0.0.1:4000 要点内部端口不公开 A 和 B 不再争抢 80/443,共享同一个域名、证书和安全边界

原则:

  1. 外层 Nginx 常驻,统一持有 80/443;
  2. A、B、API 只监听内部端口,例如 127.0.0.1:3001、127.0.0.1:3002;
  3. 安全组只开放 80/443,内部服务不暴露公网;
  4. 前端容器可以重启,但公开入口不跟着消失;
  5. 静态资源尽量从应用发布流程里抽出来,保留历史版本。

这不是说把 Nginx 独立出来后,所有发布问题都会自动消失。它解决的是“入口跟着前端容器一起消失”的结构问题;新旧资源断层还要靠缓存策略和发布策略处理。

3)Nginx 主流职责:先讲最常用的几件事

Nginx 能做的事情很多,但日常 Web 部署里最核心的是下面五类。

1. 统一入口和路由

根据域名、端口、路径,把请求分给不同服务:

example.com/aaa 到前端 A,example.com/bbb 到前端 B,example.com/api 到后端 API。

这就是它最像“网关”的部分。可以类比应用框架里的 Controller,但边界不同:

对比项Nginx应用 Controller
职责分发机器和流量层面的请求处理业务语义
位置服务器边缘应用内部
典型逻辑TLS、代理、静态资源、负载均衡参数校验、权限、事务、业务流程
不适合做复杂业务判断HTTPS 证书、机器间转发

2. TLS termination

常见链路是:

浏览器到 Nginx 走 HTTPS,Nginx 到内部服务通常走 HTTP。

公网加密和证书由 Nginx 统一处理,内部服务不用各自管理 HTTPS。若安全要求更高,内网也可以继续用 HTTPS 或 mTLS,但这属于另一个层级的部署策略。

3. 反向代理

反向代理的意思是:

Nginx 代表服务器接收请求,再转发给内部服务。

它统一补请求头、控制超时、隐藏内部拓扑,也把外部请求转换成 upstream 请求。

例如应用需要知道原始协议:

例如应用需要从 X-Forwarded-Proto 里读到原始协议 https。

如果这个头丢了,应用可能误以为请求是 HTTP,进而生成错误的跳转或 Cookie 属性。

4. 静态文件和缓存

Nginx 很适合服务 HTML、JS、CSS、图片、字体:

HTML 使用前重新验证;hash JS/CSS 可以长缓存并设置 immutable;图片和字体按内容策略设置缓存时间。

这和发布稳定性直接相关:HTML 要拿到新版,但旧 hash 资源不能立刻消失。

5. 流量控制和观测

它还能做:

  • access log / error log;
  • client_max_body_size;
  • 请求超时;
  • 简单限速;
  • 多实例负载均衡;
  • WebSocket 升级转发。

这些能力有用,但不等于所有事情都应该塞进 Nginx。业务权限、数据库判断、进程守护、自动扩容,仍然属于应用和基础设施编排层。

4)多前端怎么路由:路径和子域名是两种方案

example.com/aaa 是路径路由;aaa.example.com 是子域名 / 主机路由。

路径路由

server {
    listen 443 ssl;
    server_name example.com;

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

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

路径路由适合“一个站点下的多个模块”。但前端要知道自己挂在 /aaa/ 下,例如 Next.js 配置 basePath: "/aaa",否则 HTML 里的资源可能仍然指向根路径。

另外,proxy_pass 末尾有没有 / 会影响前缀处理:

proxy_pass http://127.0.0.1:3001/; 会把 /aaa/home 转成 /home;proxy_pass http://127.0.0.1:3001; 则会保留 /aaa/home。

规则没有绝对好坏,关键是 Nginx 的转发规则要和应用的 base path 一致。

子域名路由

子域名路由则用多个 server 匹配不同 server_name:

aaa.example.com 转发到 A,bbb.example.com 转发到 B。

它对应用侵入更小,每个应用仍可以认为自己在根路径 /。但要处理 DNS、证书、Cookie 作用域。如果子应用经常增删,还要考虑证书和解析成本。

场景更适合
同一站点多个模块路径路由
多个独立前端产品子域名路由
应用不方便改 basePath子域名路由
需要共享登录 Cookie路径路由或统一父域 Cookie

5)一次请求经过 Nginx 时发生了什么

一次请求在 Nginx 里的旅程 1. TCP80 / 443 2. TLS证书 / SNI 3. server域名 / 端口 4. location路径 / 规则 5. 处理static / redirect / proxy 6. upstream内部服务 响应浏览器 同时写 access log / error log:IP、路径、状态码、耗时、upstream

简化流程:

  1. 建立 TCP 连接,通常是 80/443;
  2. 如果是 HTTPS,完成 TLS 握手;
  3. 根据 server_name 和端口匹配 server;
  4. 根据路径匹配 location;
  5. 决定返回静态文件、重定向,还是代理给 upstream;
  6. upstream 返回后组装响应;
  7. 写 access log / error log。

一份结构化的生产配置骨架:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

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

    ssl_certificate     /etc/nginx/certs/example.com.pem;
    ssl_certificate_key /etc/nginx/certs/example.com.key;

    client_max_body_size 20m;

    location /aaa/ {
        proxy_pass http://127.0.0.1:3001/;
        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_read_timeout 60s;
    }

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

    location /api/ {
        proxy_pass http://127.0.0.1:4000;
        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_read_timeout 60s;
    }

    location /assets/ {
        alias /var/www/example/assets/;
        try_files $uri =404;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

修改配置前后,用 nginx -t 检查语法,再用 nginx -s reload 平滑加载。新版 Nginx 的 HTTP/2 写法可能有差异,例如 listen 443 ssl 加 http2 on,以当前版本文档为准。

6)发布时避免新旧版本断层

这是前面“登录成功但不跳转 / 白屏”的关键,也比背完整 Nginx 配置更重要。

核心原则

HTML 可以及时切到新版,但旧 hash 静态资源不能立刻消失。

浏览器已经打开的旧页面,在跳转路由时可能还会请求旧 chunk。如果旧资源被删,就会出现 404 或模块加载失败。

常见做法

维度做法解决什么问题
HTML 缓存no-cache,使用前重新验证避免一直拿旧入口页
hash 资源缓存max-age=31536000, immutable内容变即换文件名
旧资源保留保留上一版或多个历史版本让已打开的旧页面还能加载 chunk
原子切换current 软链接指向新版本目录避免半新半旧
蓝绿 / 滚动发布新旧服务共存,健康检查后切流避免 502 和白屏
共享资源目录多实例共享同一份静态资源避免旧请求被负载到没有旧资源的实例
前端兜底懒加载失败时自动整页刷新用户侧自恢复

一个典型的版本目录:

目录中保留 20261108-1000、20261108-1200 两个版本,current 软链接指向后者。

Nginx 只指向 current。发布时先上传新目录,再原子切换软链接,旧目录保留一段时间,确认没有请求后再清理。

对应的缓存规则:

location = /index.html {
    root /var/www/example/current;
    add_header Cache-Control "no-cache";
}

location /assets/ {
    root /var/www/example/current;
    try_files $uri =404;
    expires 30d;
    add_header Cache-Control "public, immutable";
}

注意这里有两个细节:

  1. 静态资源请求失败要返回 404,不要 fallback 成 HTML,否则会出现 MIME type mismatch;
  2. no-cache 不是不缓存,而是使用前必须重新验证,适合 HTML。

7)边界和排查顺序

Nginx 很有用,但不是万能层:

  • 适合做:入口、TLS、路由、反向代理、静态文件、缓存、日志、基础限流;
  • 不适合做:业务权限、数据库查询、进程守护、自动扩容、替代 CDN。

排查线上问题时,按四层收敛:

层看什么
请求到服务器了吗安全组、防火墙、端口监听
Nginx 选中规则了吗server_name、location、proxy_pass
Nginx 到 upstream 通了吗内部端口、容器健康状态、502 / 504
版本和缓存是否一致HTML 响应头、旧 chunk 是否 404、是否拿到 HTML 当 JS

常见状态码:

状态码常见含义
404路径或静态资源不存在
502upstream 挂了或连接失败
504upstream 超时
497HTTP 请求打到 HTTPS 端口

最后总结:

Nginx 的价值,是把机器和流量层面的公共问题从业务应用中抽出来:统一入口、TLS、路由、反向代理、静态资源和日志。应用退到内部端口,专注业务逻辑。发布时,还要保证 HTML 与 hash 资源的新旧版本有一段共存期,否则即使入口统一了,仍可能出现白屏或登录成功但不跳转。

评论

登录后即可发表评论

暂无评论,快来发表第一条评论吧!