聊到 Nginx,很多刚接触服务端的朋友第一反应是“这不就是个 Web 服务器嘛”,等真正把它丢进生产环境才发现,Nginx 的作用和应用场景比想象中大得多:静态资源服务、反向代理、负载均衡、HTTPS 证书卸载、缓存加速、限流防刷……几乎每一层都能看到它的身影。这篇文章我想从一个“普通问题”聊起,把 Nginx 的核心作用一条条拆开,再结合我这些年实际踩过的坑,从安装配置到平滑升级、问题排查,给你一份可以直接抄作业的完整参考。
整套内容适合刚入门的人,也适合已经部署过 Nginx 但没系统梳理过的同学。我尽量不说废话,全部以实际场景和可复现的配置为准。
1. Nginx 到底在解决什么问题
先说一个最容易被忽略的事实:Nginx 最初解决的是 C10K 问题,也就是单机能不能扛住一万个并发连接。
2004 年它刚出来的时候,市面上的主流做法还是“每个请求一个进程”的 Apache 模型。连接一多,内存和 CPU 就被进程调度吃干净,机器直接卡死。Nginx 的思路完全不同,它用事件驱动、异步非阻塞的模型,用少量 worker 进程就能撑住海量连接。一个进程可以同时处理成千上万个请求,就像餐厅里一个优秀的排号员同时在服务几十桌客人,而不是每个客人配一个专属服务员。
放到今天,Nginx 的核心功能已经发展成四块:
- 静态资源服务:图片、CSS、JS、HTML、音视频,交给它又稳又快。
- 反向代理:把请求转发到后端的应用服务器,比如 Java 的 Spring Boot、Node.js、PHP-FPM。
- 负载均衡:把流量分摊到多台后端机器,避免一台被压垮。
- 安全与加速:SSL/TLS 证书卸载、HTTP/2、HTTP/3(QUIC)、限流、缓存、访问控制。
所以你看,很多团队把 Nginx 放在所有流量的最前面,它不是简单的“网页服务器”,而是整个系统的入口网关。
我个人的理解是:Nginx 是“连接用户和后端服务之间的那双手”。用户请求进来,它决定把人带到哪个页面、哪个后端接口、哪台服务器,如果后端挂了它还能帮忙挡一下。这个角色决定了它的配置方式五花八门,但底层逻辑始终只有一条——把请求处理到正确的地方。
下面我就逐个拆开讲。
2. 拆开 Nginx 的四个核心作用
2.1 静态资源服务:最基础也最容易被忽视
静态资源服务是 Nginx 的基本功,也是很多人第一次接触它的原因。你本地跑了一个 Vue 或 React 项目,执行pnpm run build之后生成一个 dist 目录,想让别人能访问,最简单的办法就是让 Nginx 直接托管这个目录。配合热词里看到的“pnpm run build 的包怎么 nginx 启动”,其实就是把构建产物丢到 Nginx 的 root 路径下。
一个最简单的托管配置:
server { listen 80; server_name example.com; root /data/www; index index.html; location / { try_files $uri $uri/ /index.html; } }注意最后那个try_files,SPA 项目基本都靠它。前端路由是 history 模式时,比如/user/123,服务器上根本不存在这个物理文件,如果不加try_files,直接刷新页面会 404。try_files $uri $uri/ /index.html的含义是:先找这个路径有没有对应文件,没有再找有没有对应目录,都没有就统一返回index.html,让前端路由自己去处理。
静态资源这块有几个关键性能参数值得单独说:
sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1k; location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 30d; add_header Cache-Control "public, immutable"; }sendfile让文件从磁盘到网卡的拷贝过程由内核直接完成,减少用户态切换。gzip压缩文本类文件,实测静态资源体积能减少 60% 以上。expires 30d给静态资源设置浏览器缓存,二次访问几乎无延迟。
我见过很多团队花大价钱优化后端接口,结果前端静态资源一个 gzip 都没开,首屏加载能慢三倍。静态资源托管是最简单的优化起点。
2.2 反向代理:让请求去它该去的地方
反向代理是 Nginx 使用频率最高的功能。所谓反向代理,就是用户请求先到 Nginx,Nginx 再按照规则转发到后端的应用服务器。用户可以感知到的只有 Nginx,后端服务器具体在哪、有多少台,对用户是透明的。
对应的还有正向代理,那是替客户端转发请求的,常用于内网访问外网。Nginx 做的是反过来的事,替服务器收请求,所以叫反向代理。
一个典型的 API 转发配置:
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 X-Forwarded-Proto $scheme; } }这里有几个容易忽略的点:
第一,proxy_set_header Host $host非常重要。后端很多框架会根据 Host 头生成跳转链接或判断域名,如果不带上,后端拿到的一律是 Nginx 的内网地址,签名校验、单点登录、跨域这些全都会出问题。
第二,X-Real-IP和X-Forwarded-For是为了让后端拿到用户真实 IP。如果没有这两个配置,后端日志里看到的所有请求 IP 都是 Nginx 的地址,一旦要做封禁、限流、审计,完全没法搞。
第三,proxy_pass后面有没有子路径,行为完全不一样。比如:
location /api/ { proxy_pass http://backend/; }这种写法会把/api/前缀去掉再转发。而:
location /api/ { proxy_pass http://backend; }这种不带尾部/的,会把完整的/api/...路径直接拼到后端地址后面。这个细节是大坑,我见过不下五次因为这里多一个斜杠少一个斜杠导致接口 404。
热词里有一条“nginx 限制只转发带参数的 url”,这个需求本质就是按照查询参数决定要不要转发。常见做法是在 location 里判断$arg_或$query_string:
location /api/ { if ($args ~ "token=.+") { proxy_pass http://backend; break; } return 404; }意思很直白:请求里带了 token 参数才转发,否则直接返回 404。break的作用是命中 if 之后不再继续走后续 rewrite 规则。需要注意的是,Nginx 的if指令在很多场景下有坑,官方文档只建议在 return、rewrite 这类场景用,整体转发逻辑尽量谨慎,能用location或map实现就不要硬写一堆 if。
2.3 负载均衡:把流量摊到多台机器上
当单台后端扛不住并发,你就需要横向扩容,前面放一个 Nginx 做负载均衡。Nginx 的upstream模块就是干这个的,配合请求量把流量分发到不同后端。
最小的负载均衡配置:
upstream backend_cluster { server 192.168.1.10:8080 weight=3; server 192.168.1.11:8080 weight=1; server 192.168.1.12:8080 backup; } server { listen 80; server_name app.example.com; location / { proxy_pass http://backend_cluster; proxy_set_header Host $host; } }这里我用了三个节点:
- 前两台权重是 3:1,意思是每 4 个请求里约 3 个打到 10 这台,1 个打到 11 这台。权重适合两台机器配置不一样的场景,配置高的多加一点流量。
- 第三台打了
backup标记,平时不参与服务,只有前面两台都挂了才启用。这相当于一个灾备节点,自动化切换。
除了权重轮询,Nginx 还支持:
ip_hash:按用户 IP 的哈希结果分配,同一 IP 固定打到同一台后端。适合需要 session 保持的老项目。least_conn:优先发给当前连接数最少的后端,适合请求处理时长差异较大的场景。
负载均衡不是单纯“把请求发出去”,还要考虑后端健康状态。Nginx 有被动健康检查,即请求转发后如果连续失败max_fails次,就把这台服务器临时标记为不可用,等fail_timeout时间后再重试。常用配置:
upstream backend_cluster { server 192.168.1.10:8080 max_fails=2 fail_timeout=30s; server 192.168.1.11:8080 max_fails=2 fail_timeout=30s; }意思是 30 秒内失败 2 次就摘掉这个节点,30 秒后再试探。这种机制应对日常宕机足够了,但它属于“事后发现”,请求已经转发过去并失败了。如果要求更主动的健康探测,得用商业版 Plus 或者配合第三方模块,也可以用脚本定时探测后动态修改 upstream。
关于高可用,线上一般会再加一层 keepalived,把 Nginx 本身做成双机热备,用虚拟 IP(VIP)对外提供服务。一台 Nginx 挂了,VIP 自动漂移到另一台,对用户完全无感知。这里不展开讲 keepalived 的配置,但方向是明确的:Nginx 做流量入口,keepalived 做入口的 “保险丝”。
2.4 SSL/TLS 终端:证书卸载与安全加速
现在大部分网站都是 HTTPS,证书配置是每个 Nginx 用户绕不开的活。Nginx 在 SSL 这块的位置也非常特殊,它通常是 TLS 连接的“终点站”,外网客户端和 Nginx 之间走 HTTPS,Nginx 和后端之间可以走内网 HTTP。这样做的原因很实际:TLS 握手和加解密都是 CPU 密集操作,把这事集中在 Nginx 这一层做,后端应用就能省出大量 CPU 去处理业务逻辑。
一段常规 HTTPS 配置:
server { listen 443 ssl; http2 on; server_name www.example.com; ssl_certificate /etc/nginx/certs/example.com.pem; ssl_certificate_key /etc/nginx/certs/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://backend; } } server { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }第一个server块监听 443 端口,加载证书,处理加密流量;第二个server块把 80 端口的 HTTP 请求全部 301 跳转到 HTTPS。很多小项目直接用这一套就完成了 HTTPS 改造。
ssl_protocols建议只保留 TLSv1.2 和 TLSv1.3,老旧的 TLSv1.0、TLSv1.1 都有已知漏洞,没必要为了兼容十几年前的浏览器留着。
顺带说一下热词里的 “docker pull nginx quic 协议”。QUIC 是 HTTP/3 的底层传输协议,Nginx 从 1.25.0 开始对 HTTP/3 的支持逐渐成熟。要启用 HTTP/3,Nginx 编译时需要加--with-http_v3_module参数,然后在 listen 指令里加上http3:
listen 443 quic reuseport; listen 443 ssl; http3 on;如果你的 Nginx 是官方通过 yum/apt 安装的版本,先确认版本和编译参数是否带了 HTTP/3 模块,可以用nginx -V看。Docker 镜像docker pull nginx拉下来之后,官方主线版镜像是否包含 v3 模块取决于镜像构建参数,需要先nginx -V 2>&1 | grep http_v3验证。如果没带,就考虑用源码编译或换成带模块的镜像。QUIC 确实能显著改善弱网环境下的连接成功率,但部署复杂度也更高,不是所有项目都急着上。
3. 应用场景与选型:什么时候该用 Nginx
3.1 前端接入层:统一入口
大多数 Web 项目的第一层入口就是 Nginx。它的作用相当于一个“前台接待”,所有外部请求先到这里,再根据域名、路径、请求头分发到不同服务:
- 按域名区分:
api.example.com走 API 服务,admin.example.com走管理后台。 - 按路径区分:
/api/*走后端接口,/static/*走静态资源,/websocket走长连接服务。 - 按请求方法区分:读接口和写接口分到不同的上游。
有了这一层,后端的任何服务都不需要直接暴露公网 IP,只需要监听内网端口,整个入口的收口和安全控制都变得很轻松。
限流也是入口层常见的需求。比如给登录接口加限制:
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=10r/m; location /api/login { limit_req zone=login_limit burst=5 nodelay; proxy_pass http://backend; }这里rate=10r/m表示每分钟最多 10 个请求,burst=5表示允许突发 5 个进入排队队列。对登录、短信验证码这类高风险接口,限流是必须的。
3.2 动静分离:前端静态资源与后端动态接口解耦
传统后端渲染的项目,尤其是 PHP、Java 单体应用,静态资源和动态接口都混在一起。用户访问一个页面,服务器既要读模板文件,又要查数据库,全部串行处理,慢且耗资源。
用 Nginx 做动静分离之后,静态资源直接走 Nginx 文件系统,动态请求才转发给后端:
location ~* \.(html|css|js|png|jpg|gif|ico|svg|woff2?)$ { root /data/static; expires 7d; } location / { proxy_pass http://backend; }动静分离对混合架构特别有用。比如前端用 React 构建静态页面,后端用 Java 提供 API,整体结构就是:Nginx 托管前端静态文件,同时把/api/请求转发到 Java 服务。这也是现在最常见的前后端分离部署形态。
3.3 微服务与 API 网关场景
微服务架构里,每个服务可能单独部署在一组机器上,客户端不可能记住每个服务的地址。Nginx 可以作为轻量 API 网关,按路径把请求分发到不同的微服务:
upstream order_service { server 10.0.0.11:8080; server 10.0.0.12:8080; } upstream user_service { server 10.0.1.11:8080; server 10.0.1.12:8080; } server { listen 80; server_name gateway.example.com; location /api/order/ { proxy_pass http://order_service/; } location /api/user/ { proxy_pass http://user_service/; } }服务规模不大时,这种轻量网关方案比引入全套微服务网关框架要简单得多。它没有臃肿的依赖,规则就是纯文本配置文件,Git 管理、版本回滚都方便。只有当你需要复杂的服务发现、动态路由、熔断、灰度发布时,才应该考虑更重的网关方案。
3.4 Nginx、Apache、HAProxy 怎么选
这是一个被问烂了但又必须回答的问题。我习惯用下面这张表总结:
| 对比项 | Nginx | Apache | HAProxy | Envoy |
|---|---|---|---|---|
| 并发模型 | 事件驱动异步非阻塞 | 进程/线程模型 | 事件驱动 | 事件驱动 |
| 静态资源处理 | 强 | 一般 | 不支持 | 一般 |
| 七层路由能力 | 强 | 强 | 较弱 | 强 |
| 四层转发(TCP/UDP) | 支持(stream) | 较弱 | 非常强 | 支持 |
| 动态配置 | 需 reload | 需 reload | 需 reload | 支持 API 热更新 |
| 上手成本 | 低 | 低 | 中 | 高 |
| 生态成熟度 | 极高 | 高 | 高 | 快速增长 |
简单说:需要同时处理静态文件和动态反代,首选 Nginx;纯四层高并发流量转发,HAProxy 更专业;在 Kubernetes 里做 Ingress Controller,常见的 nginx-ingress 或 Envoy 都比较合适;需要动态路由和灰度发布,Envoy 这类云原生网关更应景。
我这几年线上项目基本都跑 Nginx,只有在一台机器上要对大量 TCP 端口做负载均衡时才考虑 HAProxy。Nginx 的最大优势是“中庸且全面”,大部分场景一个它就能全包。
4. 亲手搭一套:安装、配置与实操细节
4.1 安装 Nginx:从包管理到 Docker 到源码
不同的部署环境,安装方式不一样。我这里列三种最常用的。
包管理器安装是最快的:
# Debian / Ubuntu apt update apt install -y nginx # CentOS / RedHat / Rocky yum install -y nginx # 或者 dnf install -y nginx包管理器安装的好处是省事,版本随系统源走,能用 systemd 管理。缺点是版本通常偏旧,可能缺少新特性,比如 HTTP/3 模块。
如果是内网环境,没有外网访问,就需要离线安装。思路是找一台同系统版本的机器,可以联网装好 Nginx 和依赖,用 rpm 或 deb 包导出再拷贝。CentOS 下:
# 能联网的机器上 mkdir nginx-packages yum install --downloadonly --downloaddir=nginx-packages nginx然后把整个目录拷到内网机器,rpm -ivh nginx-packages/*.rpm即可。这里容易踩依赖坑,Nginx 依赖的 pcre、openssl、zlib 可能也被装到下载目录里了,拷过去一起装通常没问题。离线安装前最好先确认系统版本完全一致,我遇到过开发机是 CentOS 7.9、生产机是 Rocky 9,rpm 包互相不兼容,白折腾一小时。
Docker 方式适合容器化部署:
docker pull nginx:stable docker run -d --name my-nginx \ -p 80:80 -p 443:443 \ -v /data/www:/usr/share/nginx/html \ -v /data/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ nginx:stableDocker 镜像的好处是环境隔离,升级和回滚都方便。但要注意,容器里的 Nginx 配置文件是短路径,和宿主机不一定完全对应;日志最好也挂载出来,不然docker logs看起来费劲。另外如果想用 QUIC/HTTP/3,得先确认镜像里的 Nginx 是否带http_v3_module。
源码编译安装是自由度最高的方式,也是平滑升级的前提:
wget https://nginx.org/download/nginx-1.26.2.tar.gz tar xzf nginx-1.26.2.tar.gz cd nginx-1.26.2 ./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_v3_module \ --with-stream \ --with-stream_ssl_module make make install--prefix决定安装路径,后续升级、回滚都要依赖这个路径,一定要记住。--with-http_ssl_module是 HTTPS 必需的,--with-stream是四层 TCP/UDP 转发用的,--with-http_v3_module是为了 HTTP/3。
装完之后先验证版本:
nginx -v nginx -Vnginx -V会输出完整的编译参数,这个信息在升级时必须保留,后面讲平滑升级时会用到。
4.2 配置文件结构与关键参数
Nginx 主配置文件默认在/etc/nginx/nginx.conf(源码安装则在--prefix下的conf/nginx.conf)。核心结构如下:
user nginx; worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 4096; } http { include /etc/nginx/mime.types; include /etc/nginx/conf.d/*.conf; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; } }几个关键参数:
worker_processes auto:通常设置为 CPU 核数,Nginx 每个 worker 进程可以充分利用一个核。设多了反而引起上下文切换开销。worker_connections 4096:每个 worker 进程最多同时处理的连接数。最大并发连接数约等于worker_processes * worker_connections。如果这个值太小,高并发时日志里会出现 worker_connections are not enough。worker_rlimit_nofile:单个进程可以打开的最大文件数。因为每一条 TCP 连接都对应一个文件描述符,这个值太小并发一高就报 too many open files。include:把主配置拆分成多个子配置文件,方便管理。推荐每个站点或每个应用单独建一个 conf 文件,放在/etc/nginx/conf.d/下,而不是全部堆在一个文件里。
4.3 反向代理 + 负载均衡完整示例
我把两个功能合在一起给一份可以直接用的完整配置:
upstream app_backend { least_conn; server 10.0.0.10:8080 max_fails=2 fail_timeout=30s; server 10.0.0.11:8080 max_fails=2 fail_timeout=30s; } server { listen 80; server_name app.example.com; access_log /var/log/nginx/app.access.log; error_log /var/log/nginx/app.error.log; location / { proxy_pass http://app_backend; 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 30s; } location /static/ { alias /data/static/; expires 7d; } }这里把/static/请求直接交给 Nginx 读文件系统,其他请求全部负载均衡到后端。alias和root的区别值得单独强调:root会把 location 的路径拼接在根目录后面,比如root /data/static; location /static/时请求/static/a.png会找/data/static/static/a.png;alias /data/static/时则找/data/static/a.png。用错这两个指令,静态资源会全部 404,这是新手最容易踩的坑之一。
proxy_connect_timeout 5s是 Nginx 与后端建立 TCP 连接的超时时间,设太短后端偶尔忙一下就会 502。proxy_read_timeout 30s是读取后端响应的超时时间,如果后端有长任务接口,比如导出报表要跑一分钟,这里得对应调大。
4.4 HTTPS 证书配置实战
这里以已有证书文件为前提,不展开怎么申请证书,直接说配置:
server { listen 443 ssl; http2 on; server_name www.example.com; ssl_certificate /etc/nginx/certs/www.example.com.pem; ssl_certificate_key /etc/nginx/certs/www.example.com.key; ssl_session_timeout 1d; ssl_session_cache shared:SSL:10m; # 安全协议配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } }ssl_session_cache shared:SSL:10m是很多团队容易漏的配置。TLS 握手是乘法运算,每次建立新连接都要重新跑一次,代价很高。开了 session cache 之后,同一台客户端一段时间内可以复用会话密钥,握手开销大幅下降。10m 大概能缓存几万个 session,足够日常使用。
证书到期是一个高频事故。建议配一个 crontab 定时任务检查证书有效期:
0 0 * * * /usr/bin/openssl x509 -enddate -noout -in /etc/nginx/certs/www.example.com.pem每个月跑一次,看到快到期就提前换。我见过太多次证书过期导致线上全站报错,起因就是大家都不记得证书是去年哪一天配的。
4.5 前端构建产物的部署与“401 验证身份”配置
热词里的 “pnpm run build 的包怎么 nginx 启动”,我再展开一下。前端项目构建完得到 dist 目录,部署到服务器/data/www,Nginx 配置:
server { listen 80; server_name front.example.com; root /data/www; index index.html; location / { try_files $uri $uri/ /index.html; } }前端路由如果是 hash 模式,try_files那行其实不加也能跑。但 history 模式必须加,否则用户点击浏览器刷新、或直接访问二级路由时会 404。
有些后台页面需要访问控制,Nginx 自带最简单的 HTTP Basic Auth,配置两个指令就行:
location /admin/ { alias /data/www/admin/; auth_basic "Restricted Area"; auth_basic_user_file /etc/nginx/.htpasswd; }然后用工具生成密码文件:
htpasswd -c /etc/nginx/.htpasswd admin这个命令会提示输入密码,生成的文件里存的是用户名和密码哈希。之后访问/admin/就会弹浏览器原生认证框,输入账号密码才能访问。热词里提到的 “index.php 401 验证身份” 和这个类似,如果后端是 PHP 并且接口返回 401,要么是auth_basic导致的安全拦截,要么是后端代码里自己做了登录校验。先用curl -I看 401 来自哪个响应头,如果响应头里有WWW-Authenticate: Basic realm=...,基本就是 Nginx 的auth_basic在拦。
5. 平滑升级、版本管理与踩坑记录
5.1 Nginx 平滑升级到底怎么操作
为什么要单独讲平滑升级?因为很多人直接用包管理yum update nginx,版本是升了,但线上连接会被切断,运气不好配置项兼容性还会出问题。尤其热词里提到“Nginx 升级到新版本要注意什么”,这绝对是运维里一个高风险动作。
源码编译的 Nginx 平滑升级标准步骤如下:
第一步,查看当前版本和编译参数:
nginx -V把输出的 configure arguments 完整记录下来,新版本编译时参数要和原来一致,不然升级完某些模块就没了。
第二步,下载新版源码,用同样的 prefix 和 configure 参数编译:
wget https://nginx.org/download/nginx-1.26.2.tar.gz tar xzf nginx-1.26.2.tar.gz cd nginx-1.26.2 ./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_v3_module \ --with-stream make注意这里只执行make,不要执行make install,否则会直接覆盖老版本,少了回滚机会。
第三步,备份旧二进制并替换:
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old cp objs/nginx /usr/local/nginx/sbin/nginx第四步,向旧 master 进程发送 USR2 信号,启动新 master:
kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)这时新旧 master 会同时存在,新 worker 进程已接管配置。再发送 WINCH 信号给旧 master,让它优雅关闭旧 worker:
kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)升级完成后,新版本就用原 pid 文件,旧进程信息在nginx.pid.oldbin里。确认一切正常后,可以把旧的二进制文件收起来,避免误用。如果新版本有问题想回滚:
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.new mv /usr/local/nginx/sbin/nginx.old /usr/local/nginx/sbin/nginx kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid.oldbin) kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid)这套流程在热词里对应“nginx 平滑升级指南”,是线上升级的必修课。核心原则是:能热切换就不要冷重启,能回滚就不要硬着头皮修。
5.2 日常运维:reload、stop、日志切割
日常管理命令不多,但每一条都要记清楚:
| 命令 | 作用 |
|---|---|
nginx -t | 检查配置语法并显示测试结果 |
nginx -s reload | 平滑重载配置,不中断服务 |
nginx -s stop | 快速停止服务 |
nginx -s quit | 优雅停止,处理完当前请求再退出 |
systemctl status nginx | 查看服务状态和最近日志 |
nginx -V | 查看编译参数和版本 |
Windows 下关闭 Nginx 是nginx.exe -s stop或nginx.exe -s quit,对应热词里的 “cmd 关闭 nginx”。注意 Windows 下 nginx 不推荐二进制的生产部署,但本地调试完全没问题。
日志切割也经常踩坑。Nginx 默认把日志写在一个文件里,时间一长就是几十 GB。标准做法是用 logrotate:
/var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty create 640 nginx adm sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid` endscript }关键是postrotate里的kill -USR1,这个信号会让 Nginx 重新打开日志文件。如果不发这个信号,你就算把旧日志改名了,Nginx 还在往旧文件里写,等于没切割。
5.3 上线前我必做的“三查”
配置上线前,我给自己定了一个固定流程,到今天还在用:
第一查,nginx -t。这步不用多说,语法错误必须在这层拦掉。但我见过有人配完忘了跑,直接 reload,结果 reload 失败,线上老配置还在跑,新配置根本没生效,排查半天。
第二查,检查权限和端口。Nginx 报Permission denied时,多半是静态文件目录没有读权限,或者 SELinux 没放行。检查端口占用:
ss -lntp | grep :80如果端口被其他进程占了,Nginx 会报bind() to 0.0.0.0:80 failed。
第三查,做一次真实请求验证。用 curl 测:
curl -I http://127.0.0.1:80 curl -I -k https://127.0.0.1:443加上-I只看响应头,能快速判断 HTTP 状态码是不是预期。如果 502,去看 Nginx error.log 和后端服务状态;如果 403,去看目录权限和 index 文件;如果 404,先确认 root/alias 路径对不对。
6. 常见问题与排查技巧
6.1 Nginx 状态码速查表
排查问题时,状态码是第一手信号。我把最常见的整理成一张表:
| 状态码 | 含义 | 常见原因 |
|---|---|---|
| 301 | 永久重定向 | http 跳 https 配置 |
| 302 | 临时重定向 | 登录跳转、鉴权跳转 |
| 304 | 未修改 | 命中本地缓存,Nginx 返回 not modified |
| 400 | 请求错误 | 请求头格式异常、参数非法 |
| 401 | 未认证 | auth_basic 或后端登录校验失败 |
| 403 | 禁止访问 | 目录权限不足、无 index 文件、IP 被封 |
| 404 | 未找到 | root/alias 路径错误、SPA try_files 缺失 |
| 405 | 方法不允许 | 静态文件上 POST 请求未处理 |
| 413 | 请求体过大 | client_max_body_size 设置过小 |
| 429 | 请求过多 | limit_req 限流触发 |
| 500 | 服务器内部错误 | 后端应用异常 |
| 502 | 网关错误 | 后端服务未启动、端口不通、超时 |
| 503 | 服务不可用 | 后端无可用节点、正在维护 |
| 504 | 网关超时 | 后端处理超时,proxy_read_timeout 太小 |
6.2 高频故障排查实录
502 Bad Gateway 是最常见的故障。排查顺序:
systemctl status nginx确认 Nginx 本身活着。- 检查后端服务是否启动:
ss -lntp | grep 8080。 - 在后端机器上直接
curl http://127.0.0.1:8080/health,确认后端本身能不能访问。 - 如果后端正常但 Nginx 还是 502,看 Nginx error.log:
tail -f /var/log/nginx/error.log常见报错是connect() failed (111: Connection refused)或connect() failed (110: Connection timed out)。前者说明端口没开或者 IP 不通,后者说明防火墙或网络策略拦了。
504 是另一个高发问题。典型场景是后端接口本身要跑很久,比如导出大量数据,Nginx 默认proxy_read_timeout 60s,后端 60 秒内没返回,Nginx 就主动断开返回 504。解决办法是给长任务接口单独配一个 location,调大超时时间:
location /api/export/ { proxy_pass http://backend; proxy_read_timeout 300s; }403 往往不是权限问题就是索引问题。我遇到最多次的是两种:一是 root 目录下的文件权限不是 nginx 用户可读,二是autoindex off且目录下没有 index.html。先ls -l看权限,再确认目录下有没有 index 文件。另外 CentOS 系统还要注意 SELinux,getenforce一下,如果是 Enforcing,试试setsebool -P httpd_can_network_connect 1放行。
404 分清是 Nginx 的还是后端的。如果请求打到后端接口返回 404,那是后端路由问题;如果是 Nginx 直接返回 404 的页面,多半是 root 或 alias 配错。判断方法是看错误日志:
tail -f /var/log/nginx/error.log日志里会写清楚 “open() “/data/www/xxx” failed (2: No such file or directory)”,告诉你 Nginx 实际在找哪个文件,对照一下就明白了。
413 Request Entity Too Large是老生常谈。上传文件时报这个错,就是client_max_body_size没配或太小:
client_max_body_size 20m;放在http、server或location块里都行,location里的优先级最高。
6.3 我踩过的一些坑
先说一个关于if的大坑。Nginx 的if指令被官方称为 “evil”,因为它在location里和proxy_pass同时出现时行为很容易不符合直觉。不是不能用,而是只用它做改路径或 return,不要在里面写复杂逻辑。有一次我在if里设置变量再proxy_pass,结果每次 reload 都报错,最后改成用map做变量映射才解决。
再说 reload 不是“不会断”。nginx -s reload理论上不会中断现有连接,但如果你的配置里改动了 upstream 地址,正在处理的长连接会被切掉。所以我建议不要在业务高峰期做这种改动,尽量安排在凌晨低峰期。
还有一个大家都容易忽略的点:改了配置一定要先nginx -t再 reload。这个习惯我强调过无数次,但每个月依然能遇到没跑测试直接 reload 导致线上配置状态混乱的情况。反正就一条命令,多敲一下不亏。
最后是日志。排查问题第一件事永远是看日志,很多人习惯先猜。Nginx 的 access_log 和 error_log 分开看:
error.log记录错误,比如连接失败、权限不足、配置文件报错。access.log记录每次请求,状态码、耗时、来源 IP 都在里面。
分析慢请求时,在 Nginx 配置里加上$request_time字段,就能从 access log 里看出哪些接口响应慢:
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'rt=$request_time urt=$upstream_response_time';这里$request_time是 Nginx 处理请求的总时间,$upstream_response_time是后端响应时间。两者差值大,说明瓶颈在 Nginx 到后端这一段或网络;两者都大,基本就是后端接口本身慢。
7. 最后分享一点个人心得
Nginx 这个工具,文档写得很全,配置语法也不算难,真正的难度在于你对自己系统的请求链路有没有想清楚。我在生产环境折腾 Nginx 这几年,最大的体会是:配置之前先画清楚“用户请求从哪进来、经过哪些层、最后到哪台机器哪个接口”,再动手写配置文件,基本不会出大错。
每次上线前,我会强制自己做三件事:备份当前配置、执行nginx -t、把 access log 打开看两分钟真实请求。这套流程看起来土,但比任何花哨的监控面板都好使。另外,虽然我前面讲了不少进阶功能,但如果不是业务需要,不要为了炫技硬加功能。配置每多一层,故障面就大一分,保持“够用且可维护”才是最好的状态。
如果后面有机会,我打算再单独写一篇关于 Nginx 与 keepalived 高可用、以及 HTTP/3 QUIC 实战部署的内容。你们在配置 Nginx 时遇到过最诡异的问题是什么?欢迎留言交流,说不定下一篇文章就是专门为你排坑写的。