LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

写了几年Nginx配置,这些错误我见过N多次了

admin
2026年3月27日 15:57 本文热度 55

本文是「Nginx 避坑」系列第一篇,本系列共四篇

后续篇目:反向代理与负载均衡 → HTTPS、安全与跨域 → 进阶与生产实践


线上出了问题,翻了两小时日志,最后发现是 worker_processes 1 这一行。

这些 Nginx 反向代理的坑,我替你踩完了(反向代理与负载均衡)

https://oa22.cn/bbs.asp?id=38197

你的Nginx正在裸奔:配了HTTPS却从没真正安全过

https://oa22.cn/bbs.asp?id=38196

写了几年Nginx配置,这些错误我见过N多次了

https://oa22.cn/bbs.asp?id=38195

同事改了一行Nginx配置,大促当天系统崩—这5个教训,每一个都是真金白银换来的

https://oa22.cn/bbs.asp?id=38194


排查一圈下来,才意识到——不是业务代码的锅,不是数据库的锅,是Nginx配置从上线第一天就是错的。一直没人注意,直到流量上来了才暴露。

这篇不只列坑,更重要的是把成熟团队真正在用的做法一起写出来。每个问题都给出:为什么这么配、生产里怎么落地、哪些细节容易再次踩坑。对照着检查一遍自己的配置,大概率能发现几个隐患。


先说说为什么基础坑最难发现

进阶的坑,比如 WebSocket 代理断连、upstream keepalive 没配——这些一出问题,现象很明显,很快能定位方向。

基础配置的坑不一样。 它们平时完全没有症状,服务运行一切正常,直到某个临界点:流量突然上来、换了服务器、做了迁移——问题才爆出来。那个时候你往往先去查应用层,查数据库,绕了一大圈,才想到可能是Nginx本身。

所以把基础打扎实,比什么都值。


第一块:基础配置里的坑与最佳实践

坑1:worker_processes 写死了数字

这是见得最多的一个。很多配置文件里直接写着:

worker_processes 1;

你的服务器是8核16核,这一行相当于主动放弃了大半算力。CPU跑满,但大部分核都是闲着的。

✅ 最佳实践:用 auto,让Nginx自己决定

worker_processes auto;

auto 读取 /proc/cpuinfo,有几个核启几个worker,完全不用手动维护,换机器了也不需要改配置。

⚠️ 容器环境的额外处理

Docker 用 --cpus=2 限制资源时,auto 读到的是宿主机核心数,可能一下子启几十个worker出来,反而互相竞争。

成熟的做法是在启动脚本里动态计算:

#!/bin/sh
# entrypoint.sh
CORES=$(nproc)
sed -i "s/worker_processes auto/worker_processes $CORES/" /etc/nginx/nginx.conf
exec nginx -g "daemon off;"

用 exec 启动 Nginx 还有一个额外好处:Nginx 直接成为 PID 1,容器收到 SIGTERM 时可以正确优雅退出,不会强制中断在途请求。


坑2:只改了Nginx,忘了改系统限制

高并发时突然出现:

24: Too many open files

翻Nginx配置,worker_connections 已经设到65535了,怎么还会不够?

因为系统层面的限制没动。Nginx的配置和操作系统的 ulimit 是两回事,两边都要改。

✅ 最佳实践:三个地方一起改

第一处,nginx.conf:

worker_rlimit_nofile 65535;

events {
    worker_connections 65535;
    use epoll;        # Linux 下显式指定 epoll,性能最好
    multi_accept on;  # 一次性接受所有新连接,减少事件循环次数
}

第二处,系统限制(/etc/security/limits.conf):

nginx soft nofile 65535
nginx hard nofile 65535

第三处,如果用 systemd 管 Nginx(推荐):

[Service]
LimitNOFILE=65535

改完记得 systemctl daemon-reload && systemctl restart nginx,limits.conf 的修改需要重启才生效。

容量规划公式备忘:

理论最大并发 = worker_processes × worker_connections
反向代理实际并发 ≈ 上面的结果 ÷ 2

反向代理场景每个客户端连接还要占一个 Nginx→Backend 的连接,文件描述符消耗翻倍,规划时要留出足够余量。


坑3:include 文件顺序没管好

配置写了,reload 也成功了,但就是不生效,或者请求走错了虚拟主机。

根因往往是 default_server 冲突——/etc/nginx/conf.d/ 下多个文件都定义了 default_server,哪个文件名字母顺序靠前,哪个生效,行为完全不可预期。

✅ 最佳实践:用文件名前缀控制加载顺序

/etc/nginx/conf.d/
├── 00-default.conf      # 默认 server,永远最先加载
├── 10-api-service.conf
├── 20-static-site.conf
└── 30-admin.conf

把 default_server 单独放 00-default.conf,业务配置从 10- 开始。这个约定简单,多人协作时也不容易出问题。建议把这个命名规则写进团队 Wiki,作为约定俗成的工程规范。


坑4:reload 前从来不检查语法

nginx -s reload 失败后,老 worker 进程继续在跑,新配置没有生效。你以为改好了,其实根本没加载,然后开始排查业务代码……

✅ 最佳实践:检查+reload 写成一条命令

nginx -t && nginx -s reload

&& 保证语法错误时直接终止,不执行 reload。

更进一步,成熟团队会把 Nginx 配置纳入 Git 管理,每次变更走 PR + 自动化检查流水线:

# CI 流水线里的检查步骤示例
docker run --rm -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf nginx nginx -t

任何人提交的配置变更,在合并之前就能发现语法错误,不再依赖个人操作习惯。

另外,想看当前 Nginx 实际加载了哪些配置(包含所有 include 文件),用:

nginx -T   # 大写 T,输出完整配置,排查"改了但没生效"时非常好用

坑5:pid 文件权限

用非 root 用户启动 Nginx,经常遇到:

nginx: [erroropen() "/var/run/nginx.pid" failed (13: Permission denied)

✅ 最佳实践:用 systemd 管理,不手动指定 pid 路径

交给 systemd 处理 pid 文件,不要在 nginx.conf 里自己指定,省去一类权限问题:

systemctl start nginx
systemctl enable nginx   # 设置开机自启
systemctl status nginx   # 查看运行状态

如果实在需要非 root 启动且不用 systemd,临时方案是改到有写权限的路径:

pid /var/run/nginx/nginx.pid;  # 提前创建目录并授权给 nginx 用户

第二块:性能调优,几个被误解的配置

误区1:gzip 对所有类型都开

jpg、png、gif 本身就是压缩格式,再压一遍几乎没效果,只是白白消耗 CPU。

另一个高频错误是压缩级别设太高:

gzip_comp_level 9;  # 不推荐

级别 9 和级别 6 的压缩率差距不超过5%,但 CPU 消耗差距很大。9 是最高压缩率,不是最佳实践。

✅ 最佳实践:只压文本,级别定6

gzip on;
gzip_vary on;            # 告知代理服务器分别缓存压缩和非压缩版本
gzip_comp_level 6;       # 压缩率与CPU消耗的平衡点,行业公认
gzip_min_length 1024;    # 1KB以下不压,压缩开销大于传输收益
gzip_proxied any;
gzip_types
    text/plain
    text/css
    text/xml
    application/json
    application/javascript
    application/xml+rss
    image/svg+xml;
# ⛔ 绝对不要加:image/jpeg image/png image/gif video/*

gzip_vary on 很多人漏掉这一行。它的作用是在响应头里加 Vary: Accept-Encoding,告诉 CDN 和代理分别缓存压缩版和非压缩版,防止把压缩内容发给不支持 gzip 的客户端。


误区2:sendfile / tcp_nopush / tcp_nodelay 三选一

这三个参数看着像是互相矛盾:

sendfile    on;   # 零拷贝传文件
tcp_nopush  on;   # 等数据凑够了再发
tcp_nodelay on;   # 立即发,禁用 Nagle 算法

tcp_nopush(等着发)和 tcp_nodelay(立即发)看起来逻辑相反,但实际上不冲突

  • tcp_nopush
     只在 sendfile 传文件阶段生效,把文件数据积累成大包再发,减少 TCP 包数量
  • tcp_nodelay
     在 keepalive 连接复用阶段生效,后续请求立即发送,降低延迟

✅ 最佳实践:三个一起开

sendfile    on;
tcp_nopush  on;
tcp_nodelay on;

Nginx 在不同处理阶段会自动切换策略,三个同时开是所有主流 Nginx 配置模板的标准写法。


误区3:keepalive_timeout 没细想

太长:大量空闲连接占着文件描述符,流量高峰容易打满。太短:客户端频繁握手,延迟和 CPU 消耗都上去了。

✅ 最佳实践:

keepalive_timeout 65;        # 65秒,适合大多数 Web 服务
keepalive_requests 1000;     # 单个连接最多复用1000次再断开重建

keepalive_requests 这个参数很多人不知道,Nginx 老版本默认值只有 100,单个连接处理100个请求就关闭重建,高并发下会产生额外的握手开销。改成1000是合理的上限,同时也防止连接因为长期复用而累积内存不释放。


误区4:proxy_buffer 完全没配

后端响应慢时,没有 buffer 的 Nginx 会直接透传数据,Nginx 和后端之间的连接就一直挂着不释放,并发数很快跑满。

✅ 最佳实践:按场景分级配置

通用场景(API服务、普通页面):

proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
proxy_temp_file_write_size 64k;

大文件下载场景(报表导出、文件服务):

proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 256k;

两套配置分别放在不同的 location 块里,按业务类型精细化控制,不要对所有接口一刀切。


误区5:open_file_cache 一行都没配

每次静态文件请求,Nginx 都要做 open() 和 stat() 系统调用。几百 QPS 时感觉不出来,上了几千 QPS 这就是可量化的瓶颈。

✅ 最佳实践:四行配置,零成本提升

open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;  # 连"文件不存在"的结果也缓存,减少无效 stat

open_file_cache_errors on 这一行经常被漏掉。不缓存 404 的话,每次访问不存在的资源都要做系统调用,爬虫或者恶意请求很容易在这里打出瓶颈。


第三块:location 匹配,最容易自我怀疑的地方

优先级搞错了

Nginx location 匹配优先级,从高到低:

  1. =
     精确匹配(最高,命中即停止)
  2. ^~
     前缀匹配,命中后不再尝试正则
  3. ~
     / ~* 正则匹配(区分/不区分大小写)
  4. 无修饰符的普通前缀匹配(最低)

最经典的翻车场景:

# ❌ 陷阱配置
location /static/ {
    root /var/www;
}

location ~* \.(jpg|png|gif)$ {
    expires 30d;
    root /var/www;
}

GET /static/logo.png 会走哪个?正则那个。普通前缀匹配没有 ^~,挡不住正则继续尝试。

✅ 最佳实践:静态资源目录一律加 ^~,顺手把缓存策略也加上

location ^~ /static/ {
    root /var/www;
    expires 30d;
    add_header Cache-Control "public, immutable";
}

location ~* \.(jpg|png|gif|ico|woff2)$ {
    expires 365d;
    add_header Cache-Control "public, immutable";
}

^~ 加上之后,/static/ 下的请求不会再被正则抢走。同时把缓存策略一并配好,是静态资源 location 的完整最佳实践。


try_files 有两个高频坑

SPA 应用几乎人人都写这行:

try_files $uri $uri/ /index.html;

坑一:最后一个参数写 /index.html,Nginx 会做内部重定向,重新走一遍 location 匹配。如果匹配到的 location 本身还有 try_files,就会循环。

✅ 正确做法:用命名 location 完全隔离 fallback 逻辑:

location / {
    try_files $uri $uri@spa_fallback;
}

location @spa_fallback {
    root /var/www/app;
    try_files /index.html =404;
}

坑二try_files 最后一个参数不能是完整 URL:

# ❌ 错的
location / {
    try_files $uri $uri/ http://127.0.0.1:8080;
}

✅ 正确写法:配合命名 location 做代理

location / {
    try_files $uri @backend;
}

location @backend {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

if 指令——能不用就不用

Nginx 官方有篇文章叫 "If is Evil",这不是夸张。

location 块里用 if + proxy_passif 块的指令会覆盖外层的,但继承关系又不完整,产生极难排查的 bug:

# ❌ 危险写法,行为不可预测
location / {
    if ($request_method = POST) {
        proxy_pass http://backend_post;
    }
    proxy_pass http://backend_get;
}

✅ 最佳实践:用 map 替代所有条件分支

# 在 http 块里定义
map $request_method $backend {
    POST    http://backend_post;
    default http://backend_get;
}

location / {
    proxy_pass $backend;
}

map 在请求进来之前就计算好变量值,没有 if 的继承问题,逻辑清晰,性能也更好。

if 的唯一安全使用场景:只配合 return 或 rewrite 做跳转:

# ✅ 这样用是安全的
if ($host != "www.example.com") {
    return 301 https://www.example.com$request_uri;
}

alias 末尾斜杠别漏

# root:location 路径追加在 root 后面
location /static/ {
    root /var/www;
    # /static/a.js → /var/www/static/a.js ✅
}

# alias:替换掉 location 匹配的部分
location /static/ {
    alias /var/www/assets/;  # ← 末尾斜杠不能少,和 location 末尾对称
    # /static/a.js → /var/www/assets/a.js ✅
}

alias 末尾少一个斜杠,路径会拼成 /var/www/assetsa.js,4XX 报错,然后你开始怀疑自己文件放错目录了……

记忆规律location 末尾有斜杠,alias 末尾也必须有斜杠,对称就对了。


最后两件事

一、一份可以直接用的生产基础配置

把本篇所有最佳实践整合到一起,注释说明每个关键配置的用意:

user nginx;
worker_processes auto;          # 自动匹配 CPU 核心数
worker_rlimit_nofile 65535;     # 与系统 ulimit 保持一致

error_log /var/log/nginx/error.log warn;  # 生产用 warn,不要用 debug
pid /var/run/nginx.pid;

events {
    worker_connections 65535;
    use epoll;        # Linux 最优事件模型
    multi_accept on;  # 一次性接受所有新连接
}

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" rt=$request_time '
                    'urt=$upstream_response_time';
    access_log /var/log/nginx/access.log main;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    keepalive_timeout  65;
    keepalive_requests 1000;

    # 请求大小与超时,防慢速攻击
    client_max_body_size      10m;
    client_body_buffer_size   128k;
    client_header_buffer_size 4k;
    large_client_header_buffers 4 32k;
    client_body_timeout   30s;
    client_header_timeout 30s;
    send_timeout          30s;

    # gzip:只压文本,级别定6
    gzip on;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json
               application/javascript text/xml
               application/xml image/svg+xml;

    # 静态文件描述符缓存,高 QPS 场景效果明显
    open_file_cache max=1000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    server_tokens off;  # 隐藏版本号,安全加固基础项

    include /etc/nginx/conf.d/*.conf;
}

二、上线前自查清单

新项目上线前或者旧系统排查时,对照这份清单逐项确认:

基础配置
□ worker_processes 是否用了 auto(容器环境需额外处理)
□ worker_rlimit_nofile 与 worker_connections 是否一致
□ 系统 ulimit / systemd LimitNOFILE 是否同步修改
写了几年Nginx配置,这些错误我见过n多次了
□ conf.d 文件命名是否有前缀控制加载顺序

性能配置
□ gzip 是否排除了图片/视频类型
□ gzip_comp_level 是否是 6,gzip_vary 是否开启
□ sendfile / tcp_nopush / tcp_nodelay 三个是否都开启
□ keepalive_requests 是否显式配置
□ proxy_buffer 是否按业务场景分级配置
□ open_file_cache 及 open_file_cache_errors 是否开启

location 配置
□ 静态资源目录是否有 ^~ 修饰符
□ 是否有在 location 里用 if + proxy_pass 的危险写法
□ try_files 最后一个参数是否是路径/命名location(不是完整URL)
□ alias 末尾斜杠是否和 location 末尾对称

基础篇到这里。下一篇聊反向代理和负载均衡——那里有一个把无数人坑过的斜杠问题,以及 upstream keepalive 没配导致大促雪崩的真实事故复盘。

如果这篇对你有帮助,转发给还在踩这些坑的同事吧。


阅读原文:原文链接


该文章在 2026/3/27 16:37:23 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved