Nginx配置实践:从静态部署到反向代理与负载均衡全解析

发布时间:2026/9/28 17:59:37
Nginx配置实践:从静态部署到反向代理与负载均衡全解析 1. 先搞懂 Nginx 到底在干什么很多人第一次接触 Nginx是在部署一个前后端分离项目的时候。前端打包出一堆静态文件后端服务跑在某个端口上这时候你发现直接访问静态文件倒是没毛病可一旦前端要请求接口浏览器就会出现跨域报错或者你根本不想让用户直接看到后端的真实端口。此时 Nginx 的作用就浮现出来了它既能老老实实地把静态文件托管出去又能作为一个流量中转站把来自客户端的请求转发给后端的应用服务。说得直白一点Nginx 就是一道门所有流量都从它这里进再由它决定这些请求应该落到哪台机器、哪个端口、哪个目录。Nginx 能解决的问题远不止“转发”这么简单。它可以在一台服务器上挂多个网站可以保护后端应用不暴露真实地址可以在多台后端之间按策略分摊请求压力还可以做缓存、做访问控制、做 TLS 加密通信。对运维和开发来说这几乎是一个绕不开的基础组件。我遇到过不少场景明明是业务代码没问题最后排查下来全是 Nginx 配置不合理也见过很多团队因为不懂 upstream 的调度规则一到流量高峰后端就被打挂。所以把静态部署、反向代理、负载均衡这三个核心环节吃透后面再去看那些复杂的 rewrite、跨域、限流、灰度发布配置你会发现自己已经有了一个很扎实的地基。这篇文章适合谁读如果你刚接触 Nginx想知道自己应该从哪几个点入手或者你已经背过一些配置文件但不知道每行参数背后的逻辑又或者你正在准备面试需要把反向代理和负载均衡的原理讲清楚——那这篇文章能给你一条完整的路线。我会按照从静态部署到动态代理再到多节点负载均衡的递进顺序来拆解每一部分都会给出可以直接用的配置也会把那些踩过的坑指出来。2. 静态文件部署最基础也最容易踩坑2.1 一份最小可用的静态站点配置Nginx 做静态文件托管是最简单的用法。假设你有一个打包后的前端项目放在了/opt/www/myapp目录下里面是index.html、js/、css/ 等资源。你只需要在 Nginx 的配置目录下新建一个 server 块告诉它监听哪个端口、站点根目录在哪。配置大概是这个样子的server { listen 80; server_name www.example.com; root /opt/www/myapp; index index.html; location / { try_files $uri $uri/ /index.html; } }这套配置能解决大多数单页应用的需求。try_files $uri $uri/ /index.html的意思是如果请求的路径在磁盘上能找到就直接返回文件找不到就回退到index.html让前端路由接管。对 Vue、React 这类使用 History 路由的项目来说这行配置几乎是标配否则你直接刷新一个子路由页面Nginx 会返回 404。这里有一个容易忽略的点location /下面的try_files只在location /这个块内生效。如果你同时有location /api/这样的接口匹配规则那么接口路径会走另一个 location不会受这个回退规则影响。所以要养成一个习惯把静态资源匹配和接口代理分离清楚否则等你发现所有接口都被转发到index.html的时候会一头雾水。2.2 root 与 alias 的区别是你必须拿捏好的静态部署中最高频的误区就是分不清root和alias。一句话总结root会把完整的请求 URI 拼在 root 指定的目录后面而alias会用 alias 目录替换掉 location 中匹配的那一段路径。举个例子location /static/ { root /data/websites; }当一个请求是/static/img/logo.png时实际查找的文件路径是/data/websites/static/img/logo.png。也就是 root 后面的目录 完整的 URI。再看 alias 的情况location /static/ { alias /data/websites/res/; }这时请求/static/img/logo.png会去查找/data/websites/res/img/logo.png。alias 后面的内容直接替代了/static/这段前缀。如果你在配置里把 URL 前缀和目录结构弄混了文件就怎么也访问不到日志里全是 404。我自己的习惯是访问路径和磁盘路径结构一一对应时就用root需要把某个虚拟前缀映射到完全不同的目录时才用alias。另外注意alias后面目录末尾的斜杠很容易被漏掉。没有斜杠时Nginx 会尝试把它当作文件名来处理行为会变得很诡异。这种问题不看到 error.log 你几乎发现不了所以写配置的时候一定要细心。2.3 静态部署时的性能与安全细节静态文件不是配好能访问就完事了。生产环境里你会希望 Nginx 帮浏览器缓存住那些带哈希值的静态资源因为文件名变了才需要重新下载没变就可以直接用缓存。可以在 location 里加上location /assets/ { expires 30d; add_header Cache-Control public, immutable; }这样assets目录下的文件会被浏览器缓存 30 天图片、JS、CSS 如果都跟着版本号走效果会很好。但要注意如果你的index.html也放进了这个目录那问题就大了——用户每次进站都拿到缓存的旧页面发版后还要强制刷新才能看到新内容。所以一般只对指纹资源做长缓存index.html使用no-cache。还有一个很多人会踩的坑权限。Nginx 的worker_processes运行用户和项目目录的所有者如果不一致就可能出现“403 Forbidden”但配置文件本身又没有问题。我遇到过一次前端项目是另一个开发同学用 root 权限上传的目录权限是700Nginx 的 worker 用户是nginx结果怎么访问都是 403。解决办法很简单把项目目录chown -R nginx:nginx /opt/www/myapp或者调一下目录权限让运行用户有读权限。3. 反向代理让 Nginx 成为流量入口3.1 从正向代理到反向代理要理解反向代理先得知道它和正向代理的关系。正向代理是站在客户端这一侧的比如你在浏览器里配置一个代理服务器让它替你去访问 Google、YouTube那个代理服务器就是正向代理。反向代理则站在服务端这一侧客户端根本不知道真正的后端服务在哪它只知道自己访问的是 Nginx 的地址由 Nginx 来隐藏和转发。为什么需要反向代理最直接的原因是安全。假如你后端跑着一个 Tomcat监听在 8080 端口这个端口如果直接暴露到公网很容易被扫描、被攻击。用 Nginx 监听 80/443再把请求转发到内网的某个 8080 端口外部能接触到的入口就只有 Nginx后端的真实地址和端口都被藏起来了。另一个原因是灵活性你可以在反向代理层统一做 HTTPS 证书终止、请求日志、访问控制、限流等等而后端服务不需要关心这些公共能力。3.2 反向代理的核心配置模板下面这是我比较常用的一套反向代理配置兼顾了常见参数和可读性server { listen 80; server_name api.example.com; location /v1/ { proxy_pass http://127.0.0.1:8080; 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_connect_timeout 5s; proxy_read_timeout 10s; proxy_send_timeout 10s; } }这里有三个必须解释清楚的地方。第一proxy_pass的 URI 拼接规则。当proxy_pass后面不带路径时比如http://127.0.0.1:8080;它会把完整的请求 URI 原样转发给后端如果带了路径比如http://127.0.0.1:8080/;location 中匹配到的部分会被替换。这是最容易出问题的地方很多人配置了半天发现后端收到的地址路径不对基本都是这个原因。第二proxy_set_header Host $host;这一行。很多后端应用会通过Host头来判断域名、生成跳转链接、做虚拟主机路由。如果你不把原始请求的 Host 传过去后端看到的可能是一串 IP 加端口甚至可能因此返回错误内容。这个头一定要显式设置。第三X-Forwarded-For。你的后端如果要做真实 IP 统计、做安全风控必须从这一长串 IP 里取最左边的那个才是用户的真实 IP。如果让 Nginx 默认转发后端拿到的$remote_addr只是 Nginx 的地址用户一来访问应用日志里全是同一个 IP排查问题会头大。3.3 反向代理在真实场景中的常用姿势除了常规的 API 代理反向代理最常见的还是用来解决跨域问题。浏览器禁止非同源的 AJAX 请求但你前端站点和接口服务域名不一致时就可以让 Nginx 在同一个 server 里分配一个/api/前缀把请求转发到另一个源的后端同时对浏览器来说请求仍然是同源的。这比在前端代码里写死跨域地址要优雅得多。另一个场景是前后端混布时的路径规划。比如你有一个站点用户访问/时看到的是前端页面访问/api/时实际请求转给 Java 服务访问/upload/时转给文件服务。这种情况下一个 server 块里配置多个 location 就能搞定。但切记多个 location 的匹配规则是有优先级的精确匹配优先级最高然后是^~前缀匹配再往后是正则匹配~和~*最后是普通前缀匹配。如果你写了一个正则 location和一个普通前缀 location正则会优先除非你用了^~。这个优先级不搞清楚配置出来的代理行为往往和你想的完全相反。还有一类反向代理是 WebSocket。如果你的后端是 Spring Boot WebSocket 或 Node.js 的 Socket.IO需要在代理配置里额外设置 Upgrade 头proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade;没有这两行WebSocket 连接会一次又一次地断开重连而且控制台看到的报错五花八门。我第一次调这个问题时还以为是后端代码的握手逻辑写错了排查了半天才发现是 Nginx 把升级协议的头给吃掉了。4. 负载均衡把请求分摊到多台后端4.1 upstream 块与最常用的调度策略反向代理解决了“一进一出”的转发问题但它只有一个后端节点。如果你的业务量上来了或者那一个后端节点挂了服务就不可用了。这时就要引入负载均衡用 upstream 定义一组后端服务器让 Nginx 按照某种策略把请求分发到这些服务器上。最基础的配置是下面这样upstream backend_pool { server 192.168.1.11:8080; server 192.168.1.12:8080; server 192.168.1.13:8080; } server { listen 80; server_name www.example.com; location / { proxy_pass http://backend_pool; } }Nginx 默认采用轮询策略每进来一个请求依次发给第一台、第二台、第三台然后再循环。这个策略在没有特殊需求时是效率最高的因为它天然把所有节点都打了出去。如果想让性能更好的机器多承担一些请求可以给 server 加 weight 参数upstream backend_pool { server 192.168.1.11:8080 weight3; server 192.168.1.12:8080 weight1; }权重这里是 3:1那么每 4 个请求里大约有 3 个会落在 1.11 上1 个落在 1.12 上。权重适合用在不同规格的服务器混跑的场景避免新买的 16 核机器和一堆 2 核小机器平摊流量造成资源浪费。还有一种策略是ip_hash也就是按客户端 IP 的哈希结果来分配后端。效果是同一个 IP 的请求永远会打到同一台服务器上适合后端有本地会话、又不好引入分布式缓存的情况。配置只需要在 upstream 中加一行upstream backend_pool { ip_hash; server 192.168.1.11:8080; server 192.168.1.12:8080; }不过ip_hash在高并发的时候会带来一个隐患如果某个 IP 段的流量特别大那一段请求会被集中到一台节点上导致其他节点闲着某一台却被打满。所以只有当业务确实需要会话保持时才建议用否则默认轮询加独立的会话存储才是最稳的。4.2 健康检查与故障节点剔除upstream 配置并不意味着后端挂了 Nginx 就一定能发现。在默认配置下Nginx 不会主动去探测后端是否健康它只是在转发请求后收到连接失败或者超时就认定这台服务器不可用。这个机制有一个问题只要连接还没有真正失败Nginx 还是会尝试把请求发给那个已经半死不活的节点导致用户偶发性地看到一个 502。生产级的做法是使用 Nginx Plus 或 OpenResty 里的主动健康检查模块但开源版 Nginx 也有一个简单的办法max_fails和fail_timeout。示例upstream backend_pool { server 192.168.1.11:8080 max_fails3 fail_timeout10s; server 192.168.1.12:8080 max_fails3 fail_timeout10s; }意思是在 10 秒的时间内如果这台服务器连续失败 3 次Nginx 就把它在接下来的 10 秒内标记为不可用不再往它发请求。这个参数不能解决“半故障”问题但它至少能在地毯式轰炸到一台坏节点时快速把它隔离出来让其他节点接手。如果你追求更精细的主动健康检查可以考虑在业务里引入 NGINX Plus或者用 Lua 在 OpenResty 里自己实现轮询 HTTP 探测。网格化服务治理框架里也会有类似功能但 Nginx 开源版能做的成度就已经能覆盖大多数中小团队的需求了。4.3 会话保持与后端无状态化负载均衡最让人头疼的一个话题就是会话。用户登录之后如果第一次请求被转发到了 1.11下次请求被转发到了 1.12而后端应用把登录状态存在了自己的内存 Session 里那用户就会被判定为未登录体验极差。解决思路有三个层次。最简单的就是用ip_hash或者根据 URL 参数来做哈希让同一个用户请求永远打到一个节点上。这种做法操作门槛低但缺点也明显一旦某个节点故障该节点上的所有会话都会丢失流量再切到其他节点时用户还是要重新登录。第二个层次是把 Session 从进程内存里搬出来放到 Redis、Memcached 这类独立的会话存储里。这样每个后端节点都可以处理任意请求负载均衡策略可以回到轮询容错性也更好。这种方式需要改应用代码但对团队来说是收益最长久的一步。第三个层次是彻底无状态化。不使用服务端 Session把用户身份保存在 JWT 之类的 token 里发到客户端存着每次请求携带 token后端只负责验签。这种方式下Nginx 只需要做纯粹的流量分发不需要考虑任何粘性策略前端可以自由扩容缩容。这也是现在微服务架构里最常见的选择。从我自己的经验看只要条件允许把状态剥离出服务节点永远比在 Nginx 层硬扛会话保持要强得多。5. 配置实践中的关键细节与性能调优5.1 worker 进程数和事件模型怎么调很多人以为 Nginx 性能调优就是把 worker_processes 设成 CPU 核心数其实这里面还有更细的讲究。worker_processes默认是 1如果你的服务器是多核默认配置就只能跑满一个核浪费资源。一般建议设为auto让 Nginx 自动识别 CPU 核数。不过进程数不是越大越好。worker 进程越多进程间切换的开销也越大而且每个 worker 都要有自己独立的内存空间连接池、定时器、缓存结构都会成倍增长。一个经验值是把 worker 进程数设为 CPU 核心数或者核心数乘以 1.5具体还要看你的业务模型是 CPU 密集还是 IO 密集。worker_connections决定每个 worker 能同时处理多少连接默认值是 1024。要算一下系统最大连接数可以用worker_processes * worker_connections。如果 Nginx 主要是做反向代理每个连接还会额外消耗两个文件描述符一个给客户端一个给后端所以还要考虑ulimit -n是否足够大。遇到大量too many open files报错时别急着调应用的线程数先看看系统文件描述符的上限。事件模型这里现在 Nginx 在 Linux 上默认就是 epoll一般情况下不需要显式配置。但有一个容易被忽视的指令是multi_accept把它设为on每个 worker 在收到通知后可以一次性接受多个新连接。高并发接入时这项设置能减少进程被唤醒的次数对提升吞吐量有帮助。5.2 开启缓存与压缩别让后端白白扛压力反向代理模式下Nginx 可以做一层内存缓存把后端的响应短暂保存下来相同的请求在缓存未失效时直接由 Nginx 返回后端压力会小很多。最简单的配置proxy_cache_path /var/cache/nginx levels1:2 keys_zonemy_cache:10m max_size1g inactive60m; proxy_cache_key $scheme$request_method$host$request_uri; server { location /api/ { proxy_cache my_cache; proxy_cache_valid 200 60s; proxy_cache_valid 404 1m; proxy_pass http://backend_pool; } }这套配置里proxy_cache_path定义了缓存文件的存放目录、内存中的 key 区域大小、缓存总大小限制以及 inactive 时间内没有被访问的缓存会被清理。proxy_cache_valid设置按响应码区分缓存时间。要注意动态请求的缓存克制一点比较好缓存时间太长会导致用户看到旧数据一般对读多写少、对时效不太敏感的接口开个几十秒或几分钟缓存是比较合理的。还有一个动手就能立刻见效的优化是 Gzip 压缩。虽然大家对它已经听腻了但很多项目上线后其实压根没开。在 http 层级或者 server 层级加gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript application/xml text/xml image/svgxml;gzip_comp_level不建议调到 9压缩比提升不大但 CPU 占用会直线上升5 是一个性价比比较高的档位。gzip_min_length 1k的意思是小于 1KB 的资源就不压缩了这种小体积资源压一下反而可能变大完全是浪费。5.3 日志与监控的忽略成本很多小团队部署完 Nginx就把它忘在角落等到线上出问题才想起来翻日志。但 Nginx 的 access_log 和 error_log 并不是写好了就完事的。默认的日志格式包含的信息太少了建议自定义把响应时间、上游地址、请求体的关键信息都加进去。比如log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for $request_time $upstream_response_time; access_log /var/log/nginx/access.log main;加上$upstream_response_time之后你就能一眼看出慢请求到底是网络延迟还是后端处理慢。如果 Nginx 的请求耗时小但 upstream 响应耗时大那瓶颈在后端如果 Nginx 本身耗时也大就要考虑带宽、SSL 握手、或 Nginx 自身配置问题。error_log 的级别也值得设置。生产环境默认是error如果你在调试阶段可以提高到debug但调试完一定要降回来否则日志文件会以惊人的速度膨胀。另外我习惯把log_not_found off;加上因为频繁扫描目录的 404 请求如果都写进日志既拖慢性能又把磁盘空间打满还干扰真实问题的排查。6. 常见问题与排查技巧实录6.1 高频报错速查表我整理了这几年遇到频率非常高的几个 Nginx 报错场景对应原因和解决思路都放在下面。报错信息常见原因解决思路connect() failed (111: Connection refused)后端服务没启动或者监听端口不对检查后端进程和端口监听情况确认 proxy_pass 的目标地址无误connect() failed (113: No route to host)网络不通或后端防火墙拦截检查网络连通性放通 Nginx 到后端端口的访问upstream timed out (110: Connection timed out)后端处理慢超过 proxy_read_timeout根据接口耗时调大 timeout或者定位后端慢的原因no live upstreams while connecting to upstreamupstream 中所有节点都被标记为不可用检查后端服务是否全部宕机清理健康检查状态Too many open files系统文件描述符不够或 worker_connections 设置太高调整 ulimit降低 worker_connections或增加复用措施[warn] conflicting server name配置文件里有两个相同 server_name检查各配置目录下的 server 块命名冲突directory index of ... is forbidden目录下没有 index 文件且 autoindex 未开启添加 index 文件或按需开启 autoindex错误日志是关键但是很多人遇到问题第一反应是重试一下或者翻搜索引擎我强烈建议你先看/var/log/nginx/error.log的最后几行。那里面经常会直接给出准确原因比瞎猜效率高很多倍。6.2 两个有点“反直觉”的排查案例分享一个我印象深刻的案例。有次一个接口偶尔出现 502频率不高但很随机。后端日志里看不到任何报错进程也一直是活的。后来我开了一层抓包发现 Nginx 和后端之间会有极少量的 RST 包。排查下来原因是后端应用在响应完成后主动关闭了连接而 Nginx 默认使用短连接每次转发完就关掉正常情况下没问题。但那台机器的 Keep-Alive 配置在应用层出了问题导致连接在复用过程中被对端重置。最终的解法是把proxy_http_version设置为 1.1并加上proxy_set_header Connection ;让 Nginx 对后端复用长连接问题就消失了。另一个是权重不均的问题。配置里两个后端权重是 1:1但运维统计后发现某一台服务器收到的请求量明显多于另一台。原因是这两台服务器的响应速度差异很大Nginx 默认的轮询虽然能保证请求次数均衡但它不会因为你某台机器响应快就多分配流量。如果后端节点性能差异很大轮询导致快节点一直在空转慢节点却在排队。这时候应该根据实际负载重新设置 weight或者干脆让更可靠的节点承接更多连接别迷信绝对平均。6.3 配置改坏了怎么快速回滚谁都踩过改配置时手滑导致 Nginx 无法启动的坑。想避免事故务必做到两步验证。第一步每次改配置之前先备份一份当前可用的配置cp -r /etc/nginx /etc/nginx.bak.$(date %Y%m%d%H%M)第二步修改完成后测试配置是否合法nginx -t这条命令会告诉你配置语法有没有问题以及具体哪个文件的第几行报错。测试通过之后再执行重载nginx -s reloadreload是一个平滑重载worker 进程会先完成正在处理的请求再退出新配置会逐步接管不会造成请求中断。很多人图省事直接执行nginx -s stop这在流量比较低的时候问题不大但线上并发高时突然 stop 会把所有活跃连接全部切断用户会看到大量连接重置。所以记住只要不是必须重启尽量用reload。7. 一些容易被忽略的个人实操体会配置 Nginx 这几年我自己最大的感受就是它的大部分问题都不是“不懂原理”导致的而是“习惯于复制粘贴”导致的。网上随便一段配置拿出来看起来挺像回事但未必适合你的场景。比如我发现很多人把try_files写在了一个不是处理前端路由的 location 里导致某些接口请求被意外指向了index.html又比如有人图省事把proxy_buffering off;到处乱加结果后端响应直接裸奔到客户端Nginx 一个字节一个字节地转发性能大打折扣。还有一个心得是尽量把 Nginx 配置当成代码来管理。用 include 拆分配置文件把每一段 upstream、每一个 server 单独放一个文件在仓库里维护一套模板用 CI/CD 去渲染和发布。别看这个基础它能避免非常多由半手工修改导致的低级错误。每次发布前先nginx -t再 reload出了事也能快速回滚。这套流程我用了很多年可靠性很高。最后说下学习顺序。如果你想彻底吃透 Nginx不要一上来就啃那些复杂的 rewrite 正则。先把 root 和 alias 彻底搞明白再把 proxy_pass 的路径拼接逻辑记住然后动手搭一个真实的负载均衡环境亲手把一台后端节点停掉观察 Nginx 的日志和转移行为。摸清这三个核心流程之后像限流、灰度、缓存、动态模块这些东西都会变得好理解得多。希望这篇内容能帮你把基础打牢少走一些我当年走过的弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询