> 技术文档 > Nginx系列之二:HTTP模块参数调优攻略

Nginx系列之二:HTTP模块参数调优攻略


大家好,我是IT孟德,You can call me Aman(阿瞒,阿弥陀佛的ē,Not阿门的ā),一个喜欢所有对象(热爱技术)的男人。我正在创作架构专栏,秉承ITer开源精神分享给志同道合(爱江山爱技术更爱美人)的朋友。专栏更新不求速度但求质量(曹大诗人传世作品必属精品,请脑补一下《短歌行》:对酒当歌,红颜几何?譬如媳妇,吾不嫌多...青青罗裙,一见动心,但为佳人,挂念至今...),通过朴实无华、通俗易懂的图文将十六载开发和架构实战经验娓娓道来,让读者茅塞顿开、相见恨晚...如有吹牛,不吝赐教。关注wx公众号:IT孟德,一起修炼吧! 

专栏文章推荐:

离开互联网职场,我将何去何从?

告别16年IT生涯:一场关于内耗、健康和出路的反思

软件界牛马:Nginx核心原理与性能优化全攻略

架构实战:2万字真实案例全方位解析高并发系统性能优化之道

架构师指南:像呵护感情一样构建无懈可击的系统稳定性防线

系统性能评估:如何定义并发数、响应时间和吞吐量

实战:混合云架构Nginx+Lua实现流量跨机房分发

架构图的魅力:如何用UML提升架构设计的质量

为什么要做架构设计?架构设计包含哪些内容?

架构师的职责是什么?程序员如何转型为架构师?

        

        上一篇文章《软件界牛马:Nginx核心原理与性能优化全攻略》介绍了Nginx的架构和功能,本文展开分析Nginx中最核心的能力--HTTP服务器。顾名思义,HTTP服务器就是一种遵循超文本传输协议(HTTP)规范,负责接收、处理客户端HTTP请求,并返回对应数据的网络服务程序。

1、HTTP请求生命周期


        Nginx处理一个HTTP请求并生成响应的全生命周期是一个高度模块化、事件驱动、非阻塞 I/O的复杂过程。从TCP连接建立,到HTTP请求解析、模块链处理(重写、访问控制、内容生成、过滤),再到响应发送与连接回收,每个阶段都由特定模块按顺序执行,通过非阻塞I/O和进程模型(多worker)实现高并发处理。以下是一个简单的index.html请求响应流程:

Step 1:TCP连接建立

        HTTP基于TCP协议,因此请求处理的前提是建立TCP连接。

  • Nginx 的master进程启动时会创建监听套接字(由listen指令配置端口);

  • worker进程通过I/O多路复用(如epoll)监听端口的连接事件(EPOLLIN);

  • 当新连接到达时,worker进程调用accept接收连接;

  • 为连接分配内存(ngx_connection_t结构体),记录连接状态(如客户端 IP、端口、套接字描述符);

  • 连接建立后立即注册读事件,但不阻塞等待数据,由事件循环驱动;

  • 如果server块配置了ssl,Nginx会在此阶段处理SSL/TLS 握手,SSL 握手发生在HTTP请求解析之前,是连接建立的一部分。

Step 2:HTTP请求接收与解析

        连接建立后,客户端发送HTTP请求,Nginx开始接收并解析请求行和请求头,填充到ngx_http_request_t结构体。

  • 当客户端发送数据后,触发读事件,Nginx从套接字缓冲区读取数据到请求缓冲区 ,可通过client_body_buffer_size配置请求体的缓冲区大小,若超过缓冲区则写入临时文件(由client_body_temp_path指定);

  • 若请求未读完(如数据分多次发送),会暂停处理,等待下次读事件触发;

  • Nginx不会等待整个请求体到达才开始处理,它只解析请求行(方法、URI、协议版本)和请求头部(Host、User-Agent等),并将其转换为结构化数据 ngx_http_request_t

  • 若解析行失败(如非法 URI),返回 400 错误;若请求头过大(超过large_client_header_buffers配置),返回 414 错误。

Step 3:HTTP请求处理

        解析完成后,Nginx将HTTP请求处理流程划分为11个有序的逻辑阶段,每个阶段可以有多个模块注册处理函数。处理流程中前一阶段的输出作为下一阶段的输入,但并非线性的,可能会因为 rewrite 或 try_files 导致流程回跳。这是Nginx HTTP处理逻辑最核心的部分,这种设计让Nginx既能保证高效处理,又能通过模块扩展灵活支持各类场景。

//ngx_http_core_module.htypedef enum { NGX_HTTP_POST_READ_PHASE = 0, NGX_HTTP_SERVER_REWRITE_PHASE, NGX_HTTP_FIND_CONFIG_PHASE, NGX_HTTP_REWRITE_PHASE, NGX_HTTP_POST_REWRITE_PHASE, NGX_HTTP_PREACCESS_PHASE, NGX_HTTP_ACCESS_PHASE, NGX_HTTP_POST_ACCESS_PHASE, NGX_HTTP_TRY_FILES_PHASE, NGX_HTTP_CONTENT_PHASE, NGX_HTTP_LOG_PHASE} ngx_http_phases;

处理阶段

主要作用

应用场景

是否支持模块注册

典型模块

Lua扩展点

POST_READ

读取请求头后立即处理,修改客户端信息

通过 X-Real-IP 头修改客户端 IP

ngx_http_realip_module

SERVER_REWRITE

执行server块级别的URI重写规则,修改请求路径或触发跳转

域名级URL重定向(如 HTTP→HTTPS 跳转)

ngx_http_rewrite_module

rewrite_by_lua*

FIND_CONFIG

根据当前URI匹配最合适的location配置块,为后续阶段提供配置上下文

基于URI前缀或正则匹配不同处理逻辑(如/api/走代理,/static/ 走静态文件)

/

REWRITE

执行location块内的 URI 重写规则,支持更细粒度的路径修改

动态路由(如 /user/123 → /user.php?id=123)

ngx_http_rewrite_module

rewrite_by_lua*

POST_REWRITE

检查重写循环(防止无限重写),若 URI 被修改则触发重新查找 location

防止重写死循环(如超过10次重写返回 500

/

PREACCESS

访问控制前的准备工作,如初始化连接数限制、请求速率限制的计数器

限制并发连接数(limit_conn)、限制请求频率(limit_req)

ngx_http_limit_conn_module、ngx_http_limit_req_module

ACCESS

核心访问控制逻辑,决定请求是否被允许继续处理(如 IP 黑白名单、用户认证)

基于 HTTP Basic Auth 的密码保护、内网 IP白名单

ngx_http_access_module、ngx_http_auth_basic_module

access_by_lua*

POST_ACCESS

处理 access 阶段后的内部跳转(如 error_page 跳转),清理临时资源

认证失败后跳转到登录页

/

TRY_FILES

按顺序检查文件是否存在,失败则跳转到备用URI

静态文件与动态路由共存(如 /page/1 → 先查 page/1.html,不存在则走index.php)

/

CONTENT

生成响应内容,如返回静态文件、转发请求到后端服务

反向代理到 Tomcat/Node.js 后端、静态资源托管

ngx_http_proxy_module、ngx_http_index_module、ngx_http_static_module

content_by_lua*

LOG

记录请求日志

流量分析日志记录、性能监控(如记录后端响应时间)

ngx_http_log_module

log_by_lua*

Step 4:响应过滤与发送

        内容生成后(CONTENT_PHASE之后或过程中),响应数据会经过输出过滤链处理(由多个filter模块修饰响应),然后将响应数据按状态行、 响应头 、响应体的顺序发送给客户端。一旦开始发送响应体,响应头即被锁定,无法再修改。

  • Header过滤链:Nginx调用 ngx_http_send_header() 函数,触发头部过滤链修改响应头(如添加X-Powered-By, Content-Type、Content-Encoding);

  • Body过滤链:响应体通过 ngx_http_output_filter() 函数处理,触发响应体过滤链修改响应体(如gzip压缩、sub_filter替换内容、chunked_filter分片传输);

  • 先发送状态行(如HTTP/1.1 200 OK)和响应头(如Content-LengthContent-Type),头部过滤链处理完成后,一次性发送;

  • 再发送响应体(静态文件内容、后端返回数据或过滤后的内容),通过非阻塞I/O逐步发送。若响应体过大,会分块发送(启用Transfer-Encoding: chunked,HTTP/1.1 默认支持)。发送方式:

  1. 零拷贝:使用sendfile系统调用,直接从文件描述符发送到socket。

  2. 向量化I/O:使用writev系统调用,合并多个缓冲区(如响应头+部分响应体)批量发送。

Step 5:连接回收与关闭

        响应发送完成后,Nginx 根据配置决定连接的生命周期。

  • 复用连接(Keep-Alive):若请求头含 Connection: keep-alive 且未超限,Nginx 不关闭 TCP 连接,而是将其放回 keepalive 连接池。Worker 进程继续监控该套接字的可读事件,等待客户端发起新的 HTTP 请求(复用此连接)。这避免了频繁的 TCP 握手开销。

  • 关闭连接:若超过keepalive_timeout未收到新请求,Nginx则释放为该请求分配的所有内存(ngx_http_request_t 结构、缓冲区、变量等)。、关闭与上游服务器的连接、清理临时文件、从事件循环中移除对该连接套接字的监控。

2、HTTP处理优化


        理解了Nginx HTTP的生命周期,有助于更精准地配置Nginx,充分利用Nginx中HTTP处理逻辑进行精细化优化。例如大家特别熟悉的location指令就隐藏着很多门道,FIND_CONFIG阶段支持多种location匹配类型,每种类型有不同的优先级。通过理解这些优先级规则,可以更好地配置Nginx的location块,实现更加精准高效的路由分发。

2.1、location规则优先级

  • 精确匹配(=):优先级最高,URI必须与location定义的路径完全一致(区分大小写),匹配成功后立即终止搜索,直接使用该配置。示例:
location = /login { #仅匹配/login,不匹配/login/ proxy_pass http://backend;}location = /robots.txt { # 只匹配 /robots.txt return 200 \"User-agent: *\\nDisallow: /private/\";}
  • 前缀匹配(^~):第二优先级,URI以指定字符串开头时匹配,匹配后不再检查后续的正则表达式,直接使用该配置。示例:
location ^~ /images/ { # 匹配以 /images/ 开头的所有请求 # 例如:/images/logo.png, /images/icons/home.jpg # 即使有正则匹配更精确,也会优先使用此规则 root /var/www/images;}location ^~ /admin { # 匹配以 /admin 开头的所有请求 # 例如:/admin, /admin/, /admin/users auth_basic \"Admin Area\";}
  • 正则匹配(~~*):第三优先级,按配置文件中从上到下出现的顺序依次检查,第一个匹配成功的生效;若多个正则同时匹配,优先使用更长路径(因顺序匹配机制,该情况需所有前置正则均未匹配才会发生,实际场景极少见)。示例:
# ~ 区分大小写正则匹配location ~ \\.(gif|jpg|jpeg)$ { # 匹配图片文件 expires 30d; add_header Cache-Control \"public\";}# ~* 不区分大小写正则匹配 location ~* \\.(css|js)$ { # 匹配样式和脚本文件 expires 1y; gzip on;}# 捕获组示例location ~ ^/user/(\\d+)/profile$ { # 匹配 /user/123/profile # 可以使用 $1 获取用户ID proxy_pass http://backend/user/$1/profile;} 
  • 普通前缀匹配:最低优先级,URI以指定字符串开头时匹配,无修饰符,选择所有匹配规则中路径最长的配置。示例:
location / { # 匹配所有请求(兜底规则) # 优先级最低,但会选择最长匹配 try_files $uri $uri/ /index.php?$query_string;}location /api/ { # 匹配 /api/ 开头的请求 # 比/更具体,所以优先级更高 proxy_pass http://api_backend;}

2.2、优化策略

  • 精确匹配优先:对高频访问的静态资源(如 /favicon.ico)使用=

  • 减少正则匹配:避免正则检查,提高性能。

# 不推荐:大量正则匹配location ~ ^/user/(\\d+)$ { }location ~ ^/category/(\\w+)$ { }# 推荐:使用前缀匹配location ^~ /user/ { # 统一处理用户相关请求 proxy_pass http://user_service;}location ^~ /category/ { proxy_pass http://category_service;}# 避免过于复杂的正则location ~ ^/(user|admin|api|static)/(create|update|delete|view)/[a-zA-Z0-9_-]+$ { # 复杂正则影响性能}# 优化为简单匹配location ^~ /user/ { # 简单前缀匹配}
  • 正则匹配顺序优化:避免优先级冲突;将高频匹配的正则放在前面。

server { listen 80; # 错误示例:优先级冲突 location ^~ /static/ { # 这个会阻止下面的正则匹配 alias /var/www/static/; } # 这个永远不会被匹配到 location ~* \\.(css|js)$ { expires 1y; gzip on; } # 正确示例:合理安排优先级 location ~* \\.(css|js)$ { # 先处理特定文件类型 expires 1y; gzip on; alias /var/www/static/; } location ^~ /static/ { # 处理其他静态文件 alias /var/www/static/; } # 优化前(按字母顺序) location ~ \\.html$ { } location ~ \\.php$ { } # 优化后(按访问频率排序) location ~ \\.php$ { } # 最常访问 location ~ \\.html$ { } # 常访问 }

        除此此外,结合nginx http各处理阶段的特征优化的手段还有很多。禁用不必要的模块以降低内存占用与处理开销;启用TLSv1.3 可显著减少握手阶段的性能损耗;对文本类资源(如 HTML、CSS、JSON)启用压缩(如 gzip 或 br),能大幅缩减传输体积;缓存高频访问且更新频率低的API结果,可有效减轻后端服务负载;借助 rsyslog、Fluentd 等工具将日志直接转发至外部日志服务,能够规避本地磁盘IO的性能瓶颈。

3、资源调优


        每一个请求都离不开底层基础设施资源的支撑,Nginx中HTTP连接的资源消耗主要涉及内存、CPU、文件描述符和网络带宽。合理优化这些资源的使用,是保障系统高并发、低延迟的关键。

        具体来看,CPU负载主要来自TCP连接的建立与释放、HTTP协议解析、SSL/TLS 加解密以及 gzip压缩等操作;内存方面,Nginx为每个连接分配连接结构体以及读写缓冲区,用于暂存请求头、请求体和响应数据。在响应包较小的场景下,数据可在内存中高效流转,CPU占用低,且不触发磁盘I/O,整体资源消耗极为可控。一台配置为4核8GB的服务器,在合理调优的前提下,理论上可稳定支持数万乃至数十万的并发连接。

        然而,当面对大请求体或大响应体的场景时,资源消耗模式发生显著变化。一旦请求体超过client_body_buffer_size、响应体超过proxy_buffers,Nginx便会将数据写入临时文件,从而引入磁盘I/O操作。这不仅增加了延迟,还可能因频繁的读写导致I/O瓶颈,严重影响吞吐能力。且包体越大,协议解析、校验等逻辑越复杂,CPU占用也会随之攀升。

        在现代复杂的Web服务架构中,业务逻辑日趋复杂与多元化。为了保障核心业务 API 的处理性能和稳定性,强烈建议将大尺寸资源交互及高流量传输类业务进行物理隔离。 譬如将静态图片、视频等媒体资源的分发,以及大文件上传下载等高 I/O负载的操作,拆分部署到独立的Nginx集群中。这种隔离策略能有效避免此类资源密集型任务抢占核心业务处理所需的宝贵计算和网络资源,在此基础上再针对不同的业务场景和请求特征进行精细化配置。资源调优的核心逻辑,在于通过精准设置缓冲区参数最大限度规避磁盘 I/O,同时结合底层TCP连接的深度优化,构建高效、稳定的数据传输通道。

  • 固定内存开销:每个连接(ngx_connection_t)需存储连接状态、读写事件等基础信息,约占用232字节(64位系统)。每个连接关联的读写事件(ngx_event_t)各占 96字节,总计约 424字节/连接(不同nginx版本存在细微差别)。这部分与包大小无关。

  • 动态缓冲区内存:

        单请求内存占用 ≈ client_header_buffer_size + client_body_buffer_size + proxy_buffer_size + proxy_buffers。

  1. 请求阶段:根据配置的缓冲区大小(如client_header_buffer_size、client_body_buffer_size)分配内存。例如,默认请求头缓冲区为4KB,若请求头超过此值,需额外分配large_client_header_buffers。
  2. 响应阶段:代理场景下,proxy_buffer_size(响应头缓冲区)和proxy_buffers(响应内容缓冲区)按需分配。例如,proxy_buffers 8 16k 会为每个连接预分配128KB内存,响应内容过大时可能触发磁盘缓存。

  • 磁盘IO:若请求/响应包超过内存缓冲区限制,Nginx将请求体写入临时文件,增加磁盘I/O。

  • CPU消耗:解析大型请求头(如Cookie、Authorization)或复杂路由规则(如正则匹配)会增加CPU占用;压缩(gzip)、加密(SSL/TLS)操作显著消耗CPU。

  • 文件描述符:每个连接至少占用1个fd,反向代理场景需额外FD连接后端服务器(总计2个fd/每请求)。高并发时需调整系统FD上限(worker_rlimit_nofile)。

  • 网络传输:大响应包(如图片、视频)占用更多网络带宽,并因TCP分段加重内核协议栈负担。网络传输优化参见之前的文章《网络传输性能优化:如何降低网络延迟、提升系统吞吐量?》。

        以下示例配置大家重点看注释,理解各参数的用途和上下文联系。

  • nginx参数调优示例:

# 设置每个单个worker进程可以打开的最大文件描述符数# 若要使这一配置生效,还需修改系统的ulimit限制worker_rlimit_nofile 65535;http { # 缓冲区配置直接影响内存使用 # 客户端读取缓冲区 # 设置客户端请求头的缓冲区大小 client_header_buffer_size 2k; # 默认1k # 当请求头超过client_header_buffer_size时使用 # 若出现414 Request-URI Too Large或400 Bad Request错误,说明请求头过大,可增大此值 # 格式为number size,number表示最多可分配的缓冲区数量 large_client_header_buffers 4 8k; # 设置客户端请求体的缓冲区大小,存储POST等请求的body数据 # 超过设定值时,Nginx会将请求体写入磁盘临时文件 client_body_buffer_size 128k; # 默认16k # 磁盘临时文件路径由client_body_temp_path指定,需保证磁盘IO性能良好 client_body_temp_path /mnt/ssd/client_temp 1 2; # 设置客户端请求体的最大大小,超过此值返回413错误,防止过大的文件上传 client_max_body_size 10m; # 代理相关缓冲区 proxy_buffering on; # 默认开启 # 当Nginx反向代理后端服务(PHP-FPM使用fastcgi_buffer_*配置)时,用于缓冲后端返回的响应数据 # 设置代理响应头的缓冲区大小,存储后端服务器响应头 proxy_buffer_size 4k; # 默认4k #设置代理响应体的缓冲区数量和大小,存储后端服务器响应体内容 # 总大小为number * size,如api响应通常 < 1M,可设8 16k,总计128k proxy_buffers 8 16k; # 默认8个4k # 响应数据正在发送给客户端时,Nginx可以保留在内存中的\"忙\"缓冲区的总大小 # \"忙\"缓冲区指已从上游服务器接收完成,但尚未完全发送给客户端的缓冲区 # 值过小 → 频繁触发磁盘缓冲 → 性能暴跌 # 建议设为proxy_buffers单缓冲区大小的2倍,确保连续传输效率 proxy_busy_buffers_size 32k; # 当响应体超过proxy_buffers总大小时,单响应可写入临时文件的最大数据量 # 默认1024MB,允许单响应写入1GB临时文件,磁盘空间可能被快速耗尽 # 建议将文件下载等大响应体服务与业务接口服务的nginx进行拆分 # 通用Web/API服务建议设为0完全禁用磁盘缓冲,需确保99%请求响应体 < proxy_buffers总大小 proxy_max_temp_file_size 0; # 每次写入临时文件的数据块chunk大小 # 默认 = proxy_buffers单个缓冲区大小,造成高频小I/O # 建议根据磁盘类型调整,如NVMe SSD为128k~1m、SATA SSD为64k~256k、HDD为32k~128k proxy_temp_file_write_size 128k; #高性能SSD配置,大块写入减少I/O次数 # 挂载独立SSD分区 proxy_temp_path /mnt/ssd/nginx_temp 1 2; # Nginx本身不支持异步I/O写日志,可以通过缓冲机制模拟异步效果 # buffer缓冲区满或flush超时任一条件触发时,才会执行磁盘写入 access_log /var/log/nginx/access.log buffer=32k flush=1s; # 跳过用户态缓冲区,直接通过内核态传输文件数据 # 可配置在location块中,适合大文件和静态内容传输(如图片、CSS、JS等) # 启用gzip后,sendfile被绕过,不起作用 sendfile on; # 需与sendfile on配合使用 # 让TCP协议尽可能地将数据包“满载”发送,减少小数据包传输,提高网络利用率 tcp_nopush on; # 禁用Nagle算法,主要影响长连接(如keepalive),减少延迟 # 适合动态内容或低延迟场景 # tcp_nopush 和 tcp_nodelay 一般不同时开启 tcp_nodelay on; # 客户端连接在空闲状态下保持打开的最长时间(秒),超时后连接关闭,避免资源浪费 # 注意:控制客户端到Nginx的连接,若Nginx作为反向代理,需额外配置后端连接池 # 默认 75s # 注意:服务端超时应大于客户端(如浏览器Chrome/Firefox约5~15s)超时,避免\"Connection reset by peer\"错误重新建立连接 # 高并发场景建议调小,可快速释放空闲连接,减少连接堆积,防止资源被无效占用 keepalive_timeout 30s; # 单个长连接可处理的最大请求数量,达到上限后连接自动关闭,强制客户端重建连接 # 默认值 100 # 高并发尤其是短连接场景建议调大,减少连接重建次数,提升吞吐量 keepalive_requests 1000; upstream backend { server 10.0.0.1:8080; server 10.0.0.2:8080; # 每个worker进程的空闲连接池大小,必须配置,否则nginx与后端仍为短连接,性能无法提升 # 假设QPS=10000,平均响应时间0.1秒 → 需约1000个连接 → 4个worker → 参考值keepalive 250 keepalive 250; } server { location / { proxy_pass http://backend; proxy_http_version 1.1; # 清空Connection头,启用Keepalive proxy_set_header Connection \"\"; } }}
  • 文件描述符限制:

# /etc/security/limits.conf# 用户&进程级别文件描述符限制,单个进程能打开的文件描述符数* soft nofile 65535* hard nofile 65535# /etc/sysctl.conf# 系统级文件描述符限制,所有进程能打开的文件描述符总数fs.file-max = 100000
  • 系统TCP优化:

        系统内核TCP参数调优是一项极具复杂性的工作,其难度不仅在于参数数量庞大,更源于参数间的深层耦合性、场景依赖性,以及对网络协议栈底层逻辑的深度依赖。这里暂且不深入展开,后续撰写专门的文章详细介绍。

# /etc/sysctl.conf# TCP连接复用# 允许客户端主动复用处于TIME_WAIT状态的连接,解决因短连接频繁创建导致端口耗尽的问题# 适用于客户端角色(如Nginx作为反向代理向上游服务器发起大量短连接),# tcp_tw_reuse不能解决服务器端TIME_WAIT问题,因为服务端被动监听端口无法主动复用连接net.ipv4.tcp_tw_reuse = 1# 强制快速回收TIME_WAIT连接,减少资源占用# 对客户端和服务端均生效# 注意:在NAT环境中会导致随机连接失败,不应再使用# 注意:Linux4.12+ 内核已彻底移除此参数,因设计缺陷无法安全适配现代网络环境net.ipv4.tcp_tw_recycle = 0

        在实际运维中需要结合自身业务场景以及服务器资源进行调整,参数调整后的压测和监控非常关键。比如Nginx日志出现“an upstream response is buffered to a temporary file”说明proxy_buffers不足;还可以通过iotop、prometheus等工具监控临时文件(client_body_temp_path、proxy_temp_path)的使用频率和数量来判断buffer配置的合理性,如果频繁使用临时文件,意味着需要适当增加client_body_buffer_size或proxy_buffers大小。

专栏文章推荐:

离开互联网职场,我将何去何从?

告别16年IT生涯:一场关于内耗、健康和出路的反思

软件界牛马:Nginx核心原理与性能优化全攻略

架构实战:2万字真实案例全方位解析高并发系统性能优化之道

架构师指南:像呵护感情一样构建无懈可击的系统稳定性防线

系统性能评估:如何定义并发数、响应时间和吞吐量

实战:混合云架构Nginx+Lua实现流量跨机房分发

架构图的魅力:如何用UML提升架构设计的质量

为什么要做架构设计?架构设计包含哪些内容?

架构师的职责是什么?程序员如何转型为架构师?