从问题出发,讲清 Nginx"为什么需要、怎么工作、怎么配置、怎么调优、有什么坑"。不写面面俱到的手册,只讲原理和实战中真正重要的东西。
📌 学习路线建议
这篇笔记内容较多,不要试图一次全部消化。建议分四步走:
阶段 读哪些章节 目标 第一步:建立直觉 第 1~3 节 理解"为什么需要 Nginx"、事件驱动架构、配置文件层级 第二步:掌握核心 第 4~7 节 搞懂 location 匹配规则、反向代理、负载均衡、HTTPS 第三步:生产加固 第 8~11 节 缓存、压缩、限流安全、WebSocket 代理 第四步:实战调优 第 12~15 节 性能调优、常见架构模式、排错速查、最佳实践 每步读完后再进入下一步。第三步建议本地起一个 Nginx,把配置改一改、reload 一下——看十遍不如跑一遍。
这部分讲"为什么"和"怎么想"。读完这部分你能搞懂 Nginx 的全貌:为什么需要它、它内部怎么工作、配置文件怎么组织。
假设你写了一个 Node.js / Java / Python Web 服务,跑在 3000 端口。直接让用户访问行不行?行,但有很多问题:
:3000,他们只认 80/443在用户和后端应用之间放一个 Nginx:
server_name 区分多个站点——虚拟主机📌 核心思想:把"通用的网络层杂活"(HTTPS、缓存、限流、负载均衡、静态文件)从应用里剥离,交给专门的 Nginx 做。应用只管业务逻辑。这是关注点分离。
| 价值 | 没用 Nginx | 用了 Nginx |
|---|---|---|
| 统一入口 | 用户要记端口号,应用直接暴露 | Nginx 监听 80/443,隐藏后端 |
| 动静分离 | 应用处理所有请求,包括静态文件 | Nginx 直接返回静态文件,动态请求才转发 |
| 负载均衡 | 一台后端扛不住就没办法 | Nginx 分发到多台后端,自动故障转移 |
| SSL 终结 | 每个应用都配证书 | Nginx 统一加解密,后端走明文 HTTP |
| 虚拟主机 | 一台机器一个网站 | 一个 Nginx 承载多个域名 |
| 安全防护 | 应用直接面对攻击 | Nginx 做限流、IP 黑白名单、隐藏后端信息 |
Nginx 之所以快,核心在于它不像传统服务器(如 Apache)那样"一个连接一个线程"。Nginx 用的是事件驱动 + 非阻塞 I/O:
📌 打个比方:
auto(等于 CPU 核心数),每个 worker 独立处理数千并发连接:::tip 📌 为什么 reload 不中断服务?Master 收到 reload 信号后:① 启动新 worker(用新配置)→ ② 给老 worker 发"优雅关闭"信号 → ③ 老 worker 处理完手上的请求后退出。整个过程中新连接由新 worker 处理,老连接由老 worker 处理完——用户完全无感。
| 特性 | Nginx | Apache |
|---|---|---|
| 并发模型 | 事件驱动,一个 worker 管数千连接 | 每连接一线程/进程(prefork)或事件(worker/event MPM) |
| 内存占用 | 极低(几 MB / worker) | 高(每连接独立内存) |
| 静态文件 | 极快 | 快 |
| 反向代理 | 核心能力,功能丰富 | 需要 mod_proxy,不如 Nginx 灵活 |
| 动态内容 | 必须反向代理给后端 | 可直接嵌入 PHP/Python(mod_php) |
| .htaccess | 不支持(配置只在主文件改) | 支持,用户可自行配置 |
| 配置复杂度 | 简洁,声明式 | 较复杂,模块多 |
📌 一句话总结:Nginx 是"反向代理 + 静态文件服务器"的王者,Apache 是"全能 Web 服务器"的老牌。现代架构几乎都是 Nginx 做前端代理 + 后端应用跑业务。两者不是对立的,可以共存。
Nginx 配置是层层嵌套的块(block):
层级关系:
📌 像三层抽屉:http 是整个柜子的通用规则,server 是一个抽屉(一个网站),location 是抽屉里的分隔格(不同路径分开处理)。请求来了,先找哪个 server(按域名+端口),再找哪个 location(按路径)。
📌 指令继承:子块自动继承父块的指令。在 http 里设了 gzip on,所有 server 和 location 都生效。在某个 location 里单独设 gzip off,只覆盖那一个。
Ubuntu/Debian:
CentOS/RHEL:
📌 Ubuntu 的 available/enabled 设计:sites-available 是"所有写好的站点配置仓库",sites-enabled 放软链接,只链接当前要启用的。想临时停用一个站点,删软链接即可,配置文件还留着。
CentOS 没有这个设计,直接放 conf.d/ 就启用,删文件就停用。两种方式各有优劣,Ubuntu 的方式更适合多站点管理。
📌 sendfile on 为什么重要?传统读文件再发送要经过 4 次内核↔用户空间拷贝。sendfile 让内核直接把文件数据从磁盘缓存送到网卡,跳过用户空间——零拷贝,性能提升显著。这是 Nginx 静态文件飞快的核心原因之一。
| 修饰符 | 含义 | 优先级 |
|---|---|---|
= |
精确匹配,URI 完全相等 | 最高 |
^~ |
前缀匹配,匹配后不再检查正则 | 次高 |
~ / ~* |
正则匹配,按配置文件顺序,先匹配到的生效 | 中 |
| 无 | 普通前缀匹配,最长匹配生效 | 最低 |
📌 最容易踩的坑:你以为 location /api/ 会匹配 /api/users,但如果有个 location ~ ^/api/ 写在前面,正则会优先匹配。记住优先级:精确 > ^~前缀 > 正则 > 普通前缀。
📌 实际建议:
^~(避免被正则抢走)^~~* \.(jpg|png|css|js)$)
::::::tip
📌 区别:root 是拼接(location 路径保留),alias 是替换(location 路径丢弃)。
root 适合 location 路径和文件系统路径一致的情况alias 适合 location 路径和文件系统路径不一致的情况alias 目录路径末尾必须加 /,root 不需要📌 踩坑警告:alias 配正则 location 时要小心路径替换,容易 404。不确定就用 root。
📌 为什么要 proxy_set_header?请求经 Nginx 转发后,后端看到的"客户端"是 Nginx(127.0.0.1),看不到真实用户。这几行把原始信息塞进请求头传给后端,否则:
📌 X-Forwarded-For 是什么?它记录请求经过的 IP 链:客户端IP, 代理1IP, 代理2IP。$proxy_add_x_forwarded_for 会自动在已有值后面追加 Nginx 的上游 IP。后端取第一个值就是真实客户端 IP。
📌 一句话记忆:proxy_pass 带 / = 替换前缀(像 alias),不带 / = 保留完整路径。不确定就别带 URI,让路径原样传递最安全。
📌 什么时候要调 proxy_read_timeout?后端有耗时操作(如大文件生成、AI 推理、报表导出)时,默认 60s 可能不够,后端还没返回 Nginx 就超断了,用户看到 504 Gateway Timeout。调大到 300s 甚至更长。但别盲目调大——如果是后端卡死而非正常处理,调大只是延迟报错。
| 策略 | 适用场景 | 优缺点 |
|---|---|---|
| 轮询 | 后端机器配置相同 | 简单,但不考虑实际负载 |
| 加权轮询 | 后端机器配置不同 | 按能力分配,但也不考虑实时负载 |
| least_conn | 请求处理时间差异大 | 更均衡,但要追踪连接数有开销 |
| ip_hash | 需要会话保持(无 Session 共享时) | 简单,但 IP 变了或机器增减会失效 |
| hash | 需要按特定 key 固定分发 | 灵活,一致性哈希减少增减节点的影响 |
| random | 极简场景 | 最轻量,但可能不均衡 |
📌 ip_hash 的陷阱:
📌 生产建议:后端机器配置相同用 least_conn,配置不同用加权轮询,有 Session 共享方案就别用 ip_hash。
max_fails=3:30 秒内失败 3 次,标记为不可用fail_timeout=30s:失败后 30 秒内不再发给它backup:备用服务器,只有其他全挂了才会启用down:永久标记为不可用(维护时用)📌 Nginx 开源版的健康检查是被动的——只有在实际转发请求失败时才标记不可用。没有主动探测。Nginx Plus(商业版)才有主动健康检查。
开源版主动健康检查方案:用 nginx_upstream_check_module 模块,或用外部脚本 + proxy_next_upstream 配合。
📌 proxy_next_upstream:请求发给一台后端失败后,是否自动重试下一台:
📌 什么是"SSL 终结"?HTTPS 加解密很耗 CPU。让 Nginx 统一处理加解密(在 Nginx 这里"终结"加密),它和后端之间走普通 HTTP(内网安全)。后端应用完全不用管证书和加密,省心又省 CPU。
📌 证书文件里有什么?cert.pem 应该包含你的证书 + 中间证书链(按顺序拼接)。如果只放了你的证书没放中间证书,部分浏览器会报"证书不受信任"。Let's Encrypt 的 fullchain.pem 已经帮你拼好了。
📌 certbot 怎么证明"域名是你的"?它用 ACME 协议做验证:certbot 在你服务器上放一个特定文件,Let's Encrypt 通过 http://你的域名/.well-known/... 来访问——能访问到,就证明你控制这个域名。所以申请前域名必须先解析到本机、80 端口要能被外网访问。
📌 为什么有效期只有 90 天?短有效期是安全设计:万一私钥泄露,危害窗口短。certbot 装好后会自动加一个 systemd timer 定期续期,全程无人值守。certbot --nginx 还会自动帮你改好 nginx 配置(加 ssl_certificate、配 80→443 跳转)。
一个 Nginx 承载多个域名,每个域名有自己的证书——靠 SNI(Server Name Indication)实现:
📌 SNI 原理:TLS 握手时客户端在 ClientHello 里带上域名,Nginx 据此选择对应的证书。现代浏览器都支持 SNI。一个 IP 就能承载多个 HTTPS 站点。
📌 immutable 是什么?告诉浏览器这个文件永远不会变,连验证请求(304)都不用发。配合文件名加 hash(如 app.a1b2c3.js)使用——文件内容变了就换 hash,URL 变了浏览器自然重新下载。这是前端构建工具(Webpack/Vite)的标准做法。
$upstream_cache_status 的值:
| 值 | 含义 |
|---|---|
MISS |
未命中,请求发给了后端 |
HIT |
命中缓存,直接返回 |
EXPIRED |
缓存已过期,发给了后端 |
STALE |
后端不可用,返回了旧缓存 |
UPDATING |
正在更新缓存,返回了旧缓存 |
📌 什么时候用代理缓存?
📌 proxy_cache_use_stale 是生产利器:后端挂了时,Nginx 返回过期但还能用的旧缓存,用户感觉不到后端故障。这是缓存作为"降级手段"的典型用法。
📌 gzip_comp_level 选几?
1:压缩最快,压缩率最低6:推荐,压缩率和速度的平衡点9:压缩率最高,但 CPU 开销大,对高并发不划算📌 哪些文件不要压缩?已经压缩过的格式(jpg、png、mp4、zip、gzip)——再压缩几乎不会变小,反而浪费 CPU。只压缩文本类文件。
Brotli 比 Gzip 压缩率高 15-25%,但只对 HTTPS 生效(浏览器只在 HTTPS 下接受 Brotli)。现代浏览器都支持。
📌 rate=10r/s 和 burst=20 怎么理解?
rate=10r/s:平均每秒最多 10 个请求burst=20:允许瞬间来 30 个请求(10 + 20 burst),超出的直接返回 503nodelay:burst 里的请求立即处理不排队(不加 nodelay 的话,突发请求会被延迟处理)📌 限流返回什么状态码?默认 503 Service Temporarily Unavailable。可以自定义:
WebSocket 连接建立时发一个 HTTP Upgrade 请求,之后变成持久的双向连接。Nginx 默认会在一段时间没有数据后断开连接。
📌 Connection "upgrade" 为什么是固定字符串?因为 $http_connection 的值通常是 keep-alive,不会自动变成 upgrade。必须手动设为 "upgrade" 才能触发协议升级。
📌 proxy_read_timeout 86400s 为什么重要?WebSocket 连接建立后可能长时间没有数据传输(如聊天室没人说话)。默认 60s 超时会让 Nginx 断开连接,用户频繁掉线重连。调到 24 小时基本解决问题。如果真有长时间空闲的场景,应用层应该发心跳包保活。
📌 map 的作用:当 $http_upgrade 有值(是 WebSocket 请求)时,$connection_upgrade = upgrade;没有值(普通 HTTP)时 = close。这样一个 location 同时处理 HTTP 和 WebSocket。
📌 worker_connections 设多少?每个连接占约 2KB 内存。10240 个连接 × 4 个 worker = 40960 并发,内存约 80MB——对现代服务器微不足道。但要注意系统级 ulimit -n 也要调大(否则 Nginx 受限于系统 fd 限制)。
📌 最大并发 = worker_processes × worker_connections。4 worker × 10240 = 40960 并发连接。但反向代理模式下每个客户端连接还要对应一个后端连接,所以实际并发约为一半。
📌 keepalive upstream 的坑:要同时设 proxy_http_version 1.1 和 proxy_set_header Connection "",否则 Nginx 到后端还是短连接。这三件套缺一不可。
📌 tcp_nopush 和 tcp_nodelay 看似矛盾?不矛盾:
tcp_nopush:大文件传输时数据包攒够再发,减少网络包数量tcp_nodelay:小数据(如 HTTP 头)立即发送,降低延迟:::tip
📌 try_files $uri $uri/ /index.html 怎么工作?
$uri 对应的文件(如 /assets/app.js)——找到直接返回$uri/ 对应的目录——找到返回目录下 index/index.html——让前端路由接管这是 SPA 的标准配置。用户访问 /users/123,文件系统里没有这个文件,但 try_files 兜底返回 index.html,前端 JS 路由解析 /users/123 渲染对应页面。
| 症状 | 可能原因 | 解决 |
|---|---|---|
nginx: [emerg] bind() to 0.0.0.0:80 failed |
端口被占用 | ss -tlnp | grep :80 找到占用进程,停掉或改端口 |
502 Bad Gateway |
后端没启动或 Nginx 连不上后端 | 检查后端进程、端口、防火墙 |
504 Gateway Timeout |
后端响应太慢 | 调大 proxy_read_timeout,或优化后端 |
403 Forbidden |
文件权限不对或 index 文件不存在 |
检查目录权限、index 指令 |
404 Not Found |
root/alias 路径不对 |
检查文件路径,区分 root 和 alias |
413 Request Entity Too Large |
上传文件超过限制 | client_max_body_size 50m; |
too many open files |
文件描述符不够 | 调大 worker_rlimit_nofile 和系统 ulimit -n |
| 配置 reload 后不生效 | 可能是语法错误没 reload 成功 | 先 nginx -t 检查,再看 error.log |
| WebSocket 频繁断开 | proxy_read_timeout 太短 |
调大到 86400s,或应用层加心跳 |
| 后端日志里 IP 都是 127.0.0.1 | 没传 X-Real-IP / X-Forwarded-For |
加上 proxy_set_header 传递真实 IP |
| 静态文件 404 但文件存在 | root vs alias 用错 |
仔细检查路径拼接逻辑 |
📌 为什么改配置后先 nginx -t 再 reload?这是血泪经验:配置写错时直接 reload,Nginx 可能加载失败导致整个网站挂掉。nginx -t 先做语法检查,通过了再 reload 才安全。
📌 nginx -T 是排查神器:它会打印所有 include 进来的配置文件内容,拼成完整的生效配置。当你不确定某个配置到底在哪个文件里、是否生效时,nginx -T 一目了然。
nginx.conf 只放全局设置,站点配置放 sites-available/ 或 conf.d/sites-enabled/ 软链接控制启用/停用(Ubuntu 方式)include 引入(如 SSL 参数、代理头)nginx -t 再 reloadworker_processes auto + worker_connections 调大sendfile on + tcp_nopush on + tcp_nodelay onexpires 长期缓存keepalive + proxy_http_version 1.1 + Connection ""ulimit -n 和 sysctl 参数配合server_tokens off 隐藏版本号limit_req 限流client_max_body_size 限制上传大小proxy_next_upstream 配置故障自动重试proxy_cache_use_stale 后端故障时返回旧缓存降级backup 服务器做兜底nginx -t、systemctl status、日志)| 变量 | 含义 |
|---|---|
$host |
请求的 Host 头(域名) |
$remote_addr |
客户端 IP |
$request_uri |
完整请求 URI(含参数) |
$uri |
请求 URI(不含参数) |
$args |
查询参数 |
$scheme |
协议(http 或 https) |
$request_method |
请求方法(GET/POST 等) |
$request_time |
总请求处理时间 |
$upstream_response_time |
后端响应时间 |
$upstream_addr |
后端服务器地址 |
$upstream_cache_status |
缓存命中状态 |
$http_user_agent |
客户端 User-Agent |
$http_upgrade |
WebSocket 升级头 |
$proxy_add_x_forwarded_for |
追加后的 X-Forwarded-For |
Nginx 不是一个"Web 服务器"那么简单。它在现代架构中扮演的是网络入口层的角色:
curl 验证。感受一下请求是怎么流转的现实节奏:Nginx 的基础配置一两天就能上手,但 location 匹配规则、proxy_pass 路径替换、性能调优这些细节,需要在实战中反复踩坑才能真正掌握。遇到问题先
nginx -t、再看error.log——这两步解决 90% 的问题。