☰
Nginx配置实战:前后端分离部署、反向代理与故障排查
2026/10/11 7:24:30 网站建设 项目流程

1. 配置前后端服务之前:先看清 Nginx 的定位

我一直觉得,很多人配置 Nginx 容易翻车,不是因为命令不熟,而是没在动手之前想清楚 Nginx 在一个前后端分离的系统里到底扮演什么角色。就我接手的项目来看,绝大多数情况下 Nginx 就干三件事:把前端打包后的静态文件托管起来、把前端发来的接口请求转发给后端的某个服务、顺带解决掉跨域和缓存之类的问题。

说白了,Nginx 就是一个流量调度员。用户浏览器访问https://yourdomain.com,请求先落在 Nginx 上,Nginx 看这个请求是要静态资源(HTML、JS、CSS、图片)还是动态接口(/api/login),然后决定自己直接返回资源,还是把请求往上游的后端服务(比如 Spring Boot、Node.js 服务)转过去。前端和后端各管各的,端口、IP、服务地址彼此不用暴露,用户只感知到一个统一入口。

这个思路特别适合前后端分离项目,也适合一个服务器上跑多个服务的情况。你不需要在前端代码里写死后端地址,只需要让浏览器永远请求同源地址,再由 Nginx 根据路径来分流。这样前端后端独立部署、独立升级,遇到问题也能快速定位是静态资源的问题还是接口转发的问题。

另外一点,Nginx 本身是事件驱动架构,高并发下的表现远好于 Apache,而且配置语法简单、热加载方便、占用资源极低。一台 1 核 1G 的小机器,用 Nginx 扛住一个中小型前后端分离项目的日常流量,完全没什么压力。这就是为什么几乎所有部署文档里,Nginx 都是默认的第一选择。搞清楚了这些定位,后面配置的时候你就知道每一个指令是在解决什么问题,而不是在网上复制一段配置就完事。

2. 核心配置拆解:前端静态托管、路由回退与接口反向代理

2.1 前端静态资源的托管方式

前端项目开发完以后,经过构建工具(Vue 的npm run build、React 的npm run build)会输出一个dist目录,里面是纯静态文件。这时候你需要把整个目录丢到服务器上,再用 Nginx 指过去。最常见的写法是:

server { listen 80; server_name yourdomain.com; root /var/www/dist; index index.html; }

在这段配置里,root指定了静态资源在服务器磁盘上的位置,index指定了访问根路径时默认加载的文件。访问yourdomain.com的时候,Nginx 就会返回/var/www/dist/index.html。

但我必须提醒一个关键点:root和alias的区别。如果你写的是root /var/www/dist;,那么访问/js/app.js的时候,Nginx 找的是/var/www/dist/js/app.js,也就是把 URL 路径直接拼到 root 后面。但如果你写的是alias /var/www/dist/;,访问/static/js/app.js时,Nginx 会找到/var/www/dist/js/app.js,即把/static这一层替换掉。实际部署中,绝大多数场景用root就够了,只有当你需要让某个 location 映射到与 URL 完全不同的目录时,才需要启用alias,这一点搞混会导致静态资源 404,这是新手最容易踩的坑之一。

还有一个我必加的参数是try_files,尤其是做 Vue Router 或 React Router 的 history 模式时。用户在浏览器里直接访问https://yourdomain.com/dashboard,实际上这个路径在后端没有任何对应的文件,如果没有配置try_files,Nginx 会给你返回 404。正确做法是让所有不存在的文件路径都回退到index.html:

location / { root /var/www/dist; index index.html; try_files $uri $uri/ /index.html; }

$uri是当前请求的路径,$uri/是尝试把它当目录处理,最后的/index.html是兜底,也就是不管请求什么路径,只要前面两个找不到,就把index.html返回给浏览器。这样 Vue Router 拿到index.html之后会自己解析路由并渲染对应组件。如果前端用的是 hash 模式,那倒是不用管这个,但我还是建议能上 history 模式就用 history,URL 干净好看。

2.2 后端接口的反向代理配置

静态资源搞定了,接下来是重头戏:接口转发。前后端分离的核心就是前端页面只负责展示,数据全部来自后端接口。如果前端直接请求后端地址,会有两个问题:一是跨域麻烦,二是后端服务地址暴露在外。用 Nginx 做一层反向代理,前端只需要请求https://yourdomain.com/api/xxx,Nginx 再把请求转发给真正的后端服务。

基础配置长这样:

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

这段配置的意思是:所有以/api/开头的请求,都会被转发到本机8080端口。这段配置里有几个容易出问题的细节,我逐一拆解。

第一,proxy_pass后面的 URL 以/结尾和不以/结尾,行为完全不一样。这是 Nginx 配置里最经典的坑,没有之一。我现在把它讲透:如果proxy_pass后不带路径,那么请求的 URI 会原样转给后端。比如浏览器请求/api/user/list,location /api/匹配到了,Nginx 转发的 URI 依然是/api/user/list;如果后端接口定义就是/api/user/list,那就正好对上。但如果proxy_pass后面带了一个路径,比如proxy_pass http://127.0.0.1:8080/;,那这个路径会替换掉匹配部分的开头舍去。也就是/api/user/list里前面的/api/被舍去,替换成/,最终转发给后端的 URI 变成/user/list。两种写法都有人用,关键是后端接口定义必须和你的转发结果对齐。我个人的习惯是统一后端接口都带/api前缀,Nginx 里直接proxy_pass http://127.0.0.1:8080;不加路径,简单直观,后端 controller 里也不用刻意处理路径问题。

第二,proxy_set_header这四行是很有讲究的。Host如果不设置,后端看到的是127.0.0.1:8080,某些后端框架在做校验时拿不到真实的域名;X-Real-IP和X-Forwarded-For用来传递用户真实 IP,不然后端日志里永远只记录 Nginx 的内网地址,排查问题会很痛苦;X-Forwarded-Proto用来告诉后端用户请求是 HTTP 还是 HTTPS,有些后端生成重定向 URL 时需要根据它来拼协议。这些头不是随便加的,线上审计和日志系统往往都依赖它们。

第三,如果后端接口或某些服务用了 WebSocket(比如即时通讯、消息推送),上面这些配置还不够。WebSocket 需要额外加上 Upgrade 相关的头:

location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; }

先说proxy_http_version 1.1,因为默认 Nginx 转发用的是 HTTP/1.0,而 HTTP/1.0 不支持 Upgrade 机制,必须显式改成 1.1。Upgrade $http_upgrade是把浏览器请求头里的 Upgrade 字段透传给后端,Connection "upgrade"告诉后端这条连接要升级为 WebSocket 长连接。proxy_read_timeout 3600s这个也是我踩过坑之后加上的,默认 60 秒不读数据就会断开,WebSocket 长链接很容易被 Nginx 在中间掐掉。如果你的业务确实需要服务端主动推送消息,不配这几行,客户端每隔一分钟就会断一次,极其难受。

2.3 静态资源缓存策略

前端静态资源的缓存配置,很多人会忽略,但它对页面加载速度的影响非常明显。JS 和 CSS 这类文件,只要内容变了,文件名里的哈希就会变(Webpack 或 Vite 默认都会生成带 hash 的文件名),所以非常适合长缓存。图片字体这类文件也可以放心缓存。配置如下:

location /assets/ { alias /var/www/dist/assets/; expires 30d; add_header Cache-Control "public, immutable"; }

expires 30d是让浏览器在 30 天内直接使用本地缓存,都不用发请求确认。immutable这个属性是告诉浏览器这个资源在过期之前是绝对不会变的,只管大胆用。对应地,index.html必须走协商缓存,不能设置很长的过期时间,否则用户打开页面时永远加载的是旧版index.html,然后引用的还是旧版 JS,前端发版之后用户死活看不到新界面,这就是线上常见的那种"我改了你没看到"问题。所以index.html的配置要单独做:

location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; }

注意这里是location = /index.html,=是精确匹配,只对这个文件生效。这样做的好处很明显,静态资源缓存得久,用户二次访问秒开;index.html每次都会去服务器确认一下有没有新版,确认后最多 200 毫秒,成本可以忽略。这种方式是目前前端缓存的主流配置,基本所有项目都是这么玩的。

2.4 gzip 压缩与额外头信息

还有两个我觉得属于"不做后悔"的配置:gzip 压缩和安全相关的响应头。gzip 能大幅减少传输体积,一个 React 打包后的 JS 动辄一两百 KB,开启 gzip 后可能只剩四分之一。配置也不复杂:

gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1k; gzip_comp_level 5;

需要注意gzip_types一定要带上application/javascript和application/json,用默认配置的话很多现代文件格式压缩不到。gzip_min_length 1k是为了避免把那些本来就很小的文件也给压缩了,徒增 CPU 开销。gzip_comp_level 5是我实测下来的均衡点,1 到 9 是压缩率,太高了 CPU 扛不住,收益也肉眼不可见。

至于安全相关的头,我一般会加这几个:

add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Referrer-Policy strict-origin-when-cross-origin;

这些头是加给浏览器看的,X-Frame-Options SAMEORIGIN防止你的页面被别人用 iframe 嵌入后做点击劫持,nosniff防止浏览器错误嗅探文件类型。加完之后你甚至不用管具体原理,但安全扫描报告会好看很多。

3. 完整实操:一个 Server 块解决前后端分离部署

3.1 完整配置范例与注释

光拆开讲细节不够,我直接把一个在线上跑了几年的完整配置放出来。这是一个相对标准的单域名前后端分离部署模板,你们可以直接抄作业,但记得替换成自己的域名和路径。

# /etc/nginx/conf.d/yourdomain.conf server { listen 80; server_name yourdomain.com; # 将所有 HTTP 请求永久重定向到 HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name yourdomain.com; # SSL 证书配置,证书路径按实际修改 ssl_certificate /etc/nginx/ssl/yourdomain.pem; ssl_certificate_key /etc/nginx/ssl/yourdomain.key; ssl_protocols TLSv1.2 TLSv1.3; # 开启 gzip gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1k; gzip_comp_level 5; # 前端静态资源 root /var/www/dist; index index.html; # 安全相关响应头 add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Referrer-Policy strict-origin-when-cross-origin; # 前端路由回退 location / { try_files $uri $uri/ /index.html; } # index.html 不缓存 location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; } # 静态资源长缓存 location /assets/ { expires 30d; add_header Cache-Control "public, immutable"; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; 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_connect_timeout 15s; proxy_read_timeout 30s; } # WebSocket 代理(按需保留) location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } }

这段配置我建议你用一个文件保存到/etc/nginx/conf.d/下面,只要 Nginx 的nginx.conf主配置里包含include /etc/nginx/conf.d/*.conf;,就能自动生效。不同 Linux 发行版路径会有一点点不同,但大差不差。

写完配置之后,养成一个好习惯:先检查语法再 reload。

nginx -t nginx -s reload

nginx -t会检查配置文件语法是否正确,如果输出syntax is ok而且test is successful,再执行 reload。这个习惯能帮你避免很多低级错误。另外说明一下,reload和restart不一样,reload 是平滑重载配置,Nginx 主进程不会中断,正在处理的请求也不会被强制断开,而 restart 会全量重启进程,必然有短暂的中断。所以只有紧急情况下我才会用systemctl restart nginx,平时一律 reload。

3.2 配置接口路径时的几种常见情况

实际的业务场景不会像模板那么规矩,不同前后端的接口路径设计五花八门,我把最常见的三种情况拆开讲。

第一种,后端接口统一带/api前缀,前端请求也带/api。

这是最规整的方式。比如前端代码里请求/api/user/list,后端 controller 定义就是@RequestMapping("/api/user")。那配置就是:

location /api/ { proxy_pass http://127.0.0.1:8080; }

前面说了,这种写法是把原始 URI 原封不动转给后端,后端拿到的是/api/user/list,和接口定义完全匹配。好处是前后端对于接口路径的认知完全一致,排查问题时拿接口文档对比 Nginx 配置一眼就能看懂。

第二种,前端请求带/api,但后端接口本身没有/api前缀。

这种情况下,后端登录接口是/user/login,但前端统一走到/api/user/login。要转发到正确的后端地址,就得用上路径替换的写法:

location /api/ { proxy_pass http://127.0.0.1:8080/; }

注意proxy_pass后面有个/。这个带不带斜杠的区别在于:带斜杠时,Nginx 会把匹配到的location前缀部分从 URI 中移除,再拼接到代理地址后面。具体来说,请求/api/user/login匹配到/api/,前缀/api/被丢弃,剩下/user/login,拼接到http://127.0.0.1:8080/后,最终转发为http://127.0.0.1:8080/user/login,正好对应后端接口定义。

这两种方式不存在谁比谁好,完全取决于你们团队的前后端约定。但我强烈建议在项目初始定接口规范的时候,就把这个问题定死,否则换个人维护 Nginx 配置,看到proxy_pass末尾一个斜杠之差,很容易一脸懵掉。

第三种,一个域名下面挂多个前端应用,用路径区分。

这种情况在中小公司很常见,比如同一个域名,/blog/是一个博客前台,/admin/是管理后台,后端接口统一走/api/。可以用多个 location 指向不同目录:

location /blog/ { alias /var/www/blog/; index index.html; try_files $uri $uri/ /blog/index.html; } location /admin/ { alias /var/www/admin/; index index.html; try_files $uri $uri/ /admin/index.html; }

这里就必须要用alias了,因为root模式下 Nginx 会去找/var/www/blog/blog/index.html,这显然不对。alias把 URL 里的/blog/直接映射到/var/www/blog/目录。注意try_files的最后一个回退路径要写成/blog/index.html,因为浏览器实际请求的路径还是带/blog/前缀的。每个前端应用构建的时候也要把publicPath或base配置成对应路径,否则打包后访问 JS 资源全是相对路径,会全部 404。前端路由里的路径也需要带上这个子路径前缀,细节很多,但配通以后一套域名拖多个应用的感觉确实很清爽。

3.3 后端服务多环境切换思路

真实项目里,前端可能要对接开发、测试、生产好几个后端环境。传统的做法是每个环境构建一次前端产物,配置不同接口地址。现在更主流的方式是前端构建时统一用同一个域名,Nginx 根据不同来源或 Header 转发到不同后端。比如可以通过$http_origin变量做判断,但这种写法在公司内部测试环境用得多,生产环境一般还是直接用固定地址。

我特别想给你们分享一个思路:如果是几种环境并存,可以在同一个 Nginx 里用不同的 server_name 来区分。比如dev.yourdomain.com转发到内网开发环境的后端,而test.yourdomain.com转发到测试环境的后端,生产环境走yourdomain.com。这样做的好处实在是太明显了:前端只需要构建一次,不管部署到哪个环境,走同一套代码,拿到的都是同一个静态包,接口地址由 Nginx 根据访问域名不同动态决定,不会出现"测试环境引用生产环境 API"这种经典事故。

对应的配置也很简单,在多个 server 块里分别写server_name和proxy_pass就行。我工作中就是这么干的,平时切换环境只需要改本地 hosts 文件或者用不同独立域名访问,前端代码里根本不用关心当前在哪个环境,也不用在构建时传一堆环境变量,省心很多。

4. 部署上线后容易翻车的细节与排查实录

4.1 SSL 证书替换不生效

"我明明把证书文件换掉了,Nginx 也 reload 了,但访问网站发现还是旧证书。"这个问题我在群里被问过不下十次。多数情况下不是因为 Nginx 没生效,而是这几个原因。

第一,证书文件路径没变,但是文件内容没真的覆盖。有的系统上 Nginx worker 进程会在启动时把证书内容加载进内存,reload 后理论上应该失效重读。但如果你的证书是通过include指令引用的,而编辑时原文件正在被进程占用,某些编辑器保存时可能没真正写到原路径。解决办法很简单,替换完成后先检查文件内容确认是新证书,再 reload,别盲目相信编辑器的"已保存"提示。

第二,只 reload 了 Nginx,但浏览器或 CDN 有缓存。浏览器缓存证书信息的情况虽然少,但确实存在。尤其是某些企业网络环境下,中间设备和浏览器持久缓存了旧证书相关信息。遇到这种,清一下浏览器缓存或者用无痕模式再试。如果挂了 CDN(Cloudflare 之类的),CDN 边缘节点缓存了旧证书,那才是真没生效。这种场景下你需要去 CDN 控制台更新证书,再刷新 CDN 缓存。

第三,证书链不完整。很多初次搞证书的人会踩这个坑,服务器给用户返回证书的时候,只返回了域名证书,没有把中间证书一起返回。大部分主流浏览器会自动补全证书链,但有些客户端比较严格,就会连接失败。这种问题在 Nginx 上配置时,ssl_certificate直接使用 CA 机构给你的完整链文件(通常是一个.pem或者.crt,里面有多个证书块)就能解决。配置完可以用openssl s_client -connect yourdomain.com:443 -servername yourdomain.com查看服务器实际下发的证书链,多验证几轮准没错。

排查证书问题最重要的一点:千万不要只靠眼睛看文件路径对不对,要去看实际生效的证书。Nginx 配置里写的是一个路径,真正对外生效的是进程加载进内存的那份,两个不是一回事。

4.2 页面 404 与接口 404 的不同排查路径

如果用户反馈某个页面打开是 404,第一反应不是去看 Nginx,而是要先问:404 是前端页面 404,还是接口 404?这两个问题的排查方向截然不同。

如果是页面 404,打开控制台看 Network,如果 HTML 文档本身就返回 404,那说明 Nginx 没有把请求交给前端路由,大概率是try_files没写或者写错了。排查方法是先把浏览器的请求 URL 拆分来看,比如访问/user/detail,如果 Nginx 里配置的是try_files $uri $uri/ /index.html,那它会先在磁盘上找user/detail文件,找不到再回退到/index.html。如果你少了最后的/index.html,这个请求就直接 404 了。所以,SPA 项目出现这种问题,几乎是必查try_files。

如果是接口返回 404,比如前端请求/api/user/list返回 404,那要看后端服务到底收到请求没有。我常用的排查手段是直接看后端日志,后端收到了请求但是没匹配到对应路由,那就是后端接口路径和转发路径对不上,检查proxy_pass末尾是否有/;如果后端日志里根本没有记录,说明请求压根没转发到位,那就检查location匹配规则。可以用curl直接测试:

curl -i http://127.0.0.1:8080/api/user/list

如果这个 curl 直接访问后端能返回正常数据,而通过curl https://yourdomain.com/api/user/list返回 404,问题一定出在 Nginx 的转发规则上。用这个方法可以快速把问题隔离在前端、Nginx 还是后端哪一层,不用瞎猜。

4.3 502、504 报错排查思路

502 Bad Gateway 在反向代理场景下算是最高频的故障。它的本质是 Nginx 作为代理服务器,尝试跟上游后端通信,但上游没有给出有效响应。最常见的几种情况:后端服务没有启动或者启动后挂了;后端端口写错;防火墙或 SELinux 拦截了 Nginx 到后端的连接。排查命令首推:

curl http://127.0.0.1:8080/health

如果这个直接访问都失败了,那是后端的问题;如果成功,而 Nginx 转发还是 502,那就换:

nginx -t tail -f /var/log/nginx/error.log

看 error.log 是最直接的。Nginx 的错误日志里会把 upstream 连接失败的原因写出来,比如connect() failed (111: Connection refused),意思是目标端口拒绝连接,基本可以断定后端没监听或者没启动。还有一种情况是connect() failed (113: No route to host),那就是网络层都不通,可能是防火墙拦了。

504 Gateway Timeout 就要比 502 好定位得多:连接能建立,但后端处理时间超过了 Nginx 配置的proxy_read_timeout或者proxy_connect_timeout。这时优先去看是不是后端某个接口响应确实很慢,比如一次复杂报表查询要跑 5 秒以上,但 Nginx 的proxy_read_timeout只配了 3 秒。解决办法就是把超时时间调大,或者在应用层做优化,从业务上缩短处理时间。有一点值得留意:不要一看到 504 就去调大超时时间,如果后端接口本身很慢,调大 Nginx 超时只是缓解症状,真正要做的还是优化接口响应时间,不然并发一高,所有请求都堆积在 Nginx,连接数打满,问题只会更严重。

4.4 一个容易被忽略的防火墙坑

很多人在本机 curl 通,浏览器却怎么也访问不了,会去排查 Nginx 配置,结果折腾一大圈发现是防火墙。云服务器(阿里云、腾讯云、AWS)通常在两个层面都有防火墙:安全组和操作系统内防火墙。安全组没放行 80、443 端口,或者firewalld/iptables没有对应规则,端口外面根本探测不到。

排查命令:在云平台安全组确认放行了 80/443 后,在服务器本机执行:

ss -lntp | grep -E '80|443'

如果本机能监听,再用一台外部机器(或手机流量)访问公网 IP 的 80 端口试试。最常见的错误是只做了安全组放行,没管操作系统防火墙,或者反之。另外还有一个很多人都忽视的点:如果 Nginx 配置了listen 443 ssl,而你还想用 HTTP 访问,必须有一个listen 80的 server 块,否则访问 HTTP 会直接连接失败,看起来像"服务器宕机"一样。我见过不少把 80 端口重定向 443 的配置,但只写了listen 80的 server,结果 443 默认配置不正确,导致整个网站访问不了,就是这个原因。

5. 进阶:多服务、负载均衡与更复杂的场景

5.1 用 upstream 做负载均衡

如果你的后端服务需要部署多台,或者一台服务器上起了多个相同服务的实例,Nginx 的upstream模块就是为这个准备的。配置方式非常直接:

upstream backend { server 127.0.0.1:8081; server 127.0.0.1:8082; } server { listen 80; server_name yourdomain.com; location /api/ { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

默认的负载均衡策略是轮询,也就是每个请求轮流发给 8081、8082,成本最低,两台机器流量也基本平均。但如果后端接口需要保持会话,就必须加上ip_hash:

upstream backend { ip_hash; server 127.0.0.1:8081; server 127.0.0.1:8082; }

ip_hash的作用是让同一个用户 IP 的请求始终打在同一台后端机器上,这样 session 不会因为负载均衡导致来回跳。这个策略我觉得非常实用,因为不少老项目还是用 session 做登录态,不加ip_hash,用户登录后第二次请求被分到另一台机器,session 就丢了,体验直接拉垮。

还可以给每个节点加权重:

upstream backend { server 127.0.0.1:8081 weight=2; server 127.0.0.1:8082 weight=1; }

weight=2表示这台机器承担双倍流量,适合新旧机器混用、配置不对等的场景。还有几个常用的检查参数:max_fails=3 fail_timeout=30s,表示 30 秒内失败 3 次就把该节点踢出可用列表,30 秒后再试探恢复。这属于 Nginx 自带的健康检查,虽然不如专业的注册中心那么优雅,但对付小型集群足够了。

5.2 微服务网关场景与 Nginx 的边界

我之前做微服务拆分的时候,经常被问到一个问题:有了 Spring Cloud Gateway,还需要 Nginx 吗?我的看法是:需要,而且两者的位置完全不同。Nginx 在最外层,负责流量的入口,统一收掉所有公网进入的请求,做一些 TLS 终止、静态资源缓存、全局限流;网关在内部,负责对着服务注册中心做路由转发、权限校验、灰度策略。链路大致是:客户端 -> CDN -> Nginx -> 网关 -> 微服务实例。这一层一层的关系,其实很像公司门口的保安、前台和部门助理,各自解决各自层级的问题。

在这个架构下,Nginx 配置往往就变得不那么复杂了。比如你有一个微服务集群,所有依赖网关暴露的接口都从/gateway/或/api/进入,Nginx 只需要把/api/代理到网关地址,再由网关去精细路由到不同服务。Nginx 的 upstream 也只需要配网关节点:

upstream gateway { server 127.0.0.1:8080; server 127.0.0.1:8081; } location /api/ { proxy_pass http://gateway; 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; }

很多人希望 Nginx 能把微服务按 URL 精确路由到具体服务,这个我建议真的没必要。服务列表是动态的,每次扩容缩容手动去改 Nginx 太痛苦了,而且 Nginx 没有那么强的动态感知能力。让它把流量粗粒度地导给网关,剩下的路由交给网关和服务发现组件,这套模式才是主流微服务架构下最合理的工作分配。我觉得这其实也符合一般技术架构的演化规律:明确每个组件的能力边界,只让它解决它最擅长的问题。

5.3 日志与监控:上线后至少要看这两个日志

最后必须提醒的就是日志。配置 Nginx 的人负责把配置写好,但运维和排查问题靠的是日志积累。Nginx 默认的访问日志在/var/log/nginx/access.log,错误日志在/var/log/nginx/error.log,这两个文件是排障时的首选资源。

我一向的习惯是自定义日志格式,把关键信息都打进去:

log_format main '$remote_addr - [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'upstream_addr=$upstream_addr request_time=$request_time';

这个格式里,$upstream_addr能看出请求实际转发到了哪个上游节点,$request_time是 Nginx 处理请求的总耗时,$http_x_forwarded_for是取到用户真实 IP。如果发现某个接口的$request_time一直在涨,基本可以断定后端性能有问题。

配合定时任务切割日志也别忘了,不然日志文件会无限膨胀占满磁盘。最简单的方式是系统自带的logrotate,大多数 Linux 发行版装 Nginx 后会自动配置好,但如果你是自己编译安装的 Nginx,很可能没有自动配置,需要手动写一个/etc/logrotate.d/nginx文件。这个细节很多人忽略,等到服务器磁盘告警才意识到。

6. 我对这套配置方案的实际体会

这几个月反复配过各种项目之后,我最大的体会是:Nginx 配置本身不难,难的是想清楚请求该怎么流。很多新手拿着别人的配置直接复制,遇到问题不知道从哪下手,就是因为没理解 location 匹配顺序、proxy_pass 末尾斜杠这些细节背后的行为差异。我自己刚开始配 Nginx 的时候,也曾经因为一个多出来的斜杠,排查了整整一个下午。那时没有这些经验总结,只能一遍一遍看文档、加日志、curl 测试,最后甚至在 thinkphp 和 nginx.conf 之间来回改了好几次才算弄明白。

所以我很建议你们遇到问题不要急着在群里发截图,先按我上面的方法一步步隔离:先 curl 后端看服务通不通,再 curl 域名带-H看头部有没有异常,最后看 Nginx error.log 里到底报什么错误。绝大多数 Nginx 问题都逃不出这三步。

配置功夫到了一定程度,你甚至会发现 Nginx 才是那个最稳定、最不容易出 bug 的组件,它一旦配好,是真的可以扔在那里跑半年不带重启的。真正出问题的永远是业务代码、网络环境或证书过期这些外部因素。而你需要做的,就是练好基本功,让 Nginx 成为你运维体系里最踏实的那一环。

最后再分享一个小技巧:不管配什么 server,都先开一个location = /health的接口返回 200,比如:

location = /health { access_log off; default_type text/plain; return 200 "OK"; }

这样以后无论是自己做监控还是接入第三方健康检查,都有现成的探针可以用。Nginx 后面的服务挂了,你这个健康检查照样返回 OK,只有当 Nginx 本身出问题才会报警,这个行为是符合预期的。别问我怎么想到的,问就是被监控告警误报折磨过很多次换来的经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询