资讯动态

Nginx入门到部署:反向代理、负载均衡与静态资源实战

发布时间:2026/10/15 5:24:48 来源:尧图企业网站定制
先说我经历过的一个真实场景。我们团队当时把一个前后端分离的项目部署上线后端接口跑在 8080 端口前端打包出来的静态文件也扔在同一台服务器上。最开始图省事直接用 Node.js 把静态文件和接口一起处理结果用户量稍微上来一点CPU 直接飙到 90%日志里密密麻麻全是图片、CSS、JS 的请求。后来把 Nginx 请过来干三件事——接管静态资源、反代后端接口、再往下做多节点负载均衡服务器安静得像是没跑东西。这篇东西就是围绕 Nginx 入门到项目部署整理的核心锁定在反向代理、负载均衡、静态资源分发这三个方向。你不需要有运维基础哪怕是第一次接触服务器配置跟着章节往下走也能把一个真实项目完整地挂到公网上。我会把配置文件逐行解释清楚把每一步为什么这么写讲明白再把部署之后最容易踩的坑提前给你打好预防针。这套内容适合后端开发、前端开发、独立开发者也适合那些刚接手服务器、被“502 Bad Gateway”教育过的准运维同学。1. Nginx 在这台服务器里到底扮演什么角色1.1 一条请求链路看明白 Nginx 的位置先别急着敲命令脑子里先有一个画面。用户在浏览器里输入你的站点地址回车之后请求进入服务器落到 80 端口。这时候谁在 80 端口盯着如果没装 Nginx可能是一个开发服务器顺手监听如果装了 Nginx它就成了唯一的守门员。这个守门员处理请求的路径大致是这样的用户浏览器 | v Nginx80端口 / 443端口 |-- 静态资源请求 -- 服务器磁盘上的 html/css/js/png |-- 接口请求 -- 反向代理 -- 后端服务192.168.1.11:8080 等 |-- 动态转发 -- 负载均衡 -- 多台后端节点分摊Nginx 看请求是有分工意识的。请求的是静态文件它直接从磁盘读出来返回速度极快请求的是复杂业务它再转发给后面真正的应用服务处理拿到结果后原样喂回给用户。这种“扛流量但不干重活”的设计恰恰是生产环境中最合理的一种分工模式。1.2 三个关键词对应三种能力标题里那三个词其实就是 Nginx 最核心的三项技能我分别用大白话解释一下。反向代理相当于公司前台。你想找技术部的人不需要知道技术部在哪个工位直接去前台说需求前台帮你把话带到再把答复带回来给你。用户请求的也是同一个道理——用户只知道 80 端口并不知道你的后端服务跑在 8080 还是 3000Nginx 把请求接收下来转发给真正干活的进程再把结果返回给用户。这个过程对用户完全透明同时顺带把后端服务保护了起来。负载均衡相当于食堂开了两个甚至更多窗口。排队买单的人多了一个窗口肯定被压垮多开几个窗口分摊流量每个人等待时间都短了。Nginx 把请求按策略分发到一组后端服务器上避免单台机器被流量打爆。静态资源服务这个用途最初级也最常用相当于直接去仓库提货。图片、CSS、JS、字体这些不经常变化的东西本来就适合像货架一样摆好谁要直接取。Nginx 做这件事极其擅长因为它的事件驱动模型在处理高并发静态请求时非常高效。1.3 为什么不是直接让应用服务跑到底可能有人会问我的 Node.js 或者 Java 服务不也能输出静态文件吗为什么非要中间横着一个 Nginx理论上确实可以但实际生产环境中有一堆现实问题。应用服务在处理静态文件时每个请求都要占用一层业务逻辑的处理能力哪怕这个请求只是读取一张照片。当并发上来业务进程被静态请求挤占资源真正需要算力的接口请求就变慢了。Nginx 负责把静态流量终结在最前面一层应用服务专心处理 API各干各的最擅长的事情整体吞吐量完全是两个量级。再看安全性。应用服务直接暴露给公网等于把实现细节全展示出去了攻击者能直接试探框架漏洞。Nginx 挡在外面之后可以统一做限流、控制超时、处理跨域、过滤恶意请求底层服务对访问者来说是隐身的。和传统 Apache 相比Nginx 在并发连接处理上走的是事件驱动异步模型内存占用小高并发下的表现更加稳定。Apache 的模块化配置也有它的优势但如果是新项目从零选型Nginx 几乎是默认答案。简单总结一下对比维度NginxApache应用内置静态文件服务高并发静态请求能力很强一般较差配置复杂度中等清晰较高最低反向代理能力原生支持需要模块部分支持与业务隔离程度完全隔离完全隔离不隔离典型应用场景网关、静态、代理、负载传统托管场景开发调试、微型应用2. 安装和配置结构先把地基打好2.1 不同环境里装 Nginx 的方式安装这件事本身不复杂常见就这几种方式按需求选择。Debian/Ubuntu 系直接用包管理器sudo apt update sudo apt install nginx -y装完自带 systemd 管理脚本直接启动并设置开机自启sudo systemctl enable nginx sudo systemctl start nginxRHEL/CentOS 9 及以下版本用的包管理器是 yum配置文件默认路径略有不同但整体结构一致。如果是 CentOS Stream 或 Rocky Linux直接sudo dnf install nginx -y sudo systemctl enable --now nginx某些场景下比如要编译进自定义模块、或者操作系统版本很老、包管理器里没有合适版本可以用源码编译。这个步骤稍微繁琐要装编译工具和依赖库一般业务场景不太需要建议优先用系统包管理器方案维护升级都省事。Docker 环境里部署也非常常见。一条命令拉起容器把配置目录和站点目录挂载出来即可docker run -d \ --name nginx-server \ -p 80:80 \ -v /data/www:/usr/share/nginx/html \ -v /data/nginx/conf:/etc/nginx/conf.d \ nginx:1.26-alpine装完之后确认一下版本顺手验证服务是否在跑nginx -v curl -I http://localhost能正常返回 HTTP 响应头说明 Nginx 已经进入工作状态。2.2 配置文件长什么样区块怎么划分Nginx 的配置本质是一个层级化的指令集合。绝大多数发行版的配置文件入口都是/etc/nginx/nginx.conf核心结构是三个块main全局配置、events事件处理模型、httpHTTP 服务配置。一个最小化的 nginx.conf 骨架长这样# main 区块进程、日志、运行用户等全局设置 user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; # events 区块每个 worker 进程能同时处理的连接数 events { worker_connections 1024; } # http 区块真正跟站点业务相关的配置全在这里 http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # 站点配置通过 include 引入按域名/项目拆分文件 include /etc/nginx/conf.d/*.conf; }mime.types这个文件很多人容易忽略。它声明了文件扩展名和 Content-Type 的映射关系不引入的话浏览器很可能把 CSS 当成文本下载或者页面样式完全丢失。配置里必须保留这一行 include。站点文件建议不要全部堆在 nginx.conf 里面而是按项目拆分。Ubuntu 的惯例是写在/etc/nginx/sites-available/里然后软链到sites-enabled/CentOS 系的惯例是直接放在/etc/nginx/conf.d/下。两种风格都可以核心逻辑一致一个 server 块对应一个站点项目互相隔离改一个不影响另一个。2.3 改配置的正确姿势Nginx 配置改完不能直接“重启大法好”生产环境里这是大忌。一次systemctl restart nginx会让所有当前连接瞬间断开如果是线上服务等于主动制造两秒不可用。正确姿势是先校验配置有没有语法错误再平滑重载。nginx -t nginx -s reloadnginx -t会检查全部配置文件语法如果输出syntax is ok说明没问题。nginx -s reload做的事情是给 master 进程发送信号让它用新配置重新拉起 worker 进程同时优雅地回收旧连接正在处理的请求不会被强行切断。我见过太多新手在 VIM 里改完配置就直接service nginx restart然后线上所有正在下载文件、正在管理等接口响应的用户全被踢掉。记住一个原则平时操作只要不是大版本升级永远用reload别用restart。另外养成习惯Nginx 配置文件备份成本极低改动前复制一份到.bak出了奇怪问题还能快速回滚。这是我踩过几次坑之后养成的肌肉记忆。3. 静态资源部署把网页稳稳地吐出去3.1 第一个 server 块让 IP 打开一个页面安装完之后Nginx 自带一个默认站点。打开浏览器输服务器 IP能看到的欢迎页。接下来就把它替换成我们自己的项目页面。在conf.d/里新建一个myweb.conf写一个最小的静态站点配置server { listen 80; server_name www.example.com example.com; root /data/www/myweb; index index.html; location / { try_files $uri $uri/ 404; } }拆开解释一下listen 80监听 80 端口。server_name基于域名区分虚拟主机。可以写域名的多种形式也可以直接写作_表示匹配所有未匹配到的请求。root网站文件根目录请求进来之后所有文件都在这个目录下查找。index访问根路径时默认加载的文件。try_files依次尝试访问$uri找到就用找不到再尝试目录下的默认文件都没有就返回 404。这一套组合非常常用。$uri是 Nginx 变量表示当前请求的文件路径。比如用户访问http://www.example.com/Nginx 就去/data/www/myweb/index.html找文件找到后加合适的 Content-Type 返回。3.2 生产环境里文件放在哪静态文件不要直接放在/root目录下也不建议扔在/home的用户主目录里。原因很实际Nginx 的 worker 进程运行在一个低权限用户下默认是nginx或者www-data这些用户对/root这种目录天然没有访问权限权限链条如果中间断了一环就会看到 403。我踩过一次很典型的坑把整个项目解压到了/root/project配置写成root /root/project;访问任何路径都返回 403。排查了半天才反应过来是/root目录本身权限是 700Nginx 用户根本进不去。生产环境推荐的目录规划长这样/data/www/myweb/ # 前端静态文件目录 /data/www/myweb/assets/ # 打包产物里有 hash 的静态文件 /var/log/nginx/ # Nginx 日志目录文件放进去之后把归属权理顺sudo mkdir -p /data/www/myweb sudo chown -R nginx:nginx /data/www/myweb sudo chmod -R 755 /data/www/myweb这里注意目录权限 755、文件权限 644 是安全且不会出问题的组合。运行用户对目录有读和执行权限即可不必给写权限除非你的业务需要它写文件。3.3 缓存策略让第一个人开荒后面所有人吃现成静态资源的特点是“在一定时间内内容不变”所以很适合让浏览器缓存。expires指令就是干这个的location /assets/ { alias /data/www/myweb/assets/; expires 30d; add_header Cache-Control public; }配置完的效果是浏览器第一次访问以后30 天内再请求这些资源直接从本地缓存读取不再向服务器发起网络请求。对于图片、字体这类几乎不变化的资源缓存时间可以放到更长比如 90 天。但要小心CSS 和 JS 文件不能无脑长时间缓存。如果你的前端项目没有使用带 hash 的文件名打包改了一行代码用户那边却被本地缓存挡住了旧的版本一直看到打不开的页面。解决方式有两种项目打包时开启内容哈希比如main.a1b2c3.js文件内容变了哈希就变此时可以放心设置超长缓存项目没有 hash 文件名时CSS/JS 的缓存时间控制在分钟级或者小时级宁短勿长。还有一种情况明明改完配置已经reload了但用户死咬着说“页面没更新”。不要怀疑 Nginx先让用户在浏览器里强制刷新或者用无痕窗口。多数时候是浏览器缓存或者本地 CDN 缓存的问题跟服务器配置没有关系。3.4 顺手开启 Gzip 压缩文本文件在网络传输时压缩一下体积能减少 60% 以上。Nginx 的 gzip 模块默认就编译进去了只需要打开开关。gzip on; gzip_types text/plain text/css application/json application/javascript application/xml image/svgxml; gzip_min_length 1k; gzip_comp_level 5; gzip_vary on;参数逐一说清楚gzip on开启压缩。gzip_types指定哪些 MIME 类型需要压缩。注意这里不需要写text/htmlNginx 默认就会压缩 HTML。gzip_min_length 1k小于 1KB 的文件不压缩。压缩本身也有开销特别小的文件压完可能体积更大是负优化。gzip_comp_level 5压缩级别1 到 9。级别越高文件越小、CPU 消耗越高。5 是平衡点我用 5 级别没有遇到过性能问题。调到 9 反而会让高流量场景下 CPU 压力变大收益却非常有限。gzip_vary on加Vary: Accept-Encoding请求头处理带代理和 CDN 的环境时能避免缓存错乱。图片jpg/png/gif/webp、视频、压缩包本身就是压缩过的二进制内容就不用再压了。SVG 特殊一点是文本格式虽然扩展名是图片类但压缩效果明显值得放进 gzip_types。4. 反向代理让前端丝滑地调后端口4.1 场景和最小可用配置静态页面已经能访问了页面里的按钮一点要向后端发起接口请求。前端这个项目跑在 http://localhost 上后端 API 监听在 8080。直接在浏览器里请求http://服务器IP:8080/api/user/list会出现两个问题跨域被浏览器拦截端口暴露给公网既有安全风险又不优雅。解决方案就是反向代理。在原来的 server 块里加一个 locationserver { listen 80; server_name www.example.com; root /data/www/myweb; index index.html; 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; } }这段配置的意思是请求地址如果是http://www.example.com/api/user/listNginx 会把整个请求完整转发给http://127.0.0.1:8080后端拿到的请求路径仍然是/api/user/list。前端代码只需要把请求统一发到/api开头其他什么都不用管。4.2 proxy_pass 末尾斜杠的坑必须单独说这个坑我见很多人掉进去过而且一旦掉进去问题表现得很隐蔽。区别就在proxy_pass最后有没有那个/。# 写法一不带斜杠 location /api/ { proxy_pass http://127.0.0.1:8080; } # 请求 /api/user/list - 后端收到 /api/user/list完整路径保留 # 写法二带斜杠 location /api/ { proxy_pass http://127.0.0.1:8080/; } # 请求 /api/user/list - 后端收到 /user/listlocation 匹配部分被替换是不是有点迷糊列个对照表就清楚了请求路径proxy_pass 写法后端真实收到路径/api/user/listhttp://127.0.0.1:8080/api/user/list/api/user/listhttp://127.0.0.1:8080//user/list/api/user/listhttp://127.0.0.1:8080/prefix//prefix/user/list规则记一句话proxy_pass后面只要带了 URI也就是/开头的部分就会把 location 匹配到的那段路径替换掉不带 URI就完整保留原始路径。如果你的后端接口设计是/api开头的用写法一就行。如果后端接口自己要暴露为根路径或者前端转发路径和后端实际路径不一致才需要用到带斜杠的替换写法和rewrite配合。最怕的是项目里一会儿带斜杠一会儿不带接口时通时不通查起来特别费劲。我在配置里通常统一保持不带斜杠的写法再用 rewrite 处理路径变更需求减少认知负担。4.3 必带三个 header原因分别说明反向代理转发时Nginx 默认只转发基本请求信息真实客户端 IP 会丢掉后端看到的全是 Nginx 服务器地址。这就带来一堆问题因此标准配置是下面四条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;Host $host让后端知道用户访问的是哪个域名。有些应用生成自动跳转链接和 Cookie 域名时依赖这个头。X-Real-IP $remote_addr直接用户真实 IP 传给后端。X-Forwarded-For记录完整链路里每一层的 IP多级代理时 XFF 会累积后端日志里能看出真实来源。X-Forwarded-Proto告诉后端用户用的是 HTTP 还是 HTTPS。应用里做 HTTPS 强制跳转或安全 Cookie 时依赖这个头。做负载均衡的时候如果后端要按 IP 做限流或者记录访问日志这一组 header 不配全后面接业务的同事大概率会来问你“为什么日志里的 IP 全是 127.0.0.1”4.4 顺手解决跨域问题前后端分离的项目跨域几乎是标配问题。页面上在http://www.example.com后端接口却要通过http://www.example.com/api访问。按照同源策略只要协议、域名、端口有一个不一致浏览器就会拦下来。因为我们已经做了反向代理浏览器以为请求的是同一个域名跨域问题其实已经解决了一大半。只有在调用第三方接口或者前端工程启动在另一个端口的开发阶段才需要处理跨域。这时在 Nginx 转发段加响应头location /api/ { proxy_pass http://127.0.0.1:8080; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS; add_header Access-Control-Allow-Headers Authorization, Content-Type; }带OPTIONS的预检请求比较特殊。浏览器在正式发送跨域请求前会试探性地发一个OPTIONS请求问服务器“允许这么跨吗”Nginx 经常直接把预检请求当成正常请求转发给后端而后端接口往往没有处理 OPTIONS 方法的逻辑导致预检直接 404。如果出现这种情况在 Nginx 层单独终结它就行location /api/ { if ($request_method OPTIONS) { return 204; } proxy_pass http://127.0.0.1:8080; }这个方法实测非常稳预检请求被 Nginx 直接放行后端只需要处理业务请求。4.5 需要转发 WebSocket 时加这两行现在很多项目有即时聊天、消息推送、协同编辑这些功能在 Web 端基本上就是 WebSocket。WebSocket 和普通 HTTP 请求一样走 Nginx 代理但要额外处理升级协议的问题。在反向代理块里加两个 headerlocation /ws/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_http_version 1.1; proxy_read_timeout 3600s; }Upgrade和Connection upgrade这两个头告诉客户端和服务器全面切换到 WebSocket 协议。proxy_http_version 1.1也很关键HTTP/1.0 不支持长连接。最后那个proxy_read_timeout默认 60 秒就不回话会断连聊天类需求必须调长不然聊到一半连接就断了前端自动重连时会有一堆麻烦。5. 负载均衡让多台后端一起扛流量5.1 upstream 块与三种内置策略单台后端扛不住流量了就需要横向扩容再加一两台服务器跑同样的后端代码。Nginx 把这组后端服务器定义成一个“服务池”再用upstream块把所有节点聚合起来。upstream backend_servers { server 192.168.1.11:8080 max_fails2 fail_timeout30s; server 192.168.1.12:8080 max_fails2 fail_timeout30s; server 192.168.1.13:8080 backup; } server { # ... location /api/ { proxy_pass http://backend_servers; 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_pass里的地址从http://127.0.0.1:8080换成了http://backend_servers这个名称对应上面upstream定义的组名。请求到达之后Nginx 会在池子里挑一台分出去。Nginx 内置了四种负载均衡策略生产环境常用的主要以下几种策略配置方式行为特征适用场景轮询默认不写请求按顺序轮流分配每台权重一样节点配置相同的通用场景权重server x weight3;按比例分配weight 越高分到的越多新老服务器混用性能强的多扛流量最小连接least_conn;指令分给当前活动连接最少的节点长连接或请求耗时不均的场景IP 哈希ip_hash;指令同一来源 IP 固定到同一节点依赖 Session 的旧架构轮询最省心适合所有后端实例性能接近的情况。权重适合混布架构比如一台是 8 核 16G一台是 4 核 8G前者权重设为 3后者设为 1流量才能按真实容量均匀分配。最小连接适合处理时间波动大的接口有请求在处理时就优先分给闲的节点。ip_hash比较特殊它用客户端 IP 算哈希让同一个用户每次都进同一台后端这样 Session 可以保持在单机上。没有绝对完美的策略跟随业务模型调整。我一般先无脑轮询观察一段时间监控数据后端耗时曲线有明显波动再做策略调整。5.2 Session 保持与 ip_hash 的局限传统的服务端 Session 方案默认就是存在单台服务器的内存里。负载均衡最发愁的事用户第一次请求被分到 A 机器登录状态存在 A 上第二次请求被分到 B 机器B 上没有这个 Session用户就被踢回登录页了。ip_hash能让同一个 IP 始终落在同一台机器看起来解决了问题。但现实中这个方案的天花板很低用户可能从不同 IP 访问比如手机流量和 Wi-Fi 转换某个节点挂掉时原本落在它身上的请求全部被重新分配到其他节点Session 照样失效。真正工业化、没毛病的方案是中心化 Session 存储把 Session 状态抽出来放到独立的 Redis 或 MySQL 里所有后端节点共享这一份状态任意节点都能处理任意用户的请求。Nginx 层只管把流量尽可能均匀地散出去不需要费心“认人”。这个思路现在基本是标配了。如果你的项目已经升级到 JWT 这种无状态认证体系后端集群天然不依赖节点亲缘性那ip_hash基本可以完全不用直接轮询最通透。5.3 节点健康检查你不主动测Nginx 会默默帮你测配置里那两个参数max_fails2和fail_timeout30s就是节点健康检查的核心。原理是 Nginx 会与后端节点保持 TCP 连接级别的探测连续 2 次转发失败后这个节点被标记为不可用随后 30 秒内不再向它分发新请求。30 秒后 Nginx 会重新试探如果恢复则重新纳入分发队列。这是被动健康检查机制——平时不打招呼出错了才“拉黑”。主动健康检查指的是 Nginx 定时周期性地访问某个健康检查地址主动探测服务状态。这个功能在开源版里默认没有只有商业版 Nginx Plus 才带。开源场景下的标准做法是依赖被动检查同时配合外部监控系统对后端集群做探活比如拨测某台机器的/health接口机器异常就从负载池里摘除恢复后再放进去。外部监控加被动检查组合起来覆盖绝大多数场景已经足够。5.4 动静分离让每一项资源处理在最擅长的层级负载均衡不止可以按流量分也可以按内容类型分。这就是“动静分离”的思路。前端页面、图片、CSS、JS 这些静态内容由 Nginx 直接从磁盘返回不往后端转发后端只承担/api这类动态请求。配置写起来并不复杂server { listen 80; server_name www.example.com; root /data/www/myweb; index index.html; # 静态资源直接由 Nginx 返回设置缓存 location /assets/ { expires 30d; add_header Cache-Control public; } # 动态请求转发给后端负载均衡池 location /api/ { proxy_pass http://backend_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }动静分离和负载均衡是两种互补的手段。前者按请求类型分流后者按后端节点分摊。实践中通常一起使用。后端业务只处理纯逻辑吞吐量要求和后端机器配置都可控可评估整体架构的稳定性明显提升。运维同学最直观的感受是后端监控里 CPU 曲线变得非常干净流量高峰也不会看到密密麻麻的静态请求挂在那里。6. 顺手调优与安全加固6.1 全局性能参数怎么设置Nginx 官方推荐每个 CPU 核心对应一个 worker 进程这样多进程可以充分利用多核。现在写worker_processes auto;就能自动识别核数不需要手动数。如果你的服务器是 8 核auto 会自动设为 8。太多或太少都不好太少 CPU 跑不满太多会平白消耗内存和切换开销。worker_connections控制每个 worker 最多同时保持 1024 个连接。这个数值不是越大越好它受限于系统文件描述符上限。理论最大并发连接数是worker_processes * worker_connections但还要考虑每个连接本身占用的文件描述符和内存资源。常见的温和配置是worker_processes auto; events { worker_connections 2048; }如果服务器资源充足2048 是安全且够用的数值。除非你明确知道自己在干什么否则不建议盲目堆到 65535。6.2 超时参数也要管一管反向代理场景中Nginx 与后端之间的连接存在各种超时时间。业务慢不代表 Nginx 有问题很多时候就是超时时间卡得太短后端在写一条慢 SQLNginx 已经断开了连接。proxy_connect_timeout 5s; proxy_read_timeout 60s; proxy_send_timeout 60s;proxy_connect_timeout建立与后端的 TCP 连接超时时间内网环境 3 到 5 秒足够。proxy_read_timeout从后端读取响应的超时时间。后端接口本身处理时长超过 60 秒的场景需要长取值比如文件导出、批量计算。如果用户上传大文件后需要一个较长的处理周期这个值要相应放宽。proxy_send_timeout把请求发送给后端的超时时间常规场景 60 秒足够。调试阶段可以先全部放宽等业务稳定后逐步收紧。一开始就把超时压得很死会让排查问题非常痛苦因为错误日志里清一色全是 timeout难以区分是网络问题、程序问题还是配置问题。6.3 几个低成本高收益的安全项不需要复杂的防火墙规则先做这几点绝大多数基础攻击就能挡住大半。隐藏版本号server_tokens off;这会让 Nginx 错误页和响应头里不再出现具体版本号避免对方搜到对应版本的已知漏洞去找攻击思路。补充安全响应头add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header Referrer-Policy strict-origin-when-cross-origin always;X-Frame-Options防止页面被嵌进别人的 iframe 里做点击劫持。X-Content-Type-Options能防一些浏览器勤快做的 MIME 嗅探。Referrer-Policy控制跨域跳转时 Referrer 头泄露的信息量。这三行配上后页面行为不会有任何明显变化但安全性会好一个层级。限制上传体大小client_max_body_size 20m;不配的话默认是 1m用户上传超过 1MB 的文件会直接被 Nginx 拒绝。反过来如果业务根本没有上传需求或者只提交表单保持默认的 1m 反而是一种保护。对接口做基本限流limit_req_zone $binary_remote_addr zoneapi_limit:10m rate10r/s; location /api/ { limit_req zoneapi_limit burst20 nodelay; proxy_pass http://backend_servers; }这个配置的含义是每个客户端 IP 每秒可以请求 10 次突发可以到 30 次但不排队。防攻击效果谈不上固若金汤但能把大多数脚本刷接口的行为按在地上摩擦一下。$binary_remote_addr是按 IP 做维度计数省内存IPv4 的 IP 只占固定字节。10m 的共享内存空间足够保存十几万个 IP 的状态普通业务绰绰有余。7. 部署之后这些坑你大概率会遇到7.1 502 / 504 该怎么排查502 Bad Gateway 是反向代理场景里最常见的报错含义是Nginx 转发失败了它没能从后端拿到有效响应。原因分成了几大类。排查顺序从轻到重先看后端进程本身有没有活着。Java 服务崩了、Node 进程退出了Nginx 再急也没用。ps aux | grep java看一眼。接着看端口监听状态ss -lntp | grep 8080如果后端监听在 127.0.0.1而 Nginx 反代写的是服务器内网 IP也会连不上。然后看本地能不能通curl http://127.0.0.1:8080/health后端进程在自己机器上是否能正常响应。最后看防火墙和安全组云服务器的安全组规则没放行后端端口、或者本机 iptables 配置错误都会导致反复 502。常见的 Nginx 错误日志记录格式长这样[error] 12345#0: *678 connect() failed (111: Connection refused) while connecting to upstream111 Connection refused通常是端口没监听110 Connection timed out通常是防火墙规则拦截或者后端挂死。504 Gateway Timeout 的含义是连接上了但后端在超时时间内没有返回结果。优先检查后端接口自身耗时再看proxy_read_timeout是否偏短。我处理过一个典型的案例导出报表接口正常要跑一分半默认 60 秒的读取超时直接把下载请求断掉前端展示为 504。把超时设置调到 300 秒后问题消失。如果后端是因为死循环或被锁阻塞调超时就不治本只能作为临时手段重点还是要盯后端日志。7.2 404 / 403 的常见猫腻静态文件 404 和 403 很多时候不是文件真的不存在而是路径对不上。root与alias的区别是所有新手绕不过去的弯。root会完整拼接 location 路径比如location /img/ { root /data/www; } # 请求 /img/a.png - 实际读取 /data/www/img/a.png而alias是替换比如location /img/ { alias /data/www/pictures/; } # 请求 /img/a.png - 实际读取 /data/www/pictures/a.png配错了root和alias表现就是文件明明存在但 404。这种问题特征非常明显一查路径拼接就能发现问题。前端部署了 Vue / React 的 history 路由时直接刷新某个子路由页面会出现 404这是因为 Nginx 在磁盘上找不到对应的真实文件。路由是前端的事服务端只需要做一次兜底location / { try_files $uri $uri/ /index.html; }这样任何不存在的路径都回到index.html由前端路由自行渲染。注意这个配置要和静态资源缓存合理共处不要把/assets/也兜回 index。403 更多是权限问题。Nginx 工作进程用户对文件没有读权限、对目录没有搜索权限最常见的原因是文件放在了/root或者/home/用户下。确认一下nginx用户对目录链路上每一级目录都有x权限。如果使用了 SELinux还要检查安全上下文是否放行ls -Z看文件类型是否为httpd_sys_content_t不是就执行chcon -t httpd_sys_content_t /data/www修正。7.3 静态资源不更新 / 页面样式突然全乱页面样式突然全乱排查优先看开发者工具里 CSS 文件是否带出了不正确的 Content-Type。如果是text/plain多半是mime.types没有 include 进来或者自定义 location 时把默认的 mime 处理覆盖掉了。Nginx 配置里include /etc/nginx/mime.types必须存在这行删了所有样式文件都会被浏览器当成纯文本页面直接就“裸奔”了。资源不更新上面第三章提到过是缓存策略的问题。如果改了代码但浏览器一直拿旧版第一件事检查资源请求响应头里的Cache-Control和Expires。项目没有 hash 文件名时需要让 CSS/JS 的缓存时间设短有 hash 文件名时超长缓存反而是最优策略。还有一种隐蔽情况你定的缓存策略没问题但是中间套了一层 CDNCDN 节点缓存了旧文件。这时 Nginx 层挟持无力了需要去 CDN 控制台做缓存刷新或配置规则调整。前端打包时的版本号参数或者加 query 参数是处理这种场景最简单粗暴却有效的偏方。7.4 日志是排查问题的第一现场配置再多问题最终都能在日志里找到线索。access.log是访问日志记录每一次请求的 IP、时间、路径、状态码、耗时。error.log是错误日志记录 Nginx 运行过程中发生的连接错误、超时、权限问题等。这两者配合排查问题效率至少翻一倍。默认日志路径在不同系统略有差异Ubuntu 通常在/var/log/nginx/access.log和/var/log/nginx/error.log。实时跟踪看哪个接口报错tail -f /var/log/nginx/access.log | grep 502 tail -f /var/log/nginx/error.log很多问题如果只盯着状态码猜像大海捞针。比如前后端接口联调失败前端说“接口 404”后端说“我没收到请求”这时候去看 Nginx 的 access.log一条请求记录就会告诉你 Nginx 把请求转发到哪了、结果是什么双方职责清清楚楚。为了方便排查链路真实来源自定义日志格式是很有价值的。在 http 块里定义log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for; access_log /var/log/nginx/access.log main;这样日志里能看到客户端真实 IP 和 XFF。尤其负载均衡多级代理之后$remote_addr可能只是上一级 Nginx 的地址$http_x_forwarded_for里才有用户最初的真身。7.5 常用命令速查最后整理一份干活时经常敲的命令直接复制使用用途命令测试配置语法nginx -t平滑重载配置nginx -s reload查看运行状态systemctl status nginx查看端口监听ss -lntp | grep nginx实时跟踪错误日志tail -f /var/log/nginx/error.log实时跟踪访问日志并按 5xx 过滤tail -f /var/log/nginx/access.log | grep 50下载文件测转发效率curl -I http://example.com/file.iso模拟客户端看响应头curl -I -H Accept-Encoding: gzip http://example.com/最后分享一个我个人的习惯每配置完一台新服务器的 Nginx第一时间打开 error log然后人为制造一个错误比如访问一个不存在的路径再回去看日志文件里有没有对应记录。这个步骤花不到一分钟但能帮你确认日志路径正确、日志权限正常、搜索引擎能匹配到问题。很多线上事故所谓“查不出原因”本质上都是日志链路没有提前验证好到了真正需要日志救命的时候才发现写不进去或者路径不对。把这一步做扎实了Nginx 这条线就算稳了。后续深入的话还可以继续研究 rewrite 规则、HTTP/2 / HTTP/3、TLS 证书自动续期、灰度发布、基于 njs 做请求改造这些方向每一块展开都是独立的长文到时候再接着聊。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑