
1. 状态码Nginx 排错的第一步也是很多人忽略掉的第一步先说个真实场景。前段时间有个朋友找我说他的网站突然打不开了给我发了张截图——浏览器里白屏按 F12 看到一堆 502 和 504。他第一反应是代码出问题了把后端项目重启了好几遍结果毫无变化。最后我远程上去看了一眼Nginx 配置里的proxy_pass指向的端口写错了上游服务根本没在这个端口上监听。这种问题状态码早就告诉你了但你得先看懂它在说什么。状态码是 Nginx 和浏览器之间对话的语言。入门阶段很多人把精力都花在背指令上觉得能配出一个能跑的 server 块就算学会了。但实际工作中绝大多数 Nginx 相关的问题排查第一步都是看状态码。状态码能帮你把问题快速定位到两个大方向请求有没有到达 Nginx如果到了是 Nginx 自己拒绝的还是它背后的上游服务出了问题方向对了排查就快了一半。1.1 常见状态码的含义与真实场景200 和 404 这种大家都认识我不多说重点讲几个实战里你一定会碰到、但可能一直没搞透的状态码。301 和 302重定向301 是永久重定向302 是临时重定向。最常见的场景就是 HTTP 跳 HTTPS。你配置了证书之后希望用户访问http://example.com时自动跳到https://example.com那就得靠 rewrite 或者 return 指令返回 301。还有一个常见场景是域名调整老域名要迁到新域名返回 301 是最佳实践——它不只是让浏览器跳转还会让搜索引擎把权重转移到新地址。这里有个容易踩的坑301 是会被浏览器永久缓存的。如果你的配置写错了返回了不该返回的 301用户浏览器会记住这个跳转你改了配置之后用户那边可能还是会被跳走。尤其是 Chrome这个缓存非常顽固。我在实际调试中经常被这个问题坑到后来学乖了在做域名级跳转实验时要么用 302 先验证要么开一个无痕窗口测试要么直接在开发者工具里把“Disable cache”打开。实践下来最稳妥的方案是先用 302 验证跳转规则对不对确认无误后再切成 301。403没有权限访问看到 403先别急着怀疑文件权限。先检查两件事一是index指令有没有配置二是 Nginx 的工作进程通常是www-data用户有没有权限读到站点根目录。很多人把项目传到服务器上权限是 700只有 root 能读Nginx 进程读不到自然就 403 了。还有一种情况是目录下没有index.html或index.php文件而autoindex又是 offNginx 出于安全考虑不会列出目录内容这时它也会直接返回 403。这两种场景的排查路径完全不同我从实际经验里总结出的顺序是先确认目录下有没有 index 文件再检查ls -l看权限这个顺序能快速排除七八成的 403 问题。404路径不对404 太常见了但 Nginx 返回的 404 和上游服务返回的 404 含义不同。如果是 Nginx 直接返回的 404通常是root或alias指令写错了。如果请求是转发到后端后返回的 404那问题就在后端路由上。怎么看是哪里返回的看响应头的Server字段。Nginx 返回的Server 字段是nginx后端返回的会是后端框架的标识。这个细节很实用我在判断问题时几乎第一时间就看这个字段。1.2 499 和 504两个最容易被误判的状态码499客户端断开连接499 是 Nginx 特有的状态码标准 HTTP 状态码里没有这个。它表示客户端在 Nginx 还没处理完请求之前就主动断开了连接。最常见的是客户端设置了超时时间太短比如一个接口正常需要 3 秒才能返回但客户端的 HTTP 请求超时设成了 2 秒那么客户端断开后Nginx 就会在日志里记一个 499。如果你在 access.log 里看到大量 499第一反应应该是去看看上游接口的响应时间而不是怀疑 Nginx 本身出问题了。502 和 504上游服务的两种失败502 Bad Gateway 表示 Nginx 连接上游服务失败最常见的原因是后端服务没启动、端口不对、或者防火墙拦了。504 Gateway Timeout 则是 Nginx 连接上了上游服务但上游在proxy_read_timeout默认 60 秒内没有返回响应。注意这两个问题的处理方向完全不同。我见过不少人把 504 当 502 处理疯狂重启后端服务结果当然没用。504 的核心是调整超时时间配置或者优化后端接口的性能而不是重启。我还遇到过一种比较隐蔽的情况Nginx 和后端服务在同一台机器上后端启动时绑定的地址是127.0.0.1但 Nginx 的proxy_pass写的是服务器公网 IP导致 502。后来我改成http://127.0.0.1:8080问题瞬间消失。遇到底层网络问题先把proxy_pass改成回环地址测试能快速区分是网络链路的问题还是服务本身的问题。2. 安装方式选型包管理器、源码编译还是 Docker很多人入门 Nginx 时第一个决策就在安装方式上纠结了。其实这个话题没有绝对的标准答案只有哪个更适合你当下的场景。我三种方式都用过分别说说它们的特点和适用场景。2.1 三种安装方式对比与适用场景安装方式优点缺点适合人群包管理器apt/yum安装快、依赖自动解决、方便卸载版本可能偏旧刚入门、对版本没特殊要求的人源码编译可自定义模块、版本新、可深度优化编译时间长、依赖多、升级麻烦需要特定模块如nginx-rtmp、http_v3的人Docker环境隔离、迁移方便、可一条命令启动配置文件在容器外需要了解卷挂载习惯容器化部署、需要快速起多个实例的人如果你只是想学 Nginx 或者做常规的反向代理用apt install nginx或yum install nginx就够了没必要折腾编译安装。但如果你需要一些官方默认包里没有的模块比如 RTMP 流媒体模块或者你用的 Linux 发行版自带的 Nginx 版本太旧、不支持某些新指令比如 HTTP/3 相关配置那就得考虑自己编译了。编译安装的核心步骤是三步下载源码、./configure添加模块、make make install。这里我提醒一句编译前一定要先跑一下nginx -V看看你已经存在的系统级 Nginx 之前编译了哪些模块然后在新编译时把这些模块都带上否则很有可能出现旧配置里用到的模块在新版本里反而找不到了。这个坑我踩过一次花了两个小时才反应过来。Docker 方式则更适合那种需要频繁创建、销毁测试环境或者团队里每个人开发环境都要保持一致的情况。我自己在本地测试时基本都用 Docker 起一个 Nginx 容器方便得很。2.2 Docker 挂载多个项目目录的坑热搜里有个词是“docker安装nginx并挂载多个项目目录”这个需求确实很典型。很多人在这一步容易踩坑正则表达式里的配置启动时总是找不到default.conf或者访问项目时 404。问题一般出在挂载关系上。常见错误是只挂载了项目目录却忘了挂载配置目录和日志目录docker run -d \ -p 80:80 \ -v /data/project1:/usr/share/nginx/html/project1 \ -v /data/project2:/usr/share/nginx/html/project2 \ --name nginx-server \ nginx:stable-alpine这样启动容器之后你去容器里看/usr/share/nginx/html下面会有project1和project2两个目录但它们各自没有独立的 Nginx 配置——除非你额外挂载了/etc/nginx/conf.d的配置目录否则访问http://server/project1时Nginx 只会按默认的根目录去查找index.html根本不会知道你期望的是把请求交给哪个项目处理。正确的做法是在宿主机上准备好每个项目的独立配置文件然后统一挂载到容器的配置目录里docker run -d \ -p 80:80 \ -v /data/nginx/conf.d:/etc/nginx/conf.d \ -v /data/project1:/usr/share/nginx/html/project1 \ -v /data/project2:/usr/share/nginx/html/project2 \ -v /data/nginx/logs:/var/log/nginx \ --name nginx-server \ nginx:stable-alpine这里有一个我自己总结出的细节宿主机上的配置目录路径和容器内的路径业务上容易混。配置文件里写root路径时必须写容器内的路径如/usr/share/nginx/html/project1而不是宿主机的/data/project1。因为请求到达 Nginx 时处理请求的程序在容器里它只能看到容器内的文件系统。我自己第一回用 Docker 跑 Nginx 时就在这个点上绕了很久后来养成了一个习惯每次写完配置文件先docker exec nginx-server ls -l确认容器里的路径真实存在再启动服务。3. 配置文件体系从入口到虚拟主机逐层拆解Nginx 的配置文件是树状嵌套结构从全局到局部一层层展开。很多新手第一次打开nginx.conf看到密密麻麻的指令头皮发麻。其实拆开看就三块内容全局块、events 块、http 块。3.1 主配置文件的结构与最小改动原则默认的nginx.conf长这样不同系统略有差异但结构一致# 全局块 user www-data; worker_processes auto; pid /run/nginx.pid; # events 块 events { worker_connections 768; } # http 块 http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式 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; sendfile on; keepalive_timeout 65; # 引入虚拟主机配置 include /etc/nginx/conf.d/*.conf; }重点说几个关键指令worker_processes autoNginx 的工作进程数。设为auto会自动与 CPU 核心数对齐。前期直接保持默认即可也没必要盲目调大。生产环境可以根据 CPU 核数和预期的并发连接数做简单估算。比如 4 核 CPU默认 auto 就是 4 个 worker每个 worker 的worker_connections是 1024Linux 上可以放宽到 4096理论上能支撑的最大并发连接数大约是 4 × 1024 4096但这只是理论值还要受文件描述符限制和内存影响。include /etc/nginx/conf.d/*.conf这是虚拟主机配置的入口。强烈建议不要在nginx.conf里直接写 server 块而是把每个站点单独放到conf.d目录下的独立文件里。这样每个站点一个文件改其中一个不影响其他站点排查问题时也可以直接看对应文件不用在一大坨配置里找。我自己有个习惯性约束叫“最小改动原则”尽量不修改 Nginx 自带的主配置文件新增站点就新建conf.d/*.conf文件扩展模块就修改nginx.conf里的include路径。这样即使将来要升级 Nginx、或者迁移到另一台机器主要配置文件基本不用动直接搬站点配置就能跑。3.2 server 块一个站点的心脏一个最基本的虚拟主机配置长这样server { listen 80; server_name example.com www.example.com; root /var/www/example; index index.html; location / { try_files $uri $uri/ 404; } }listen 80是监听端口。server_name是域名匹配支持精确匹配、通配符*.example.com和正则。这里有个重要的知识点当 Nginx 接收到一个请求时它会先根据listen端口选出对应的 server 集合然后再根据server_name去匹配。如果匹配不到任何 server 块就用默认的 server可以通过default_server参数显式指定。所以如果你配置了多个站点、并且把listen 80都写成了listen 80 default_server那所有没匹配到域名的请求都会跑进这个默认站点生产环境里这个坑经常造成“所有域名都打开同一个页面”的诡异问题。我自己的习惯是只在一个地方用default_server并且让它返回一个简单的占位页面防止裸 IP 访问进来。root指定站点根目录。index指定默认首页文件。try_files $uri $uri/ 404的意思是先尝试按请求的路径找文件找不到就尝试按目录找还找不到就返回 404。这一行是静态网站部署的核心很多 404 问题就是少了这一行或者写错了。3.3 location 匹配顺序不搞清楚会被坑得很惨location是 Nginx 配置里最灵活也最容易写错的地方。匹配规则有优先级记不住的话实际行为会跟你的预期完全相反。匹配优先级从高到低location /path精确匹配优先级最高。一旦命中立即停止搜索。location ^~ /path前缀匹配且一旦命中不再检查正则表达式。location ~或location ~*正则表达式匹配~*不区分大小写按配置文件中的顺序依次检查命中第一个匹配的即停止。location /path普通前缀匹配按最长匹配前缀生效。如果没有正则命中选最长的前缀匹配。我用一个生活化的类比来解释精确匹配就像直呼其名你喊“小明”他不会理别的名字前缀匹配^~像按部门找人你找“技术部张伟”系统就直接把人给你了不再去核对全公司还有没有叫张伟的人正则匹配像按特征找人“穿红衣服的男生”碰到一个符合的就停普通前缀匹配则像快递按地址配送地址写得越长越具体越优先。举个例子location / { # 匹配所有请求兜底 } location /static/ { # 匹配 /static/ 开头的请求 } location ~ \.(png|jpg|gif)$ { # 匹配图片文件 } location ^~ /api/ { # 匹配 /api/ 开头的请求不再检查正则 }如果你的配置文件里同时有location /static/和location ~ \.png$那么请求/static/logo.png会走哪个答案是正则那个。因为/static/是普通前缀匹配而正则匹配优先级更高。如果你希望/static/下面全部走静态文件处理、不再匹配正则就必须给/static/加上^~。这个细节是我在很多线上配置里见过的经典事故来源你要是想彻底掌控 location 的行为最好在本地拿几组请求一一测试确认你理解的和 Nginx 实际执行的是一致的。3.4 root 与 alias一字之差天壤之别这是一个特别经典的问题几乎每个新手都会在里面栽一次。root和alias的区别可以一句话总结root会把完整的 URI 追加到路径后面alias则是把路径替换掉。location /images/ { root /var/www/example; }请求/images/logo.pngNginx 会去找/var/www/example/images/logo.png。location /images/ { alias /var/www/static/; }请求/images/logo.pngNginx 会去找/var/www/static/logo.png。注意alias的路径末尾带了斜杠要是漏了斜杠路径拼接就会变成/var/www/staticlogo.png直接 404。这里有一个我长期使用的自测技巧在浏览器直接请求那个图片或文件然后看 Nginx 错误日志里的open() failed错误它会明确告诉你去哪里找文件了。根据这个真实路径回推到底是 root 还是 alias 的行为基本一眼就能定位问题。4. 反向代理与负载均衡Nginx 的核心生产力静态文件只是 Nginx 能力的一部分它真正在企业里被大规模使用核心原因还是反向代理和负载均衡。这也是热搜词里“nginx反向代理”排得很靠前的原因。4.1 反向代理的基本配置与常见业务场景反向代理解决的核心问题是把来自客户端的请求按照规则转发到内部网络中的后端服务然后再把后端的响应返回给客户端。对客户端来说它只跟 Nginx 打交道不知道背后到底有几台服务器。一个最基础的反向代理配置server { listen 80; server_name api.example.com; location / { 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 Host $host和X-Forwarded-For两行一定要有。如果你不加这两行后端服务拿到的客户端 IP 全是 Nginx 的地址就无法做访问日志分析、无法做限流、无法做地域统计。而且有些框架如 Laravel、Django在生成绝对 URL 时依赖Host头缺了它可能会生成错误的链接地址。4.2 proxy_pass 带不带斜杠一个极其容易出错的细节proxy_pass的路径处理规则是整个反向代理配置里最容易让人混乱的地方。带不带斜杠行为完全不同。location /api/ { proxy_pass http://backend:8080/; }请求/api/user转发到后端时URI 中的/api/会被替换成/即后端收到的是/user。location /api/ { proxy_pass http://backend:8080; }请求/api/user转发到后端时URI 原样保留为/api/user。这个差异意味着如果你的后端接口路由前缀本来就不带/api那你在proxy_pass中加了斜杠就把前缀剥离掉再转发如果你的后端路由本身就要带/api前缀那proxy_pass里就不能加斜杠。这两种配置我在实际项目中都用过曾经因为加不加斜杠的问题导致前端请求全部 404排查了整整一个下午。最后是通过抓包看到后端的实际请求路径才发现是斜杠导致的路径变化。我自己后来记住了一个判断口诀proxy_pass里带有 URI 部分哪怕是/就会做替换没有就原样转发。4.3 upstream 与负载均衡策略当后端服务有多个实例时用upstream定义一组服务器然后在proxy_pass里引用这个组名upstream backend_cluster { server 192.168.1.10:8080 weight3; server 192.168.1.11:8080 weight1; server 192.168.1.12:8080 backup; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_cluster; proxy_set_header Host $host; } }默认策略是轮询round-robin即每个请求按顺序轮流分配给不同后端。上面配置里weight3表示第一台机器每收到 3 个请求第二台才收到 1 个适合两台机器性能不对等的场景。backup则表示这台机器是备用节点只有当其他节点都不可用时才会参与服务。除了轮询Nginx 还支持ip_hash、least_conn等策略。ip_hash能保证同一个 IP 的请求始终打到同一台后端服务器如果你的后端有 session 本地存储的需求这个策略在早期没有 Redis 共享 session 时的作用很明显。但要注意ip_hash是拿 IPv4 地址的前三段做哈希的同一局域网内的机器比如同一办公网出口 IP会全部被分到同一台后端流量非常不均匀这点我在实际使用中发现后果断还是切回了least_conn或普通轮询。负载均衡模式下还有一个隐蔽的坑如果后端某台机器挂了Nginx 默认会继续把请求发过去直到多次失败后才将其标记为不可用。这个过程里用户会看到 502。生产环境里upstream边的max_fails和fail_timeout参数值得提早进行配置upstream backend_cluster { server 192.168.1.10:8080 max_fails3 fail_timeout30s; server 192.168.1.11:8080 max_fails3 fail_timeout30s; }含义是30 秒内请求失败 3 次就把这台机器标记为不可用30 秒后再尝试恢复。这样即使某台后端出问题Nginx 也能在一个故障窗口内自动摘除它不会让大量请求持续踩到坏节点上。5. 前后端分离项目部署从构建产物到线上可访问热搜词里有一句“pnpm run build的包怎么nginx启动”这个提问我经常看到。其实这个问题的本质是前端项目经过构建之后生成了一堆静态文件怎么让 Nginx 把这个目录当成一个网站去服务。5.1 静态资源发布的第一步目录和权限前端项目pnpm build之后产物通常在dist目录下。你要做的事情其实只有两步把dist里的内容拷贝到服务器上的某个目录然后写一个 Nginx 配置指向这个目录。一个典型的部署流程# 在服务器上创建目录 mkdir -p /var/www/myapp # 本地打包 pnpm build # 将产物上传到服务器从本地终端执行 scp -r dist/* userserver:/var/www/myapp/ # 写 Nginx 配置然后 reloadNginx 配置server { listen 80; server_name myapp.example.com; root /var/www/myapp; index index.html; location / { try_files $uri $uri/ /index.html; } # 静态资源缓存策略 location /assets/ { expires 30d; add_header Cache-Control public, no-transform; } }注意try_files $uri $uri/ /index.html这一行相比纯静态站点的404这里改成了把找不到的路径统统回退到index.html。这个配置是专门为前端单页应用SPA准备的前端路由是 history 模式时比如访问/user/profile服务端并不存在这个目录必须让 Nginx 把请求交给index.html然后由前端 JavaScript 接管路由把它渲染成对应用户页面的路径。如果你不写这一行用户直接在地址栏输入/user/profile就会看到 404 页面——我在实际项目里帮同事排查过好几次这种“页面刷新就 404”的问题罪魁祸首全都是缺少这个回退规则。5.2 前端项目上线后的常见问题缓存混乱静态资源发布后经常碰到用户还在用旧版本页面、浏览器缓存了旧 JS 文件之类的问题。如果构建工具Vite/Webpack产出的文件名带 hash那是天然免疫于这个问题。但如果你用的是不带 hash 的文件名就需要 Nginx 层面配合要么在响应头上加短缓存、要么干脆禁用 HTML 页面的缓存location / { try_files $uri $uri/ /index.html; add_header Cache-Control no-cache, no-store, must-revalidate; }这样 HTML 每次都不走缓存而assets目录里的 JS/CSS 文件因为文件名带 hash可以用expires 30d做长缓存既保证加载速度又解决了更新问题。这套组合拳是我在前端项目 Nginx 发布配置里沉淀下来的标准配置基本可以复用到大半年以上的各类 SPA 项目里。6. HTTPS 配置与证书文件哪些私钥格式 Nginx 能认现在没有 HTTPS 的网站基本没法看搜索引擎不待见浏览器也会打红叉。Nginx 配 HTTPS 并不复杂但有两个高频问题让新手困扰证书文件应该长什么样、服务器上到底该放哪几个文件。6.1 证书链、私钥与 Nginx 的 HTTPS 配置从证书服务商比如 Let’s Encrypt、阿里云、腾讯云下载证书之后你会拿到几个文件。对 Nginx 来说核心只需要两个一个是证书文件通常是fullchain.pem或cert.pem一个是私钥文件通常是privkey.pem。server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; root /var/www/example; index index.html; }fullchain.pem里包含了站点证书和中间证书privkey.pem是私钥。这两个文件必须配对。很多人第一次配置时容易把站点证书只含自己证书的那个cert.pem当成fullchain.pem用导致在某些浏览器上提示证书链不完整。如果你不确定手里文件的内容可以执行openssl x509 -in fullchain.pem -text -noout查看里面是不是包含多张证书。如果只有一张那你用的很可能不是 fullchain。私钥文件的权限也要处理Nginx 主进程通常以 root 启动然后降权到www-data用户。如果私钥文件权限是 777 或者其他人能读Nginx 出于安全考虑会在启动时直接报错并拒绝启动日志里会提示不安全。正确做法是chmod 600 privkey.pem并保证文件属主是 root。6.2 Nginx 支持哪些类型的私钥热搜词里有个“nginx 支持哪种类型的私钥”这个问题的核心在于Nginx 支持 PEM 格式的私钥这是最通用的一种格式。实际场景里常见的私钥有两种算法RSA 私钥最常见的类型文件内容以-----BEGIN RSA PRIVATE KEY-----或-----BEGIN PRIVATE KEY-----开头。ECC 私钥基于椭圆曲线算法文件内容以-----BEGIN EC PRIVATE KEY-----开头文件更短密钥交换更快。Nginx 对这两种都支持配置方式上没有区别都是写在ssl_certificate_key里。唯一需要注意的是如果你在证书服务商那边生成的是 ECC 证书但配置里误填了 RSA 的私钥或者反过来Nginx 会在启动时或重载时直接报错。这种密钥不匹配的错误信息很明确看日志基本都能定位。有一个常见情况需要说明如果你在阿里云或腾讯云申请证书时选的是 RSA那下载的私钥大概率是-----BEGIN RSA PRIVATE KEY-----开头如果你选的是 ECC那就是-----BEGIN EC PRIVATE KEY-----开头。可以通过简单的head -1 privkey.pem命令来确认开头的标记判断是哪一类。我自己在排错时习惯于看到密钥不匹配的报错后先去对比证书里的公钥和本地私钥能否对上用openssl x509 -in fullchain.pem -noout -pubkey和openssl pkey -in privkey.pem -pubout分别提取公钥再做比对。6.3 客户端证书验证mTLS 与安全边界热搜词里有一个“nginx 需要 client证书”这涉及的是双向 TLSmTLS场景。普通 HTTPS 是客户端验证服务器的身份mTLS 则是服务器也要验证客户端的身份。配置思路是基于主流证书体系为特定接口或整个站点增加客户端证书验证server { listen 443 ssl; server_name secure.example.com; ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem; # 要求客户端出示证书 ssl_verify_client on; ssl_client_certificate /etc/nginx/ssl/ca-chain.pem; location / { proxy_pass http://backend:8080; proxy_set_header X-Client-Verify $ssl_client_verify; proxy_set_header X-Client-Cert-Subject $ssl_client_s_dn; } }平时个人项目很少用到 mTLS但在企业内部的微服务网关、物联网设备接入、或者一些高安全要求的接口体系中这个配置就是一种有效的安全加固方式。这里要注意ssl_client_certificate指向的是 CA 证书签发客户端证书的那个 CA不是客户端证书本身。同时ssl_verify_client设置为on时客户端如果没有证书或证书无效TLS 握手阶段就会失败根本不会进入 HTTP 逻辑。如果你希望既允许有证书的访问、也允许没有证书的访问在代码里做更细粒度的控制可以设为optional而非onNginx 就会把验证结果通过$ssl_client_verify变量传给后端由后端自己做下一步决策。7. 日志、诊断与排错一条完整的实战排查链路配置写完服务跑起来不等于万事大吉。Nginx 运行期间总会出各种意料之外的问题。这一章我分享一个我自己的完整排查链路值得你记下来并反复套用。7.1 排错前必做三件事第一件事检查配置语法。nginx -t这条命令会告诉你配置文件有没有语法错误输出syntax is ok和test is successful才能进行下一步。如果你用的是 Docker 部署需要进容器执行docker exec nginx-server nginx -t。事实上我建议把它作为每次修改配置后的一个固定动作先nginx -t再reload而不是改完直接重启。第二件事区分 reload 和 restart。nginx -s reload是平滑重载它会让 Nginx 重新读取配置文件但不会中断当前正在处理的请求。systemctl restart nginx是重启会中断所有连接。生产环境里只要配置语法没问题一律用reload。这也意味着你不需要担心改了配置后服务会短暂不可用——前提是语法检查过了。如果有语法错误reload 会失败Nginx 会继续使用旧的配置运行而不会停下来。很多人不了解这个机制看到 reload 报错后慌慌张张地去重启服务反而把一个问题变成两个问题。第三件事找到日志。默认情况下访问日志在/var/log/nginx/access.log错误日志在/var/log/nginx/error.log。Docker 部署时如果你没有挂载日志目录需要用docker logs nginx-server查看。注意错误日志才是排错入口访问日志更侧重流量分析和状态码统计。如果你看到的是一堆 404 或 502去 error.log 里看有没有对应的connect() failed或open() failed记录这些错误消息几乎会把根本原因直接摆在你面前。7.2 一个真实问题的完整排查复盘去年有个开发环境出了一个问题前端访问 Nginx 能正常打开页面但页面一切换路由就 404。我当时没有直接去翻配置文件而是按这个顺序做了一次系统性排查在浏览器开发者工具的 Network 面板里看 404 请求的完整 URL确认请求路径是什么。看到请求是/user/profile这是一个典型的前端路由地址。在服务器的错误日志里执行tail -f /var/log/nginx/error.log同时刷新页面日志里明确写了open() /var/www/myapp/user/profile failed (2: No such file or directory)。看到 Nginx 在尝试找user/profile这个路径下的文件说明 Nginx 根本没有进入前端路由回退流程也就是try_files缺少/index.html这个回退参数。修改try_files $uri $uri/ /index.html;然后nginx -t通过nginx -s reload。再次刷新页面正常。整个过程不到十分钟。如果你想训练自己的排错能力可以刻意练习一下这个模式先看状态码判断问题方向再看错误日志定位具体文件或连接最后针对性地调整配置并用nginx -t验证。不要一上来就改配置更不要凭记忆凭感觉去猜。改配置之前先看清楚日志里到底报了什么这是我从大量线上问题中总结出来最有效的排错方法论。7.3 几个常见的配置陷阱与检查点还有几个高频陷阱顺手帮你整理出来了端口冲突Nginx 启动失败时第一件事检查listen 80是不是被其他进程比如 Apache、另一个 Nginx 实例占了。ss -lntp | grep 80或netstat -tlnp | grep :80可以快速看到是哪个进程在监听。SELinux 拦截在 CentOS/RHEL 系机器上有时候 Nginx 配置看起来完全正确但就是连不上后端端口。这大概率是 SELinux 拦截了 Nginx 的网络访问。临时验证可以执行setenforce 0如果问题消失就是 SELinux 的规则问题。长期方案是调整布尔值或添加自定义策略而不是关掉 SELinux。这个坑在云服务器上很常见。server_name 匹配顺序如果配置了多个域名请求的Host头和某个server_name匹配不上Nginx 会使用默认的 server。很多人配了多个站点后发现所有域名都指向同一个页面原因就是所有 server 块都没有default_server或者第一个 server 块意外地成了默认站点。正确的做法是显式指定一个default_server比如把裸 IP 的访问统一指向一个占位页面。location 正则的顺序正则 location 是按配置文件中的顺序匹配的命中第一个即生效。所以如果你有多个正则 location要注意彼此是否有包含关系把更具体的规则写在前面。比如~ \.php$和~ ^/admin/.*\.php$后者要写在前面否则管理后台的 PHP 请求也会走前面那个通用规则导致不同的处理逻辑。8. 入门之后还应该往哪个方向深入如果你把前面这些内容都消化了那么你已经可以在服务器上独立部署一个静态站点、反向代理一组后端服务也能看懂大多数 Nginx 配置了。但 Nginx 的边界远不止于此。我建议下一步按这样一个优先级去拓展限流与安全limit_req和limit_conn模块可以按 IP 限制请求频率和并发连接数。再配合基础的 IP 黑白名单allow/deny能挡住不少低级的恶意请求。这个方向对生产系统非常实用尤其是在接口暴露在公网的情况下。日志分析与监控学会用log_format定制日志把响应时间、上游地址记下来配合访问量分析和错误告警能让你第一时间感知服务异常。熟练之后甚至可以写个简单脚本定时统计日志里的 5xx 数量超过阈值就告警成本低又见效快。负载均衡的策略深入在实际有多个后端节点的环境中反复观察不同策略下各节点的负载情况。least_conn对长请求更友好ip_hash适合有状态场景upstream的主动健康检查需要借助nginx_plus或者 OpenResty 才能实现。先理解清楚每个策略的适用边界再根据业务形态做选择。OpenResty / Nginx Lua如果有一天你发现标准 Nginx 配置已经满足不了业务需求比如需要在网关层做复杂的鉴权逻辑可以了解一下 OpenResty。它把 Lua 嵌入 Nginx有能力在请求的不同阶段执行自定义逻辑灵活性提升一个量级。不过这个方向对 Lua 语言和 Nginx 生命周期都有门槛建议前面几步都走稳了再碰。从我自己的经验来看Nginx 这门技术最忌讳的就是只看文档不实操。配置文件写错了没关系改对了就行状态码看不懂没关系多遇几次自然就记住了。你只要把一台云服务器和一个域名用起来把上面提到的这些配置一条一条亲手敲一遍很快就能从“照着抄”变成“随手写”。遇到报错时别慌先看状态码再看日志最后再动手改配置——这个习惯一旦养成Nginx 会变成你最得心应手的工具之一。