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 保存成功,页面没有跳转;刷新后再试,又恢复正常。
这不是登录接口“半成功”,而是前后端流程中断了:
- 浏览器里还运行着旧版 HTML / 旧版 JS;
- 旧页面引用的是旧 hash 资源,例如
app.a1b2c3.js; - 部署后服务器上只剩新版
app.d4e5f6.js; - 登录接口成功,token 保存成功;
- 路由跳转时需要懒加载旧页面 chunk;
- 旧 chunk 请求失败,跳转流程中断;
- 刷新后拿到新版 HTML 和 JS,问题消失。
也可能出现更直接的白屏:
- 前端容器重启,入口 Nginx 一起消失,请求返回 502;
- 浏览器拿到旧 HTML,旧 JS 已不存在,首屏无法挂载;
- SPA fallback 把
index.html当成 JS 返回,触发 MIME / module load error。
所以问题不只是一句“Nginx 没配好”。更准确地说:
应用发布时,公开入口、HTML、hash 静态资源、服务进程同时发生变化,浏览器里的旧页面和服务器上的新版本之间出现了断层。
2)调整结构:Nginx 独立持有大门口
A 的内置 Nginx 不是完全不能用。它的问题在于位置太靠前、职责太混:既是 A 的静态文件服务,又是整台服务器的公开入口,还参与 A 的发布生命周期。
更稳定的结构是:
原则:
- 外层 Nginx 常驻,统一持有
80/443; - A、B、API 只监听内部端口,例如
127.0.0.1:3001、127.0.0.1:3002; - 安全组只开放
80/443,内部服务不暴露公网; - 前端容器可以重启,但公开入口不跟着消失;
- 静态资源尽量从应用发布流程里抽出来,保留历史版本。
这不是说把 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 时发生了什么
简化流程:
- 建立 TCP 连接,通常是
80/443; - 如果是 HTTPS,完成 TLS 握手;
- 根据
server_name和端口匹配server; - 根据路径匹配
location; - 决定返回静态文件、重定向,还是代理给 upstream;
- upstream 返回后组装响应;
- 写 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";
}
注意这里有两个细节:
- 静态资源请求失败要返回 404,不要 fallback 成 HTML,否则会出现 MIME type mismatch;
no-cache不是不缓存,而是使用前必须重新验证,适合 HTML。
7)边界和排查顺序
Nginx 很有用,但不是万能层:
- 适合做:入口、TLS、路由、反向代理、静态文件、缓存、日志、基础限流;
- 不适合做:业务权限、数据库查询、进程守护、自动扩容、替代 CDN。
排查线上问题时,按四层收敛:
| 层 | 看什么 |
|---|---|
| 请求到服务器了吗 | 安全组、防火墙、端口监听 |
| Nginx 选中规则了吗 | server_name、location、proxy_pass |
| Nginx 到 upstream 通了吗 | 内部端口、容器健康状态、502 / 504 |
| 版本和缓存是否一致 | HTML 响应头、旧 chunk 是否 404、是否拿到 HTML 当 JS |
常见状态码:
| 状态码 | 常见含义 |
|---|---|
| 404 | 路径或静态资源不存在 |
| 502 | upstream 挂了或连接失败 |
| 504 | upstream 超时 |
| 497 | HTTP 请求打到 HTTPS 端口 |
最后总结:
Nginx 的价值,是把机器和流量层面的公共问题从业务应用中抽出来:统一入口、TLS、路由、反向代理、静态资源和日志。应用退到内部端口,专注业务逻辑。发布时,还要保证 HTML 与 hash 资源的新旧版本有一段共存期,否则即使入口统一了,仍可能出现白屏或登录成功但不跳转。
评论
登录后即可发表评论