← 全部材料 Nginx
NGINX · 14 个必答点

网关层的坑都在细节里

Nginx 的配置看起来简单,出问题的地方却很集中:location 优先级、proxy_pass 的斜杠、add_header 的继承。这三处答不清,说明只抄过配置没排过障。

01先讲模型再讲配置多进程事件循环决定了它能扛多少并发、什么情况下会退化。
02三处高频陷阱要能背location 优先级、斜杠、add_header 继承,几乎每场都问。
03故障按码定性502 查可达性,504 查耗时,先看 error.log 再猜。
1

Nginx 为什么能扛住高并发★

不是"用了 epoll"这么简单
Nginx 的高并发来自三件事的组合:多进程 + 每进程单线程事件循环 + 非阻塞 IO。单线程意味着没有线程切换和锁开销,事件循环让它用极少的资源管理几万个连接。反过来,任何阻塞操作都会卡住整个 worker。

传统模型:一连接一线程

  • 每个连接一个线程/进程
  • 并发数受内存和线程切换限制
  • 上万连接基本撑不住
  • 阻塞 IO:读不到数据就干等

Nginx:事件驱动

  • 少量 worker,每个都是单线程事件循环
  • 一个 worker 管理数万连接
  • 非阻塞 IO:没数据就先去处理别的
  • 没有锁竞争,CPU 缓存友好
一个数字对比(量级示意)Apache prefork(一连接一进程) 并发几百到几千就吃满内存 Nginx(事件驱动) 单机几万连接是常见水平 原因不在"代码写得好",而在模型: 等待数据的时间被复用来处理其他连接,而不是用来阻塞线程
面试怎么答Nginx 是 master 加多个 worker 的多进程模型,每个 worker 是单线程事件循环,用 epoll 做非阻塞 IO。高并发不是靠线程多,而是靠等待数据的时间被复用来处理其他连接。代价是 worker 里不能有阻塞操作,磁盘慢或者用同步模块都会拖垮整个 worker。
2

master / worker 与平滑重启★

改配置为什么不用停服务
master 只做管理(读配置、拉起 worker、处理信号),worker 真正处理请求。reload 时 master 拉起新 worker,等旧 worker 把手上的请求处理完再退出,所以能做到零中断。
master 启动读配置,创建监听 socket
→
fork worker多个 worker 共享同一个监听端口
→
worker 接请求由内核决定哪个 worker 拿到连接
→
nginx -s reloadmaster 校验新配置
→
新 worker 起用新配置开始接新连接
→
旧 worker 退处理完在途请求后优雅退出
面试怎么答master 进程只负责管理:读配置、fork worker、处理信号。worker 才是真正接请求的。reload 时 master 校验新配置后拉起新 worker 接新连接,旧 worker 处理完在途请求再退出,所以不断服务。多个 worker 共享监听端口靠的是 SO_REUSEPORT,由内核分发。
3

配置结构与继承规则

上下文决定指令能不能写
Nginx 配置是树形上下文:main → events / http → server → location。每个指令只能在特定上下文里出现,写错位置会直接报 directive is not allowed here。继承规则是内层覆盖外层,同层不合并。
四个关键上下文能写什么main worker_processes、user、pid、error_log(全局级) events worker_connections、use epoll http mime、gzip、upstream、log_format、include server listen、server_name、ssl、location、root location proxy_pass、try_files、add_header、rewrite
面试怎么答Nginx 配置是树形上下文,main、events、http、server、location 逐层嵌套,每个指令有它能出现的层,写错位置会直接报错。继承原则是内层覆盖外层,但同层的多个指令不会合并——这一点在 add_header 上最容易踩坑。
4

location 匹配优先级★

这道题几乎每场都问
优先级从高到低:= 精确 → ^~ 前缀且不再查正则 → ~ / ~* 正则(按配置顺序) → 普通前缀(最长匹配)。记住一条主线:精确最优先,正则次之,普通前缀最后。
◐ 可交互:输一个 URI,看哪条 location 赢
先找 =有完全相同的就立刻用,结束
→
再找 ^~最长前缀命中且带 ^~,直接用,不查正则
→
再按顺序试正则第一个匹配的正则就是结果
→
最后用最长前缀没有正则命中时,用最长的普通前缀
面试怎么答优先级是先精确匹配等于号,然后是最长前缀带 ^~(命中就不再查正则),然后是正则按书写顺序取第一个匹配,最后才是普通前缀取最长。前缀之间比长度,正则之间比顺序,这两个规则不一样。^~ 的作用就是防止被后面的正则抢走。
5

proxy_pass 的斜杠★

多一个斜杠,路径就变了
规则只有一条:proxy_pass 后面带 URI(有斜杠或有路径)时,会把 location 匹配到的前缀替换掉;不带 URI 时,原样转发完整路径。这是反向代理最常见的 404 原因。
四种写法的实际转发结果请求: GET /api/user/1 location /api/ { proxy_pass http://backend; } -> /api/user/1 location /api/ { proxy_pass http://backend/; } -> /user/1 location /api/ { proxy_pass http://backend/v1; } -> /v1user/1 ← 注意这里 location /api { proxy_pass http://backend/v2/; } -> /v2/user/1
面试怎么答proxy_pass 后面如果带 URI(包括单独一个斜杠),Nginx 会把 location 匹配到的前缀替换成这个 URI;不带 URI 就原样转发完整路径。所以 location 和 proxy_pass 的斜杠要么都有要么都没有,一边有一边没有就会拼出错误路径,这是 404 最常见的原因。
6

负载均衡策略

五种算法各自的适用场景
默认是轮询;weight 按权重分流;ip_hash 保证同一 IP 落到同一台(解决 session);least_conn 优先给连接数少的(适合长连接和请求耗时差异大);hash 按自定义 key 分片。
选型判断无状态服务,机器同配置 轮询 机器配置差异大 weight 有会话且没做集中存储 ip_hash(或直接的粘性 cookie) 请求耗时差异大 / 长连接 least_conn 需要按用户或租户分片 hash $key consistent
面试怎么答默认用轮询;机器配置不同用 weight 按能力分配;没做集中式 session 时用 ip_hash 保证同一 IP 打到同一台,但要注意 NAT 后面很多人会共用一个 IP;请求耗时差异大或长连接多用 least_conn。另外要配 keepalive 减少和上游的握手开销。
7

静态资源与缓存★

强缓存和协商缓存怎么配
两类缓存要分开:带 hash 的产物用强缓存(expires 1y + immutable),入口 HTML 用协商缓存(no-cache,每次都问)。配反了就会出现"改了代码用户看不到"或"每次都全量下载"。
两种缓存的行为差异强缓存(Cache-Control: max-age=31536000) 浏览器直接用本地副本,不发请求 适合:内容变了文件名也会变的产物 协商缓存(Cache-Control: no-cache + ETag/Last-Modified) 每次问服务器"变了吗",没变回 304 适合:index.html 这种地址固定但内容会变的文件
面试怎么答按文件名是否带 hash 来区分。带 hash 的 JS/CSS 配一年强缓存加 immutable,内容变了文件名也变,不会读到旧文件;入口 HTML 必须用 no-cache 走协商缓存,否则发版后用户还在用旧的 HTML,引用的还是旧 hash 的资源。这是前端发布最经典的一套配置。
8

压缩:gzip 与 brotli

开之前先看这两个参数
压缩能省 60%~80% 的传输体积,但配置错会有两个坑:对图片视频也压(无效且浪费 CPU)、gzip_comp_level 开太高(CPU 换来的收益很小)。另外压缩和 sendfile 一起用要开 gzip_static。
面试怎么答gzip 的性价比拐点在 level 5 左右,再往上 CPU 开销大增但体积几乎不降。要设 gzip_min_length 跳过小文件,开 gzip_vary 避免 CDN 缓存串版本,并且只压文本类型,图片视频不要再压。更彻底的做法是构建时就生成 .gz 和 .br,用 gzip_static 直接发。
9

HTTPS 与 HTTP/2

证书、协议、跳转三件事
HTTPS 配置本身不复杂,容易出问题的是三处:证书链不完整(浏览器报不受信任)、HTTP 没跳 HTTPS、开了 HTTP/2 但没配 ALPN。TLS 版本要禁用 1.0 / 1.1。
面试怎么答HTTPS 要配完整证书链,只配站点证书会导致部分客户端报不受信任;TLS 只留 1.2 和 1.3;80 端口统一 301 跳到 443。开 HTTP/2 能多路复用减少握手,但服务端推送已经废弃。HSTS 要谨慎,一旦下发在有效期内无法回退。
10

跨域与 add_header 的继承陷阱★

配了却没生效,多半是这个原因
add_header 的继承规则很反直觉:子级一旦有自己的 add_header,就会完全覆盖父级的全部 add_header,不是合并。所以 location 里加了一条,server 层的 CORS 头就全没了。
面试怎么答add_header 的继承是"覆盖"不是"合并":子级 location 里只要写了自己的 add_header,父级 server 上的全部 add_header 都会失效。所以要么每个 location 写全,要么用 include 抽成公共文件。另外要加 always 参数,否则错误响应不带 CORS 头,前端会误报成跨域问题。
11

rewrite 与 return

能用 return 就别用 rewrite
两者都能改地址,但用途不同:return 直接返回状态码或跳转,性能最好且行为明确;rewrite 会重写 URI 并继续走 location 匹配,容易产生意料之外的循环。能定死就用 return。
常见场景的正确写法整站跳 HTTPS return 301 https://$host$request_uri; ← 用 return 旧域名跳新域名 return 301 https://new.com$request_uri; 去掉末尾斜杠 rewrite ^/(.*)/$ /$1 permanent; 老路径映射到新路径 rewrite ^/old/(.*)$ /new/$1 permanent; 内部转发不改 URL rewrite ^/api/(.*)$ /internal/$1 last;
面试怎么答return 直接返回状态码或跳转,性能最好、行为最确定,整站跳转和域名迁移都用它。rewrite 会重写 URI 并继续走匹配流程,last 是重新匹配 location、break 是不再匹配,用错会死循环。原则是能用 return 就不用 rewrite,能不用 if 就不用 if。
12

限流:limit_req 与 limit_conn

漏桶和并发数两道闸
limit_req 限速率(漏桶,按请求数),limit_conn 限并发(按连接数)。两者针对的问题不同:前者防刷接口,后者防大文件下载把连接占满。
两个参数的含义rate=10r/s 平均每秒 10 个请求 burst=20 允许多出 20 个排队(超过就 503/429) nodelay 排队的立刻处理,只限平均速率 不加 nodelay 则严格匀速放行,排队的要等
面试怎么答limit_req 限速率,是漏桶模型,rate 控制平均速率、burst 控制允许的突发排队数,加 nodelay 让排队的立刻处理否则会严格匀速放行。limit_conn 限单 IP 的并发连接数,用来防大文件下载占满连接。多机部署时单机限流会失效,要在入口统一限或用 Redis。
13

SPA 的 try_files

刷新 404 的标准解法
单页应用的路由是前端管的,服务器上不存在 /order/123 这个文件。所以要把"找不到的路径"统统交给 index.html——这正是 try_files 做的事。
try_files 的求值顺序try_files $uri $uri/ /index.html; 1. 找 root 下是否有这个文件 -> 有就返回 2. 加上斜杠看是否是目录 -> 是目录就找 index 3. 都没有,内部转发到 /index.html(状态码 200,不是 302)
面试怎么答SPA 的路由在前端,服务器上没有对应文件,所以刷新会 404。解决方法是 location / 里用 try_files 先找文件、再找目录、最后回落到 index.html。注意静态资源目录要单独配 try_files $uri =404,否则资源 404 也会返回 HTML,报错信息会变得很难懂。
14

502 / 504 怎么查★

这两个码指向完全不同的原因
502 Bad Gateway 是上游返回了非法响应或连接被拒(进程挂了、端口不通、协议不对);504 Gateway Timeout 是上游没在规定时间内响应(慢查询、死锁、超时时间设小了)。先分清楚是哪一个,排查方向完全不同。

502 的方向

  • 后端进程是否活着(端口有无监听)
  • proxy_pass 的地址端口对不对
  • 后端是不是崩了、OOM 被杀
  • 协议不匹配(HTTP 打到 HTTPS 端口)
  • 响应头过大(buffer 不够)

504 的方向

  • 后端接口本身慢(慢 SQL、外部依赖)
  • proxy_read_timeout 是否太小
  • 负载高、线程池被占满
  • 数据库锁等待
定位命令看错误日志 tail -f /var/log/nginx/error.log "connect() failed (111: Connection refused)" -> 后端没起 "upstream timed out" -> 504,后端慢 "upstream prematurely closed" -> 后端主动断开 查端口 ss -lntp | grep 8080 直连测试 curl -v http://10.0.0.11:8080/health 看进程 ps aux | grep java / systemctl status app
面试怎么答502 是上游不可达或返回了非法响应,先查后端进程是否活着、端口通不通、协议对不对;504 是上游太慢,先看后端接口耗时和超时配置。最快的一步是看 Nginx 的 error.log,里面会直接写 connect failed 还是 upstream timed out,据此定性再往下查。
15

速答清单

十句话收口
01Nginx 为什么高并发?
多进程加每进程单线程事件循环,用 epoll 做非阻塞 IO。等待数据的时间被复用来处理其他连接,所以少量 worker 能扛几万连接。
02reload 为什么不断服务?
master 拉起新 worker 接新连接,旧 worker 处理完在途请求后退出。多 worker 共享端口靠 SO_REUSEPORT。
03location 的匹配顺序?
精确 = 优先,然后是最长前缀带 ^~(命中不查正则),再按顺序试正则,最后用最长普通前缀。
04^~ 有什么用?
前缀命中后不再尝试正则匹配,防止静态资源被后面的正则 location 抢走。
05proxy_pass 带不带斜杠的区别?
带 URI(含单独斜杠)会替换掉 location 的前缀,不带则原样转发完整路径。两边斜杠要配对,否则拼出错误路径。
06add_header 的坑是什么?
子级只要有自己的 add_header,父级的全部失效,是覆盖不是合并。另外不加 always,错误响应不会带这些头。
07带 hash 的静态资源怎么配缓存?
expires 1y 加强缓存加 immutable。入口 index.html 必须 no-cache 走协商缓存,否则发版不生效。
08SPA 刷新 404 怎么解决?
location / 里用 try_files $uri $uri/ /index.html。静态资源目录单独配 try_files $uri =404。
09502 和 504 的本质区别?
502 是上游不可达或响应非法(进程挂了、端口不通、header 过大);504 是上游超时(接口慢、负载高)。
10limit_req 的 burst 和 nodelay?
burst 是允许的突发排队数,nodelay 让排队的立刻处理只限平均速率;不加 nodelay 会严格匀速放行,排队的要等。