VPS上的Nginx性能调优
在Linux VPS上调优Nginx以应对生产流量。涵盖worker进程、压缩、缓存、HTTP/2、TLS优化、内核sysctl参数以及wrk基准测试。
默认的Nginx安装可以处理中等流量,没有任何问题。但默认配置偏保守。在一台4 vCPU、8 GB内存的VPS上,通过调整worker进程、压缩、缓存和内核参数,你可以大幅提升每秒请求数。本指南逐层讲解每个优化点,并用基准测试证明每项更改的效果。
我们假设Nginx已经安装并在提供服务。如果还没有,请先阅读VPS上的Nginx管理指南。
所有示例针对Debian 12或Ubuntu 24.04上的Nginx mainline版本。配置文件结构在Nginx配置文件结构指南中介绍。
如何建立Nginx的性能基准线?
在更改任何配置之前,先用wrk测量当前性能。这为你提供了调优后的对比基准。没有数据,你只是在猜测。
在另一台机器(你的本地工作站或另一台VPS)上安装wrk。永远不要在被测试的服务器上运行基准测试。基准测试工具和Web服务器会争夺CPU资源,结果毫无意义。
apt install wrk
对一个静态页面运行30秒测试,使用4个线程和200个连接:
wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
4 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.34ms 5.67ms 89.12ms 78.45%
Req/Sec 4.12k 312.45 5.23k 72.50%
493440 requests in 30.01s, 1.92GB read
Requests/sec: 16442.18
Transfer/sec: 65.52MB
记录四个数字:requests/sec、平均延迟、最大延迟和transfer/sec。这就是你的基准线。
Nginx应该使用多少个worker进程和连接数?
将worker_processes设为auto,每个CPU核心生成一个worker。在4 vCPU的VPS上,这意味着4个worker。每个worker是单线程的,通过epoll独立处理连接。每核一个worker避免了上下文切换的开销。
最大并发连接数的公式:
最大连接数 = worker_processes x worker_connections
| vCPUs | worker_processes | worker_connections | 最大连接数 |
|---|---|---|---|
| 1 | 1 | 2048 | 2,048 |
| 2 | 2 | 2048 | 4,096 |
| 4 | 4 | 2048 | 8,192 |
| 8 | 8 | 2048 | 16,384 |
每个连接消耗一个文件描述符。将worker_rlimit_nofile设置为高于worker_connections的值,以避免触及操作系统限制。安全值为worker_connections * 2,这考虑了代理时的upstream连接。
编辑/etc/nginx/nginx.conf:
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
worker_cpu_affinity auto将每个worker绑定到一个CPU核心,减少进程迁移导致的缓存未命中。multi_accept on让worker一次性接受所有等待中的连接,而不是逐个接受。use epoll在Linux上是默认值,但显式声明更好。
accept_mutex指令自Nginx 1.11.3起默认为off,因为Linux内核4.5+支持EPOLLEXCLUSIVE,可以在worker之间分配连接而无需互斥锁。保持关闭状态。
nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx
哪些TCP指令能提升Nginx吞吐量?
三个指令协同工作,优化Nginx通过TCP发送数据的方式。sendfile绕过用户空间缓冲区,直接在内核中的文件描述符之间复制数据。tcp_nopush将响应头和文件开头部分打包到一个TCP包中。tcp_nodelay禁用Nagle算法,让小包(如响应的最后部分)立即发送。
在/etc/nginx/nginx.conf的http块中添加:
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
#...现有指令...
}
sendfile对静态文件服务影响最大。没有它,Nginx将文件读入缓冲区,然后将缓冲区写入socket。两次复制。有了sendfile,内核执行零拷贝传输。在繁忙的静态文件服务器上,仅此一项就能明显降低CPU使用率。
tcp_nopush和tcp_nodelay并不矛盾。Nginx在构建响应时应用tcp_nopush,然后在最后一个包时切换到tcp_nodelay。结果:总包数更少,最后一个包无延迟。
如何调整Nginx的keepalive连接?
Keepalive连接让客户端可以复用一个TCP连接发送多个HTTP请求。这避免了每次请求都要进行TCP握手和TLS协商的开销。单个页面加载可能触发20-50个子请求,用于CSS、JS、图片和字体。
http {
keepalive_timeout 65;
keepalive_requests 1000;
#...现有指令...
}
keepalive_timeout 65在65秒后关闭空闲连接。设太高会在空闲客户端上浪费文件描述符。设太低会强制重连。65秒对大多数工作负载来说是合理的默认值。
keepalive_requests 1000允许每个连接最多1,000个请求,之后Nginx关闭连接。自Nginx 1.19.10起默认值为1000。如果你位于发送大量请求的负载均衡器后面,可以增加到10000。
Upstream keepalive
如果Nginx代理到后端(Node.js、Python、Go),upstream keepalive连接可以避免Nginx每次请求都向后端打开新的TCP连接。这是大多数代理配置浪费时间的地方。
upstream backend {
server 127.0.0.1:3000;
keepalive 64;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
keepalive 64为每个worker保持64个到后端的空闲连接。proxy_http_version 1.1是必需的,因为keepalive是HTTP/1.1的功能。空的Connection头清除了客户端的Connection: close头,防止Nginx将其转发到upstream。
更多代理配置信息,参见Nginx反向代理指南。
如何调整Nginx代理缓冲区大小?
当Nginx代理请求时,它会缓冲后端的响应。如果缓冲区太小,Nginx会将响应写入磁盘上的临时文件。磁盘I/O比内存慢几个数量级。
http {
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
#...现有指令...
}
proxy_buffer_size 16k处理后端的响应头。大多数头在4k-8k内,但设置了大量cookie或自定义头的应用需要更多。16k安全且不浪费。
proxy_buffers 4 32k为响应体分配4个32k的缓冲区(每个连接共128k)。根据典型响应大小来设置。100k以下的API响应可以轻松容纳。如果你提供大payload,增加缓冲区数量而不是大小。
proxy_busy_buffers_size 64k控制Nginx在继续从后端读取数据时可以向客户端发送多少缓冲数据。不应超过proxy_buffers的总量。
注意错误日志中的这条消息:
an upstream response is buffered to a temporary file
如果频繁出现,增加proxy_buffers。检查日志:
journalctl -u nginx --no-pager | grep "temporary file"
如何在Nginx中配置静态文件缓存?
静态文件缓存告诉浏览器在本地存储资源。这完全消除了重复请求。对于带哈希文件名的资源(如app.a1b2c3.js),设置激进的过期时间。对于HTML,保持较短。
server {
location ~* \.(css|js|woff2|woff|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location ~* \.(jpg|jpeg|png|gif|ico|webp|avif)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, no-cache";
}
}
immutable告诉浏览器完全不要重新验证该资源。只用于带指纹的文件名。静态资源的access_log off减少了日志产生的磁盘I/O。
关于缓存头与安全头的交互,参见。
open_file_cache
Nginx可以缓存频繁访问文件的文件描述符、修改时间和存在性检查。这避免了重复的stat()和open()系统调用。
http {
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
max=10000最多保留10,000个条目。根据你提供的静态文件数量来设置。如果你提供500个文件,max=1000就够了。如果你从CDN源提供50,000个资源,相应增加。
inactive=30s驱逐30秒内未访问的条目。open_file_cache_min_uses 2只缓存在inactive窗口内至少访问过两次的文件。这防止一次性请求污染缓存。
open_file_cache_errors on也缓存404查找。如果客户端反复请求一个不存在的文件,Nginx从缓存中响应,而不是每次都访问文件系统。
如何在Nginx中启用gzip和Brotli压缩?
Gzip级别4-6在CPU成本和压缩比之间提供最佳平衡。超过级别6,压缩率提升不到2%,但CPU时间翻倍。Brotli级别4通常比gzip级别9压缩效果更好,CPU成本相近。
Gzip
http {
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
}
gzip_min_length 256跳过小于256字节的文件。压缩微小文件可能因gzip头而产生比原文件更大的输出。gzip_vary on添加Vary: Accept-Encoding头,让缓存分别存储压缩和未压缩版本。gzip_proxied any即使请求经过代理也压缩响应。
Brotli
Brotli在文本资源上比gzip压缩率高15-25%。所有现代浏览器都支持它。在Ubuntu 24.04上使用发行版的Nginx包时,直接安装模块:
apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-static
在Debian 12或使用来自nginx.org的Nginx mainline时,Brotli模块未包含在内。你需要将其编译为动态模块或使用第三方仓库。ngx_brotli仓库有构建说明。
模块加载后,进行配置:
http {
brotli on;
brotli_comp_level 4;
brotli_static on;
brotli_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml;
}
brotli_static on在.br预压缩文件存在时直接提供。这让你可以在构建时使用更高压缩级别(如11)压缩资源,而无需在运行时承担CPU成本。
brotli_comp_level 4是动态压缩的最佳选择。与gzip不同,Brotli的级别1-4很快。级别5+会变得慢很多。
压缩对比
| 内容类型 | gzip级别5 | Brotli级别4 | 胜出 |
|---|---|---|---|
| HTML | 72% | 78% | Brotli |
| CSS | 80% | 85% | Brotli |
| JavaScript | 75% | 82% | Brotli |
| JSON | 78% | 83% | Brotli |
比率表示相对于原始文件节省的字节数。Brotli始终以5-8个百分点的优势胜出。
两个模块可以同时运行。Nginx向声明Accept-Encoding: br的客户端提供Brotli,其余回退到gzip。
如何优化Nginx的TLS和HTTP/2性能?
TLS通过握手和密钥交换增加延迟。会话缓存、OCSP stapling和TLS 1.3将这种开销降到最低。HTTP/2在单个连接上多路复用请求,消除了HTTP层的队头阻塞。
HTTP/2
自Nginx 1.25.1起,listen指令上的http2参数已弃用。改用http2指令:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
#...
}
TLS性能
http {
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 127.0.0.53 valid=300s;
resolver_timeout 5s;
}
ssl_session_cache shared:SSL:10m将TLS会话参数存储在10 MB的共享内存区域中。1兆字节可以保存约4,000个会话。回访客户端跳过完整的TLS握手,用更廉价的操作恢复会话。
ssl_session_tickets off是更安全的默认值。会话票据使用对称密钥,如果泄露,可以解密所有过去的会话(无前向保密)。如果你需要多服务器配置的票据,请频繁轮换密钥。
ssl_stapling on让Nginx从CA获取并缓存OCSP响应,然后将其包含在TLS握手中。客户端不需要单独联系CA。这在首次连接时节省100-300毫秒。
ssl_prefer_server_ciphers off对TLS 1.3是正确的,因为客户端和服务器以不同方式协商密码套件。对于TLS 1.2向后兼容,选择的密码套件仍然重要,但TLS 1.3的密码套件都很强。
完整的TLS和Let's Encrypt设置,参见Nginx TLS与Let's Encrypt配置指南。
哪些Linux内核参数能提升Nginx性能?
四个内核参数限制了Nginx处理高连接量的能力。默认值对通用服务器来说偏保守。调整它们可以消除操作系统层面的瓶颈。
| 参数 | 默认值 | 推荐值 | 原因 |
|---|---|---|---|
net.core.somaxconn |
4096 | 65535 | 最大监听积压队列。低值在突发流量时导致连接丢弃。 |
fs.file-max |
~100000 | 500000 | 系统级文件描述符限制。每个连接是一个文件描述符。 |
net.ipv4.tcp_tw_reuse |
0 | 1 | 重用TIME_WAIT状态的socket用于新连接。加速连接回收。 |
net.ipv4.tcp_fastopen |
0 | 3 | 为客户端和服务器启用TCP Fast Open。新连接节省一个往返。 |
net.ipv4.ip_local_port_range |
32768 60999 | 1024 65535 | 扩展出站连接(代理/upstream)的临时端口范围。 |
net.core.netdev_max_backlog |
1000 | 16384 | 当网卡接收速度快于内核处理速度时的入站数据包队列长度。 |
无需重启即时生效:
sysctl -w net.core.somaxconn=65535
sysctl -w fs.file-max=500000
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fastopen=3
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w net.core.netdev_max_backlog=16384
重启后持久化:
cat > /etc/sysctl.d/99-nginx-tuning.conf << 'EOF'
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384
EOF
sysctl -p /etc/sysctl.d/99-nginx-tuning.conf
net.core.somaxconn = 65535
fs.file-max = 500000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 16384
还需要增加Nginx systemd单元的每进程文件描述符限制。创建一个override:
mkdir -p /etc/systemd/system/nginx.service.d
cat > /etc/systemd/system/nginx.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=65535
EOF
systemctl daemon-reload
systemctl restart nginx
cat /proc/$(pgrep -f 'nginx: master')/limits | grep "open files"
Max open files 65535 65535 files
如何优化访问日志性能?
每个请求写一行日志会消耗磁盘I/O。在高流量服务器上,访问日志可能成为瓶颈。缓冲日志以批量方式写入磁盘。
http {
access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
}
buffer=64k在64 KB内存缓冲区中累积日志条目。flush=5s至少每5秒将缓冲区写入磁盘,即使未满。用几秒钟的日志延迟换取大幅减少的磁盘I/O。
如果不需要静态资源(图片、CSS、JS)的访问日志,按上面缓存部分所示按location禁用。
调优后的Nginx快多少?
应用所有更改后运行同样的wrk基准测试。从同一台机器、使用相同参数测试:
wrk -t4 -c200 -d30s https://your-server.example.com/
Running 30s test @ https://your-server.example.com/
4 threads and 200 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.21ms 2.34ms 42.56ms 81.23%
Req/Sec 9.78k 478.12 11.42k 68.75%
1171200 requests in 30.02s, 4.56GB read
Requests/sec: 39013.66
Transfer/sec: 155.61MB
调优前 vs. 调优后
| 指标 | 调优前 | 调优后 | 变化 |
|---|---|---|---|
| Requests/sec | 16,442 | 39,014 | +137% |
| 平均延迟 | 12.34ms | 5.21ms | -58% |
| 最大延迟 | 89.12ms | 42.56ms | -52% |
| Transfer/sec | 65.52 MB | 155.61 MB | +137% |
这些数据来自一台Virtua Cloud VPS,4 vCPU、8 GB内存,运行Debian 12和Nginx mainline,提供带CSS和JavaScript资源的静态HTML页面。你的结果会因工作负载、网络条件以及是否代理到后端而有所不同。
最大的收益来自内核sysctl(消除操作系统瓶颈)、worker/连接调优(利用所有可用CPU)和压缩(减少网络传输字节)。TLS会话缓存和HTTP/2的效果较小但可以测量,特别是在首次连接延迟方面。
完整的调优配置
应用了所有调优的完整/etc/nginx/nginx.conf:
user www-data;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 8192;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# TCP优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Keepalive
keepalive_timeout 65;
keepalive_requests 1000;
# 缓冲区
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# 文件缓存
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
# Brotli(如已安装模块)
# brotli on;
# brotli_comp_level 4;
# brotli_static on;
# brotli_types text/plain text/css text/javascript
# application/javascript application/json application/xml
# image/svg+xml;
# TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# 日志
access_log /var/log/nginx/access.log combined buffer=64k flush=5s;
error_log /var/log/nginx/error.log warn;
# 隐藏版本
server_tokens off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
server_tokens off在响应头和错误页面中隐藏Nginx版本。版本泄露帮助攻击者针对已知漏洞。
出了问题?
首先检查错误日志:
journalctl -u nginx -f
调优后常见的问题:
- 错误日志中出现**"too many open files"**:
worker_rlimit_nofile低于worker_connections,或systemd的LimitNOFILE未设置。两者都要检查。 - "could not build optimal types_hash":在
http块中将types_hash_max_size增加到4096。 - Brotli模块无法加载:运行
nginx -V 2>&1 | grep brotli检查模块是否已编译。如果使用动态模块,确认load_module指令在nginx.conf顶部。 - OCSP stapling不工作:Nginx启动后的第一个请求不会有stapled响应。用
openssl s_client -connect your-server:443 -status < /dev/null 2>&1 | grep -A 2 "OCSP Response"测试。如果显示"no response sent",检查ssl_trusted_certificate是否指向完整证书链,以及resolver是否已设置。 - wrk没有显示改善:确保从另一台机器测试。如果通过互联网测试,网络延迟占主导地位,会掩盖服务器端的改善。从同一数据中心的VPS测试以获得准确的服务器性能数据。
版权所有 2026 Virtua.Cloud。保留所有权利。 本内容为 Virtua.Cloud 团队原创作品。 未经书面许可,禁止复制、转载或再分发。