说实话,搜“nginx 配置”这个关键词,搜出来的东西永远是一堆让你更懵的碎片。有人让你改 nginx.conf,有人塞给你一大段 proxy_pass 让你照抄,但很少有人把配置文件的组织逻辑、每个指令为什么这么写、改完以后出了异常该怎么定位讲清楚。我这些年从源码编译、业务分发、静态资源托管到平滑升级踩过不少坑,这篇就把 nginx 配置这条线从头到尾捋一遍:安装完之后文件目录怎么组织、反向代理怎么配才不容易出 502、静态资源和共享目录的 root 与 alias 差别在哪、高并发场景下 worker 和内核参数怎么调、生产环境升级二进制如何做到不掉线,最后再把我排错时最常用的思路和日志定位方法分享出来。内容不追求“配置大全”,而是把最常用、最容易踩坑的部分讲透,适合刚接触 nginx 的运维、后端开发,也包括那些已经在用 nginx 但一直没理清配置逻辑的人。
1. 安装这件事,决定了你后面少踩一半坑
很多人在配置阶段被各种奇怪问题卡住,根源其实在安装这一步就没选对路子。不同发行版、不同安装方式,配置文件路径和权限模型完全不一样,如果你照着网上一个教程去改,结果文件路径都不对,那自然怎么改都不生效。
1.1 包管理器安装:最快,但要知道它帮你做了什么
在 Ubuntu/Debian 上一条apt install nginx,在 CentOS/RHEL 上一条yum install nginx,装完服务就能跑。这种方式最大优势是配置目录很规范,比如/etc/nginx/nginx.conf是主配置,/etc/nginx/conf.d/里放自定义站点配置,日志在/var/log/nginx/下,systemd 管理方式也是现成的。
但缺点同样明显:发行版自带版本通常比较保守,可能缺少你后面需要的模块,比如--with-stream(四层转发)、--with-http_v2_module(HTTP/2)。遇到这种情况,要么你装的是旧版本,要么你根本不知道当前这个 nginx 编译时带了哪些模块。我的建议是,装完第一件事先执行:
nginx -V这一步不会有人强调,但它真的比什么都重要。-V会把编译参数、模块列表全部打印出来,你能立刻确认手里这个 nginx 是不是你想要的版本,有没有 SSL 模块,有没有 stream 模块。很多人在配 HTTPS 的时候发现listen 443 ssl;直接报错,就是因为源码编译时没带--with-http_ssl_module,而自己还浑然不知。
1.2 源码编译安装:可控性最强,也是最稳的生产选择
如果是公司业务服务器,我更推荐你自己编译安装。编译不是为了“显得专业”,而是为了让你清楚知道自己装了哪些模块、二进制放在哪个路径、未来升级的时候怎么去操作。
编译前先把依赖装齐,Ubuntu 下一般是这几个包:
apt-get install -y build-essential libpcre3-dev zlib1g-dev libssl-devCentOS 下对应的是:
yum install -y gcc gcc-c++ pcre-devel zlib-devel openssl-devel注意,新版 nginx(1.25 之后)开始用 PCRE2,如果你装的是新版源码,依赖包要换成libpcre2-dev或pcre2-devel,否则 configure 阶段会报找不到 PCRE 库。我当年就被这个坑过一次——编译 1.25 的源码,系统里只有 pcre 没有 pcre2,卡了半天才发现是版本配套问题。
我的常用编译参数如下:
./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-stream \ --with-stream_ssl_module然后编译安装:
make -j$(nproc) make install这里我要重点说下--prefix参数。它决定了你未来所有配置文件的路径,比如/usr/local/nginx/conf/nginx.conf。很多人喜欢默认不指定--prefix,结果 nginx 被装到/usr/local/nginx倒也罢了,但如果你在多个环境上分别用源码编译和包管理器安装,nginx 的配置路径完全不统一,后续交接非常痛苦。我个人的习惯是:生产服务器一律源码编译到固定 prefix,配合一套手写的 systemd service 管理,不用发行版自带的启动脚本。
源码安装的 nginx 没有自带 systemd 管理文件,需要你手动写一个,这是我一直在用的模板:
[Unit] Description=nginx - high performance web server After=network.target [Service] Type=forking PIDFile=/usr/local/nginx/logs/nginx.pid ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s quit PrivateTmp=true [Install] WantedBy=multi-user.target写好后放到/etc/systemd/system/nginx.service,执行systemctl daemon-reload,之后就能用systemctl start nginx、systemctl reload nginx来管理了。这套方式的好处是统一了管理入口,也方便设置开机自启。
1.3 Windows 下的 nginx 和 Linux 差异不小
如果你只是本地开发或者测试,Windows 版也能用。官网下载对应的 zip 压缩包,解压后直接双击nginx.exe就能启动,默认监听 80 端口,访问http://localhost能看到欢迎页。
但有几个点必须清楚。第一,Windows 版没有 daemon 模型,没有 Linux 上那种 master-worker 进程架构,一个 nginx.exe 进程扛所有事,性能表现远不如 Linux。第二,nginx -s reload在 Windows 上虽然能执行,但某些场景下配置不生效需要人工确认进程是否真的重启了。第三,Windows 下要把 nginx 做成系统服务,一般要借助 NSSM 或 WinSW 这类工具,不然开机不会自动启动。综合来看,Windows 上玩玩可以,生产环境还是老老实实用 Linux。
1.4 安装完之后立刻做的检查
不管用哪种方式装,装完都要按这个顺序做一遍基础验证:
nginx -t nginx -V ss -lntp | grep 80 curl -I http://127.0.0.1nginx -t是检查配置语法,nginx -V看编译参数,ss确认端口监听,curl -I确认 HTTP 响应正常。四步走完,你才拥有一个“确定没毛病的起点”,后面改配置的时候出了问题才能往配置逻辑上排查,而不是怀疑环境。
还有一个容易忽略的:有些 Ubuntu 系统默认开了 AppArmor,CentOS 可能开了 SELinux,这些安全模块会拦截 nginx 访问非默认目录。最典型的表现是配置没写错、nginx -t也通过,但访问静态文件就是 403。遇到这种情况先执行getenforce看看 SELinux 状态,如果是 Enforcing,要么setsebool -P httpd_read_user_content 1放开权限,要么把文件放到允许的目录下。
2. 读懂 nginx.conf 的主线:main、http、server 和 location 的协作逻辑
配置 nginx 之前,先要把配置文件的层次结构理解透。很多人一上来就盯着 location 里的各种正则,结果连 server 和 location 之间的关系都没理清楚,越改越乱。
2.1 四个层级的职责分配
我常拿“小区物业”这套比喻来讲:main上下文是物业管理处,管的是整个小区的公共事务,比如 nginx 运行身份、worker 进程数量、PID 文件路径,这些配置在最外层,所有 server、http 都要受它约束。
http块就是小区里的“公共设施条例”,规定所有站点共用的行为,比如 MIME 类型、日志格式、默认超时时间。所有 HTTP 相关的 server 块都必须嵌套在 http 块内部。
server块对应一栋楼的门牌号。它通过listen指定端口、server_name指定域名,来决定“哪个请求进哪栋楼”。一台 nginx 上可以定义几十个 server,只要端口和域名不冲突就行。
location块则是楼里的具体房间,它根据请求 URI 的路径前缀或正则规则,告诉 nginx“这个路径该执行什么动作”。location 可以指向静态目录,也可以把请求转到后端服务。
理解这层嵌套关系以后,你拿到一份别人的配置文件,至少不会再一头雾水。
2.2 全局参数里最常见的几个“为什么”
主配置里有一行user nginx;,很多新手不知道它的意义。它的作用是设置 worker 进程的运行系统用户。如果进程以 root 身份跑,一旦 nginx 或第三方模块有漏洞,攻击者直接拿到 root 权限;用它跑,即便被攻破也只是低权限用户。但这也带来另一个问题:nginx 对静态文件的访问权限取决于这个 user。你 web 目录的文件假如是另一个用户创建的,权限没放够,nginx 读不了就会 403。排 403 的时候,除了看路径,还要想一下 nginx 的运行用户和文件权限之间的关系。
worker_processes auto;的意思是按 CPU 核心数启动 worker 进程。它不是越大越好,因为每个 worker 都独立占用内存,而且进程切换也会消耗 CPU。生产环境一般设为 auto 或者等于物理核心数。我有段时间贪心,在 8 核机器上设了 32 个 worker,结果高并发下性能反而下降,后来仔细看了监控,发现大量时间耗在进程调度上。
worker_connections定义的是单个 worker 进程能同时打开的连接数上限,包括与客户端的连接、与后端的连接、日志文件句柄等。所以整个 nginx 理论上最大并发连接数不是这两个值直接相乘,涉及反向代理时,每个请求通常要占用两条连接,计算公式要留出余量。这个细节放到第 5 节再展开讲。
2.3 include 机制:没人会把所有配置堆在一个文件里
刚学 nginx 的时候,我也把所有 server、upstream 全部堆进 nginx.conf,结果是文件越来越长,每次改一个站点都胆战心惊,生怕语法写错连累所有站点。后来我才发现 nginx 早就准备好了拆分配置的方案。
默认的http块里通常有一行:
include /etc/nginx/conf.d/*.conf;这行是关键。它把conf.d目录下所有.conf文件都加载进来。你完全可以给每个服务单独建一个文件,比如api.conf、web.conf、upload.conf,互不干扰。改其中某一个文件后,nginx -t检查的是全部,但有且只有你改动的文件会被 reload。
Debian/Ubuntu 系的官方 nginx 包还有一套sites-available/sites-enabled的约定:可用配置放在sites-available,然后在sites-enabled里建软链接来启用。这个设计是为了方便批量启停站点,如果只是个人项目,直接用conf.d就够了。
2.4 server_name 和 location 的匹配优先级
server_name对多域名非常重要。它的匹配优先级是:精确匹配 > 通配符前缀*.example.com> 通配符后缀www.example.*> 正则~^www\.example\.com$> 默认 server(第一个或显式指定的default_server)。这个规则特别容易被忽略,比如你同时定义了server_name api.example.com;和server_name example.com;,如果访问的是api.example.com而前者恰好没写对,请求就会落到后一个 server,现象就是你明明配了接口转发,结果返回的是 HTML 页面。
location的匹配优先级同样是个坑。规则按以下顺序:=精确匹配优先,然后^~前缀匹配如果命中就不再检查正则,接着是正则~/~*按顺序匹配,最后才是普通前缀匹配取最长者。很多人写location /api和location /api/v1,却不知道前者其实会同时匹配/api/v1,如果不了解“最长前缀优先”的规则,配置出来的行为会很诡异。
3. 反向代理配置:给后端服务套上一层“门面”
反向代理是 nginx 最核心也是最常见的用途。它的本质是客户端连的是 nginx,nginx 再往后端转发请求,后端拿到的请求看起来就像是 nginx 发出去的。这样做的好处很多:客户端不知道后端真实地址,安全性提高;SSL 证书统一在 nginx 层终止,后端不用关注证书;还可以在后端集群间做负载均衡。
3.1 一个可以直接抄的反向代理模板
下面这个模板我用了很多年,适合大多数 Web 应用:
upstream backend { server 127.0.0.1:8080; server 127.0.0.1:8081; keepalive 32; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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; } }这里每个 header 都不是凑数的。Host如果不设置,后端收到的 Host 是backend或127.0.0.1:8080,很多框架拿这个值生成绝对链接时会出错。X-Real-IP和X-Forwarded-For是把客户端真实 IP 传下去,否则后端只能看到 nginx 的 IP,日志里的客户端来源全是错的。X-Forwarded-Proto告诉后端请求原本是 HTTP 还是 HTTPS,否则后端拿$scheme判断时会被 nginx 的普通连接误导。
proxy_http_version 1.1和Connection ""这两行配了对upstream里的keepalive 32才有效果。它能让 nginx 与后端之间保持长连接,避免每个请求都重新建立 TCP 连接。性能测试里,这两行通常能带来非常明显的 QPS 提升。
3.2 proxy_pass 的路径行为:最容易出 bug 的地方
proxy_pass后面带不带路径,行为完全不同。这是 nginx 配置里最容易踩的坑之一。
# 不带 URI location /api/ { proxy_pass http://backend; }这种情况下,nginx 会把原始 URI 原样转发给后端,/api/user到了后端还是/api/user。
# 带 URI location /api/ { proxy_pass http://backend/; }这种写法里,nginx 会用替换规则:把匹配到的/api/部分替换成proxy_pass后面的/。所以请求/api/user到后端就变成了/user。如果你的后端接口本来就定义在/api路径下,却写了带/的 proxy_pass,大概率会拿到 404。
这里我的建议是,同一套服务尽量只固定一种写法。一般推荐 location 后面不带路径、proxy_pass 也不带路径,让两端路径保持一致,减少心智负担。
3.3 超时配置、缓冲和生产里常见的 502/504
反向代理模式下,常见的异常状态码其实都能从配置上找原因。
502 Bad Gateway 通常是后端服务根本没起来、端口没监听、或者后端进程崩了。排查命令很简单:
ss -lntp | grep 8080 curl http://127.0.0.1:8080/health如果本机 curl 都没响应,问题不在 nginx,先修后端。
504 Gateway Timeout 则多半是 nginx 等后端响应等太久了。默认的proxy_read_timeout是 60 秒,如果你有一个慢接口(比如导出报表这种)耗时超过 60 秒,nginx 就先放弃等待了。处理办法不是盲目把时间调到 600 秒,而是先分清慢请求是不是合理的:合理就调大:
location /export/ { proxy_read_timeout 300s; }不合理就先去优化后端,调超时只是掩盖问题。
还有个容易忽略的参数是proxy_buffering。默认开启时,nginx 会等后端响应攒够再一次性回给客户端,这对大多场景是好事;但如果你做的是流式响应(比如 SSE、实时日志),就必须关闭缓冲:
location /stream/ { proxy_buffering off; }否则客户端会感觉到数据卡顿、一直收不到,像是超时一样。
3.4 WebSocket 和 HTTPS 终止
WebSocket 的代理跟普通 HTTP 有些差异,关键在于升级协议和超时。标准配置如下:
location /ws/ { proxy_pass http://ws_backend; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; }Upgrade和Connection头告诉后端这是一个 WebSocket 升级请求。proxy_read_timeout必须调大,因为 WebSocket 连接本质上是长连接,默认 60 秒超时会让连接频繁断开。
HTTPS 终止就简单了,在 server 块里加两行:
listen 443 ssl; ssl_certificate /path/to/server.crt; ssl_certificate_key /path/to/server.key;证书文件注意权限,私钥一般要chmod 600,否则 nginx 启动时会报权限过宽的错误。
4. 静态文件和共享目录:动静分离的关键细节
很多人以为 nginx 配静态文件就是写个root指向目录,实际用起来总是遇到 403、404,甚至配置“看起来对”但访问的是别的目录。这里面的坑,基本都出在 root 和 alias、以及目录浏览、访问控制这些细节上。
4.1 root 和 alias:一字之差,路径天差地别
这是 nginx 配置里最高频的混淆点。看这两个示例:
location /static/ { root /data/web; }访问/static/a.css时,nginx 实际读取的文件路径是root + 完整URI,也就是/data/web/static/a.css。
location /static/ { alias /data/files/; }访问/static/a.css时,nginx 实际读取的是alias + URI 去掉匹配前缀的部分,也就是/data/files/a.css。
简单来说,root是把完整的 URI 拼在根路径后面,alias是用你指定的路径替换掉 location 匹配到的部分。我见过不少新手文件放在/data/files下,却用root /data/files;配了location /static/,结果实际找的是/data/files/static/xxx,白白多了一层目录,于是 404。诊断这种问题时,最快的办法是打开error.log,里面会直接打印实际尝试的文件路径。
4.2 共享文件和文件服务的配置实践
“nginx 共享文件”这块,本质是 nginx 直接暴露一个目录给用户下载或预览。最基础的做法是开启目录列表:
location /download/ { alias /data/share/; autoindex on; autoindex_localtime on; charset utf-8; }autoindex_localime on让列表时间显示为本地时区,否则默认是 UTC,用户看到的修改时间会差 8 小时。charset utf-8是为了让中文文件名不乱码。
如果共享目录本身是 NFS 或者网络挂载盘,还需要注意 nginx 的user配置对挂载点是否有读权限。很多网络文件系统会对 uid 做映射,你在挂载端看到的权限和在 nginx 进程里的实际权限可能不一致,表现就是列表能开,但点进去下载时报 403。
下载场景里,sendfile on;基本是必开的。它让 nginx 直接通过内核的 sendfile 系统调用把磁盘文件发到网卡,省掉了用户态内存拷贝,大文件下载性能提升非常明显。同时可以加tcp_nopush on;,它在 sendfile 开启时会将响应头和数据包合并发送,减少网络小包数量。
4.3 静态资源缓存和压缩:访客体验立刻变好
静态资源天然适合缓存。简单的配置是在 location 里加expires:
location /static/ { alias /data/static/; expires 7d; add_header Cache-Control "public, max-age=604800"; }expires 7d会同时生成Expires头和Cache-Control: max-age=604800,浏览器就会在 7 天内直接使用本地缓存。如果你的资源带了版本号(比如a.1.2.3.js),缓存时间可以放心调长,因为文件变了 URL 也会变。
压缩方面,gzip 是性价比很高的优化:
gzip on; gzip_static on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/javascript application/json image/svg+xml;gzip_static on启用后,如果磁盘上已经有预压缩好的.gz文件,nginx 直接发送它,不重复压缩,省 CPU。这里要注意别把图片格式加进gzip_types,JPEG/PNG 本身已经压缩过了,再 gzip 白耗 CPU 还减不了多少体积。
4.4 访问控制:IP 白名单和基础认证
共享目录如果不想对所有人开放,可以做两层简单控制。第一层是 IP 白名单:
location /admin/ { allow 10.0.0.0/8; deny all; }注意,deny all必须放在allow之后才有意义,nginx 是按顺序匹配的,先放行白名单,其余全部拒绝。
第二层是 HTTP Basic 认证:
location /private/ { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; }.htpasswd可以用openssl passwd -apr1生成,也可以用htpasswd命令。文件内容格式是用户名:加密密码,注意权限要设为600,否则 nginx 或系统安全策略可能拒绝读取。
5. 负载均衡与高并发调优:worker 数量、upstream 策略和内核参数
nginx 能扛高并发,靠的不只是它性能好,还要你会“把并发拆给多个进程、把请求合理分给多个后端”。
5.1 upstream 的三种负载策略怎么选
上一节模板里已经出现了upstream backend,它就是后端服务器池。nginx 默认使用加权轮询,每个请求按顺序轮流分发到各个后端。这种策略在请求处理时间差不多的场景下最公平。
如果你的后端业务有缓存、有本地状态,轮询会导致每个请求都换后端,缓存命中率很低。这时候用ip_hash,同一客户端 IP 的请求会始终打到同一个后端:
upstream backend { ip_hash; server 127.0.0.1:8080; server 127.0.0.1:8081; }least_conn则是把请求分给当前活跃连接数最少的后端,适合后端请求耗时差异大的场景,比如既有秒开接口又有重计算接口。least_conn的算法会在一定权重基础上选择连接数最少的节点,适合大多数混合业务。
这三种策略的适用场景总结如下:
| 策略 | 适用场景 | 典型问题 |
|---|---|---|
| 默认轮询 | 后端无状态、处理能力均衡 | 缓存命中率低 |
| ip_hash | 需要会话保持、本地缓存 | 某 IP 容易压垮单节点 |
| least_conn | 请求耗时差异大 | 配置复杂,理解门槛高 |
5.2 后端健康检查不配置,502 会打得你措手不及
默认情况下,nginx 只有在把请求转发给一个不可用的后端时,才会通过max_fails和fail_timeout判断它“挂了”,然后暂时不再向它转发。默认值是max_fails 1和fail_timeout 10s,意味着只要失败一次,该后端会在 10 秒内被标记为不可用。这个默认值对生产环境来说太敏感,偶尔一次超时就把节点摘掉,会造成不必要的抖动。
我一般会把参数放宽:
upstream backend { server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server 127.0.0.1:8081 max_fails=3 fail_timeout=30s; }意思是 30 秒内失败 3 次才摘除节点。注意,这只是被动健康检查——nginx 只在实际转发失败的时候才知道后端挂了。如果需要主动探测(比如定期发健康检查请求),得用 nginx 商业版或者结合第三方模块来实现,日常使用中被动检查配合监控告警已经足够。
5.3 高并发下的 worker 与内核参数协同调整
先说并发能力的大致模型。假设worker_processes是 8,worker_connections是 10240,理论上最大并发连接约为 8 × 10240 = 81920。但做反向代理时,每个用户请求会占用一条客户端连接和一条到后端的连接(除非启用了 upstream keepalive),所以实际能承载的并发请求数会打折扣,大约是连接数的一半左右。这也是为什么我会在代理场景里把worker_connections设到 20480 以上,而不是习惯性的 1024。
worker_connections设大了之后,Linux 系统层面也要跟着调,否则 nginx 会报“too many open files”或者连接队列直接溢出不响应。
在/etc/security/limits.conf里,把 nginx 用户的可打开文件数调大:
nginx soft nofile 100000 nginx hard nofile 100000然后在内核参数文件/etc/sysctl.conf里追加:
net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 65535 net.ipv4.ip_local_port_range = 1024 65535改完执行sysctl -p生效。somaxconn和tcp_max_syn_backlog控制的是 TCP 连接队列的长度,高并发下如果队列太短,新连接会被直接丢弃,用户端表现就是长时间连接不上。ip_local_port_range影响的是 nginx 主动向后端发起连接时能用的本地端口范围,反向代理场景大量后端连接时很容易耗尽端口,这个范围越大越好。
另外别忘了在events块里显式指定use epoll;。Linux 下 epoll 是目前效率最高的事件模型,nginx 在 Linux 上会默认使用 epoll,但显式写出来一方面是明确意图,另一方面避免某些环境自动探测出了意外。
6. 平滑升级和回滚:生产环境更换二进制的完整流程
“nginx 平滑升级”这个热搜词我猜点进去的人,大部分是遇到了重启会打断长连接的场景。确实,业务运行中你不能贸然杀掉 nginx 重启,WebSocket 连接会断、正在下载的文件会失败、在线用户会被强制踢下线。解决办法是让旧的 worker 进程处理完手头的请求再退出,新的 worker 接替工作,整个过程对用户几乎无感知。
6.1 先分清楚 reload、restart、平滑升级三件事
nginx -s reload是我日常改配置最常用的一步。它的本质是给 master 进程发送 HUP 信号,master 会重新加载配置文件,然后启动新的 worker 进程;旧的 worker 进程会被优雅关闭,处理完当前请求后才退出。所以改配置用 reload 就够了,不会中断已有连接。
nginx -s restart严格来说不存在这个命令,常见的是systemctl restart nginx或手动 kill 后再启动。这会导致 master 和 worker 全部退出,所有连接直接断开,生产环境要尽量避免。
平滑升级则是在你换了 nginx 二进制文件后,如何做到不重启 master 就切换新版本。这是通过信号机制实现的,比 reload 更进一层。
6.2 平滑升级的完整操作步骤
假设你现有的 nginx 在/usr/local/nginx,现在要升级到 1.26.x 版本,完整流程如下。
第一步,备份旧二进制:
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old第二步,编译新版本,注意--prefix要和旧版本一致:
./configure --prefix=/usr/local/nginx ...(你的原有参数) make -j$(nproc)编译完成后,不要急着make install。make install会直接覆盖二进制和配置文件,如果你没备份,出问题想回滚就麻烦了。更稳妥的做法是先把新二进制拷贝过去:
cp ./objs/nginx /usr/local/nginx/sbin/nginx注意覆盖前确认旧二进制已经备份。
第三步,向正在运行的 master 进程发送 USR2 信号:
kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)这一步会让旧的 master 把 PID 文件改名为nginx.pid.oldbin,然后启动一个新的 master 进程和一组新的 worker。此时新旧两套进程同时在线,新 master 用的是新二进制。
第四步,让旧 master 优雅退出:
kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)QUIT信号会让旧 master 和它管理的 worker 在处理完当前连接后退出。等到旧进程全部消失,新版本就已经完全接管流量了。
验证一下:
nginx -v ps aux | grep nginx如果 ps 里只剩新的 master 和 worker,说明升级完成。
6.3 回滚:出了问题别慌,十几秒就能救回来
升级完发现新版本有 bug 怎么办?如果你按上面步骤备份了旧二进制,回滚非常快。只要把新 master 先停掉,再让旧的 master 复活。
kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid) kill -HUP $(cat /usr/local/nginx/logs/nginx.pid.oldbin)HUP会让旧 master 重新读取自己的配置并拉起 worker。等新 master 退出、旧 master 和 worker 稳定运行后,再把旧二进制恢复回去:
cp /usr/local/nginx/sbin/nginx.old /usr/local/nginx/sbin/nginx签名式的顺序很关键,先让进程切换,再改二进制文件,防止旧 master 还没起来时二进制已经被覆盖。
说到这我也提醒一句:很多人喜欢在源码目录里直接make upgrade,这个命令本质就是把上面步骤自动化了,会依次执行 USR2 和 QUIT。但它要求旧二进制必须放在objs/目录,实际生产中我反而更推荐手动执行信号操作,因为每一步都能看到进程状态,心里有底。
7. 配置不生效、502、403:排错思路与日志定位
最后这部分是实操里最有用的。配置报错不可怕,可怕的是“nginx -t 通过了,但行为完全不对”的这种玄学问题。我遇到过太多次了,这里把常见问题的排查链路整理出来。
7.1 第一板斧永远是 nginx -t 和日志
修改任何配置后,先做语法检查:
nginx -t它能帮你拦住语法错误、指令拼写错误、缺少模块这三大类问题。但注意,nginx -t只检查语法,不检查逻辑。你配置一个不存在的域名、写错了一个路径、include 了一个不在加载列表里的文件,它全都不会报错。
nginx -t通过后,真正的问题是看日志。日志位置取决于你的安装方式和配置,源码编译通常默认在--prefix下的logs/目录,包管理安装通常在/var/log/nginx/。排错核心命令:
tail -f /var/log/nginx/error.log tail -f /var/log/nginx/access.logerror.log会告诉你权限问题、文件路径问题、连接超时问题;access.log会显示每个请求的返回状态码和耗时。绝大多数排错都是靠这两个文件说话,而不是靠猜。
7.2 403、404、502、504 一张表说清楚
| 状态码 | 真实原因 | 排查方向 |
|---|---|---|
| 403 | nginx 没权限读文件、目录没有 index、被访问控制规则拦截 | 确认 user 权限、目录是否存在 index 文件、allow/deny 顺序 |
| 404 | root/alias 拼接出来的实际路径不对、location 没匹配上 | 看 error.log 里的实际文件路径,检查 location 优先级 |
| 502 | 后端没启动、端口不对、后端进程崩了 | ss检查端口,本机 curl 后端 |
| 504 | 后端响应时间超过 proxy_read_timeout | 调大超时,或优化后端性能 |
| 499 | 客户端提前断开 | 通常不是配置问题,关注后端日志 |
这里我想展开讲一个最典型的 502 案例。后端明明在监听,本机 curl 也通,但 nginx 转发就是 502。后来发现是proxy_pass http://localhost:8080;里 localhost 在服务器上被解析成了 IPv6 的::1,而后端只监听了 IPv4 的127.0.0.1,nginx 连 IPv6 地址当然连不上。解决方法是把localhost显式改成127.0.0.1。这个坑在云服务器上尤其常见。
还有一个“配置改了但总是不生效”的经典问题。分布式 Linux 的 nginx 包会把站点配置放在sites-enabled/里,而nginx.conf默认只 includeconf.d/*.conf。你如果在nginx.conf里改了配置,系统会提示你“不要再编辑这个文件的 server 块,去 sites-enabled 里改”,但很多人没注意。最后的结果是配置文件语法完全正确,nginx -t 也通过,但你修改的 server 块根本没被加载。
7.3 一些偏门但值得记住的排错点
第一是 upstream 的 DNS 缓存问题。nginx 在启动或者 reload 时才解析upstream里的域名,如果你的后端是动态 IP(比如云数据库的域名解析变了),nginx 不会实时感知,会一直往旧 IP 转发,直到 reload。这种情况要在 upstream 里开启 resolver:
resolver 8.8.8.8 valid=30s; upstream backend { server api.internal.example.com resolve; }但注意,server ... resolve;这个写法需要 nginx 版本支持,并且要谨慎使用,因为它会改变 upstream 的解析行为。
第二是浏览器缓存造成的“明明改了但没变”。改完静态资源配置后,你本机浏览器可能还在用旧的缓存响应。此时不要用浏览器刷新判断,直接命令行验证:
curl -I "http://your-server/static/a.css?v=12345"看返回的Cache-Control和Last-Modified再判断。
第三是多层反向代理场景下,如果你在 nginx 外层还有 CDN 或者其他负载均衡,注意X-Forwarded-For会叠加,后端拿到的 IP 列表是原始效果还是拼接效果,取决于你每层是否覆盖传递。
7.4 卸载 nginx 的干净方式
最后补一句卸载相关的。热词里也有“linux 系统卸载 nginx”。如果是最初用包管理器装的,删除干净需要三步:
systemctl stop nginx systemctl disable nginx apt-get remove --purge nginx nginx-common但源码编译安装的 nginx 没有独立的卸载命令,只能手动清理:先停进程,再删除--prefix指定目录,检查是否有软链接指向/usr/local/nginx,最后清理 systemd service 文件。nginx -s quit而不是kill -9,让它优雅退出,避免留下异常状态的监听端口。
回到排错本身,我的体会是:nginx 的大多数“疑难杂症”,追根溯源都是三层问题——路径拼错了、权限不够、或者进程之间状态没同步。只要你坚持用nginx -t验语法、用 error.log 看真实路径、再用curl分层验证,九成问题都能在 10 分钟内定位。真正难的不是配置,是建立一套“先验证环境,再怀疑配置”的排错顺序。这套顺序我基本每天都在用,也算是我写这篇 nginx 配置总结里最想传达的东西。