nginx是一款高性能的开源Web服务器和反向代理服务器。它由俄罗斯的工程师Igor Sysoev开发,并于2004年首次公开发布。Nginx的设计目标是提供高性能、稳定性和低资源消耗的解决方案,以应对大流量的网站和应用程序。
ginx的主要特点之一是其事件驱动的架构,它采用异步、非阻塞的方式处理并发连接。这使得Nginx能够高效地处理大量的并发请求,而不会占用过多的系统资源。此外,Nginx还具有灵活的配置选项和模块化的架构,使其能够适应各种不同的应用场景。
然而人要衣装马靠鞍,虽然nginx本身的设计架构上可以有能力提供大并发高性能服务,但是在实际部署的时候,也需要根据业务需要对nginx以及其依赖的操作系统进行性能参数调整,以期让nginx能够在实际应用环境中达到最佳性能和安全性。本文参考nginx的官方文档和网络上的成功经验,结合自己的学习和实践经验,全方位地对nginx服务器的配置优化进行了描述。
2.1 CPU和进程数调整
nginx配置文件中的worker_processe参数可以用来给nginx配置worker进程数量,一般的建议是,如果服务器有多少CPU核就最大配置多少worker进程,可以直接配置成:
worker_process auto;
text
让nginx启动的时候自己检测有多少CPU核并启动对应数量的worker进程。 当然也不是一概而论的,譬如对于本身nginx只是做代理转发功能的服务器,往往CPU不是瓶颈,按需配置进程数即可,而对于高磁盘I/O的业务环境,适当超过实际的CPU核心数反而可以提高系统的响应能力,不至于因为部分请求的磁盘读写卡顿导致其他请求的响应受到影响。
nginx配置文件中的worker_cpu_affinity参数用来设置worker进程和cpu核之间的绑定关系,指定让某个worker进程在某个或某几个CPU核上运行,避免因为进程在不同的核之间进行切换,导致性能的下降。从1.9.10版本开始,worker_cpu_affinity参数也可以配置成auto,让nginx自行执行CPU核心亲和性绑定操作。
2.2 并发连接数调整
linux操作系统单进程的默认最大可以打开的文件句柄数是1024个,这对于一个提供高并发服务的nginx是远远不够的,因此首先需要打开linux操作系统的限制,然后在nginx配置里面同步增加可以接受的并发连接的数量。
2.2.1 修改操作系统单进程最大句柄数限制
通过 ulimit -n 命令查看当前linux的限制值,如果只是临时修改限制,则直接可以用以下命令来设置允许但进程最多可以打开65536个文件句柄:
ulimit -n 65536
那么单个进程打开65536个句柄是上限吗?远远不是的,只是大家习惯用65536来举例而以,一般来说也是足够用了。
以上设置方法当linux重启后就实效了,那么如果希望无论是不是操作系统重启设置都永久生效,那么就需要修改linux的核心配置,首先需要确认linux内核允许的最大文件句柄数:
cat /proc/sys/fs/file-max
1000000
这表明这台Linux系统最多允许一个单独的Linux用户登录会话可以同时打开(即包含所有用户打开文件数总和)1000000个文件,是Linux系统级硬限制。也可以通过以下命令查看:
sysctl fs.file-max
并通过以下命令进行修改:
sysctl -w fs.file-max=1000000
解开了内核级别的限制后,再修改用户级别的限制, 配置文件 /etc/security/limits.conf了,添加以下两行:
* soft nofile 65536
* hard nofile 65536
nginx配置文件中的event块中的worker_connections参数可以用来配置单个worker进程允许建立的最大tcp并发连接数,那么如果配置了n个worker进程,理论上nginx可以支持n*worker_connections个并发连接。配置如下:
# 允许一个worker最大打开的文件句柄数
worker_rlimit_nofile 65536;
events {
......
# 允许一个worker支持的最大并发连接数
worker_connections 60000;
}
需要注意的是n*worker_connections是不应该超过/proc/sys/fs/file-max的值的,同时worker_connections也应该是小于/etc/security/limits.conf中的配置的。
2.3 使用操作系统的高性能异步I/O模型:
对于不同的操作系统,配置采用不同的异步I/O模型,列表如下:
linux | mac | free-bsd | windows |
---|---|---|---|
epoll | kqueue | kqueue | iocp |
以linux为例,配置如下:
events {
......
use epoll;
}
对于windows, iocp模型在老的nginx版本中中是不支持的,如果要在windows中使用要么就升级到最新版本要么就是自己从新版本中将iocp事件模块移植过来。
2.4 启用keepalived功能
http keepalive连接保持特性能够避免每次进行http请求都需要重新建立tcp连接带来的额外消耗,从而提升http访问的响应效率。
2.4.1 和客户端保持keepalive
nginx控制和客户端连接的keepalive是通过以下四个参数来控制的:
- keepalive_disable:可以指定对于那些keepalived处理功能缺陷的浏览器关闭keepalive功能,譬如msie6。
- keepalive_time:表示一个连接最大存活的可复用的时间。
- keepalive_timeout:表示如果超过指定的时间没有新的请求该连接就会被关闭。
- keepalive_requests:表示一个被复用的连接最大支持多少次请求,超过次数后连接就会被关闭了。
举例如下:
http {
keepalive_timeout 60;
keepalive_requests 100;
}
如果ngi=nx作为代理服务器提供http服务,那么nginx本身也可以配置为和上游服务器保持keepalive的连接复用。和上游服务器的keepalive功能是通过ngx_http_upstream_module来提供的。包括以下配置指令:
- keepalive: 用来配置最大和上游服务器保持多少个keepalive的连接。
- keepalive_time: 表示一个连接最大存活的可复用的时间。
- keepalive_timeout: 表示如果超过指定的时间没有新的请求该连接就会被关闭。
text
例如:
upstream memcached_backend {
......
keepalive 32;
keepalive_time 100;
keepalive_timeout 60;
}
http协议规定了一套缓存规则,我们可以利用这点,来是很少更新的内容能够被缓存到浏览器的缓存里面,减少浏览器对nginx数据的访问量,降低nginx的资源消耗,提升系统性能,例如:
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
}
以上配置开启了对静态图片文件、ico图标文件、css文件、js文件的缓存,允许浏览器缓存30天,缓存时间超过30天后再次访问就会重新向nginx成为器发起下载请求。
2.5.2 启用nginx代理缓存
如果nginx是作为代理服务器为用户提供http服务的,那么nginx在做代理的时候还可以对代理的内容开启缓存功能,这样子有助于降低上游服务器的请求压力,提升整体系统的性能。和客户端缓存不同,nginx代理缓存是存储在nginx服务器的本地磁盘里面的。例如:
# 定义一个proxy_cache代理缓存
# 缓存内容存放在/path/to/cache目录中
# 使用共享内存test_cache存放缓存索引表
# 可以缓存的内容最大10g
# inactive指定了内容如果超过多久未被访问将被回收
# 如果为off,则nginx会将缓存文件直接写入指定的cache文件中,而不是使用temp_path存储,
# official建议为off,避免文件在不同文件系统中不必要的拷贝
proxy_cache_path /path/to/cache levels=1:2 keys_zone=test_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
location / {
# 对当前location启用proxy cache,指定key_zone
proxy_cache test_cache;
proxy_pass http://www.test.com;
}
}
关于配置代理缓存功能的更多信息可以查看 Nginx Proxy Cache原理和最佳实践。
2.5.3 启用nginx本地文件缓存
nginx在提供静态文件访问服务的时候,需要不断打开/关闭本地的磁盘文件,大量的打开/关闭操作会影响系统的性能,所以nginx提供了本地磁盘文件句柄的缓存功能以缓解这个问题。例如:
http {
...
open_file_cache max=1024 inactive=10s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
通过上述设置,nginx在其打开文件缓存中最多缓存1024个句柄,如果超过inactive=10s时间没有使用则回收对应的文件句柄,缓存的有效期为60s,并且如果文件句柄要被放入缓存至少要使用2次,文件打开错误(譬如找不到该文件)的状态也会被缓存。实际业务环境中,可以根据业务并发量和服务器的资源情况进行参数的优化调整。
详细的信息可以查看nginx的官方文档。
2.6 日志优化
nginx在运行的过程中会产生大量的访问日志和error日志,在并发量大的时候,也有可能引起nginx本地磁盘i/o被大量消耗在了写日志上面,因此有时候也有必要进行日志优化。
2.6.1 日志缓存
nginx提供了日志缓存的功能,避免每次写日志都产生磁盘I/O引起性能下降,例如:
access_log /var/log/nginx/access.log main buffer=128k flush=3s;
以上配置给名为main的access_log设置了一个128k的缓存,当日志写满128k或者超过3s才会将它刷到磁盘中。
2.6.2 关闭日志
如果确认不需要保存访问日志,可以直接关闭掉(虽然不推荐),例如:
access_log off;
并且通过提高日志等级来减少errror log的输出,例如:
error_log /opt/nginx/logs/error.log error;
2.7.1 客户端请求超时
限制超时值可以提高性能:它将在指定的时间段内等待客户端的头部和请求体,如果响应数据在该时间段内未到达,nginx将主动关闭连接,避免nginx被低效率的客户端消耗大量的连接资源引起性能下降,例如:
http {
client_body_timeout 10; # nginx两次socket recv客户端body的最大间隔时间
client_header_timeout 10; # nginx两次socket recv客户端header的最大间隔时间
send_timeout 10; # nginx两次socket send响应给客户端的最大间隔时间
}
text
以上一些参数对socket通信的超时时间进行了规定,如果超时则会关闭连接。
2.7.3 客户端请求内容的缓存
缓冲可以通过在缓冲区填充时保持部分响应来增加客户端到服务器通信的效率。当响应超过缓冲区大小时,nginx将将响应写入磁盘 - 这可能会对性能产生负面影响。但不要担心:您可以根据需要更改缓冲区大小,例如:
http {
client_header_buffer_size 2k; # 客户端http请求的正常头部的内存缓存大小
large_client_header_buffers 4 8k; # 设置最大的http请求头部的缓存数量和大小
client_body_buffer_size 128k; # 客户端http请求body部分的最大内存缓存
client_max_body_size 9m; # 最大允许的客户端上传的body部分的大小
}
client_header_buffer_size:可以设置一个正常http请求头所需的缓存大小,满足80%的需求,避免设置太小导致必须重新分配内存引起效率下降,或者太大引起资源浪费。如果有特别大的请求头超过了这个值,则会按照large_client_header_buffers设置的分配新的请求头缓存空间。
large_client_header_buffers:设置http请求头最多能够保存在多少个缓冲区中,每个缓冲区最大多少字节,而且规定了http请求头中的请求头字段是不能超过一个缓冲区大小的。
client_body_buffer_size:设置http请求中如果有body部分,那么最大可以缓存在内存中的大小,如果超过这个大小,将会被缓存到本地临时文件中去的。这个值应该能覆盖绝大部分POST请求的需求,避免因为设置太小引起大量的磁盘I/O情况的发生。
client_max_body_size:限制客户端提交的内容的大小,防止有客户端提交巨型数据引起nginx将其缓存到本地临时文件中,导致磁盘I/O激增或者本地空间不足的故障。
2.7.3 socket优化
listen *:80 backlog=2048;
在内核接收到一个连接请求后,正常的情况下是立即回调应用层(这里是nginx)进行处理,但是现在内核允许设置延后接收请求,等到客户端有数据法上来的时候才回调应用层,这样的好处是减少无效连接、提高处理效率、节约资源。配置如下:
listen *:80 deferred;
调整接收和发送缓冲区大小,根据TCP滑动窗口大小的占比1-1/(2^tcp_adv_win_scale),计算出缓冲区大小上限;
举例:例如若我们的带宽为2Gbps,时延为10ms,那么带宽时延积BDP则为2G/8*0.01=2.5MB,所以这样的网络中可以设最大接收窗口为2.5MB,当tcp_adv_win_scale=2时最大读缓存可以设为4/3*2.5MB=3.3MB。当然还要综合服务器的实际内存和并发量来考虑最终的配置值,配置举例如下:
listen *:80 recvbuf=1048576 sndbuf=1048576;
sendfile
指令在某些情况下可能会导致一些问题,例如在某些操作系统或文件系统中可能存在兼容性问题。因此,在特定的应用场景中,可能需要根据具体情况来决定是否启用或禁用sendfile
指令。例如:
http {
sendfile on;
tcp_nopush on;
}
2.8 异步文件io优化
异步文件IO是一种文件读写操作的方式,它允许Nginx在执行文件IO操作时不会阻塞其他的请求处理,从而提高服务器的并发性能。当aio被设置为on时,Nginx将使用异步文件IO进行文件的读取和写入操作。这意味着Nginx在进行文件IO操作时,不会阻塞其他的请求处理,而是继续处理其他请求。这对于高并发的场景非常有用,可以提高服务器的响应性能和吞吐量。
需要注意的是,默认情况下,aio是禁用的(即设置为off),这意味着Nginx将使用同步的文件IO操作。在同步模式下,Nginx在进行文件IO操作时,会阻塞其他的请求处理,直到文件IO操作完成。
在选择是否启用aio时,需要根据具体的应用场景和服务器硬件环境进行评估。异步文件IO在某些情况下可以显著提高性能,特别是在处理大量并发请求或对文件IO操作较频繁的情况下。但在某些特定的环境中,异步文件IO可能会导致性能下降或不稳定。因此,建议在启用aio之前进行性能测试和评估,以确定最佳的配置方式。
2.9 内容压缩
http协议规范支持对响应的内容进行压缩,nginx默认内置了gzip压缩能力。通过压缩方式响应,可以大大降低网络I/O,提升客户端的访问体验。但是压缩也不是免费的午餐,配置不当也会导致nginx因为需要进行压缩消耗大量的CPU资源引起系统故障。
# 开启gzip压缩功能
gzip on;
# 设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取。
# 默认值是0,表示不管页面多大都进行压缩。
# 如果本身内容很小进行压缩,那么压缩效率就很差,可能越压越大,得不偿失
gzip_min_length 1k;
# 压缩缓存区大小。表示申请4个单位的位16K的内存作为压缩结果流缓存
gzip_buffers 4 16k;
# 设置http请求只要要等于这个指定的HTTP协议版本才启用压缩功能,默认是1.1,
gzip_http_version 1.1;
# 压缩比率。用来指定gzip压缩比,1压缩比最小,处理速度最快;
# 9压缩比最大,传输速度快,但处理最慢,也比较消耗CPU资源
gzip_comp_level 2;
# 用来指定压缩的类型,一般对文本类型的内容进行压缩
# 图片、压缩包、视频不要开启压缩
gzip_types text/plain text/css text/xml application/javascript;
# 开启或者关闭"Vary: Accept-Encoding" 响应头
# nginx响应Vary: Accept-Encoding用来告诉缓存,Accept-Encoding头需要作为
# 缓存key的一部分
gzip_vary on;
另外补充一下,brotli压缩是2015年9月谷歌开源的一个压缩算法,其压缩率比gzip更高,如果浏览器支持的情况下,也可以考虑给nginx加载brotli压缩模块,提供更高效率的压缩能力。
2.10 多线程
从1.7.12版本开始,nginx提供了线程池能力。能够在读写文件的时候用多线程来避免阻塞worker进程的问题,特别是对I/O比较密集的情况下效果会比较好。多线程默认是不开启的,需要在编译的时候添加--with-threads选项来开启。配置举例如下:
location /video/ {
sendfile on;
aio threads;
}
以上配置nginx在进行sendfile时会将文件读取和发送的工作卸载到多线程中来避免阻塞worker进程,从而提升nginx的并发能力。
2.11 开启http2
相对于http/1.1协议,http2协议可以提供更好的并发和传输性能,通过开启http2协议,能够让有能力支持http2的浏览器通过http2来访问nginx,从而提升用户的体验。但是http2是依赖于ssl的,因此,需要提前准备好https证书和对应的私钥。开启http2的配置举例如下:
server {
listen 443 ssl http2;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_certificate cert.crt;
ssl_certificate_key cert.key;
....
}
2.12 安全
2.12.1 SSL优化
2.12.1.1 禁用老旧的ssl协议
现在最新的ssl协议已经更新到TLS v1.3了,建议不要再使用TLS v1.1及之前的版本了,配置指令举例如下:
ssl_protocols TLSv1.2 TLSv1.3;
2.12.1.2 禁止弱加密套件
密码套件是提供加密、认证和完整性的算法组合。为了保护数据传输安全,TLS/SSL使用一个或多个密码套件。这些密码套件在SSL/TLS协商过程中以及数据传输过程中使用。弱密码套件和过时的密码套件配置可能会给你的网站带来安全隐患,并使其容易受到攻击。如果你使用这些密码套件,攻击者可能会拦截或修改传输中的数据。 由于加密套件的应用是依赖于nginx和openssl的版本的,Mozilla SSL Configuration Generator 可以帮助我们来生成nginx的配置参数,通过这个工具生成的nginx ssl配置举例如下:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /path/to/signed_cert_plus_intermediates;
ssl_certificate_key /path/to/private_key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam /path/to/dhparam;
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;
# replace with the IP address of your resolver
resolver 127.0.0.1;
}
2.12.1.3 使用安全的密钥交换机制
DH参数的目的是允许交换一个密钥,用于在会话中加密消息的传输记录。临时DH提供前向安全性,意味着会话密钥在会话结束后被删除。因此,攻击者无法检索两方之间交换的消息,除非是最后一个会话。 用openssl命令生成一个至少2048bits的唯一DH组,例如:
openssl dhparam -out dhparams.pem 2048
然后在nginx中加载这个dhparams.pem文件:
ssl_dhparam ./ssl/dhparam.pem;
在客户端请求https网站的时候,需要对CA中间商的证书的有效性进行验证。如果客户端访问CA的域名被污染或者访问CA的服务器网络比较卡,会导致验证的时间会比较长,导致用户体验下降。通过nginx的ocsp stapling机制,nginx会去CA服务器进行ocsp查询并缓存结果,客户端在进行TLS连接握手的时候nginx就会把缓存的结果响应给客户端,避免客户端自己去请求验证。下面是nginx开启ocsp stapling功能的配置,举例如下:
# 开启 OCSP Stapling
ssl_stapling on;
# 启用nginx对OCSP响应的验证
ssl_stapling_verify on;
# 默认信任的ca根证书
ssl_trusted_certificate lestencrypt_root.cer;
# 添加resolver解析OSCP响应服务器的主机名,valid表示缓存。
resolver 114.114.114.114 223.5.5.5 valid=60s;
# DNS解析的超时时间
resolver_timeout 3s;
2.12.2 关闭服务器响应中的版本信息的显示
默认情况下,当客户端发送请求并获得Nginx服务器的响应时,响应头中会包含服务器的版本信息。这个版本信息可能包含敏感的细节,如Nginx的版本号和其他相关信息,这可能会增加服务器面临的潜在安全风险。配置如下;
server_token off;
通过使用"server_tokens off"指令,可以禁用服务器版本信息的显示。当该指令在Nginx配置文件中进行配置后,服务器响应的响应头中将不再包含版本信息。这样可以提高服务器的安全性,因为潜在的攻击者将无法直接获得关于服务器使用的软件版本的详细信息。
2.12.3 防ddos攻击
通过nginx自带的限流模块能够在一定程度上防止小规模ddos攻击流量。 包括,限制每个IP的最大并发连接数和每个IP的每秒最大请求数,同时配合限制客户端请求最大body大小超时时间等参数来防止ddos攻击。配置举例如下:
# 定义一个zone用于限制单个IP的并发连接数量
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
# 定义一个zone用于限制单个IP的每秒请求数,限制平均速率为5rps
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=5r/s;
# 在server级别上限制但IP最多10个并发连接
# 在server级别上限制单个个IP的每秒最大请求数为10
server {
limit_conn conn_limit_per_ip 10;
limit_req zone=req_limit_per_ip burst=10 nodelay;
}
# 限制客户端请求body部分的内存缓冲区大小,超过则写入临时文件
# request body is written into a temporary file
client_body_buffer_size 128k;
# 限制最大可以上传的body大小
client_max_body_size 10m;
# 限制客户端请求header部分的缓冲区大小
client_header_buffer_size 3m;
# 限制客户端请求头最大允许的缓冲区个数和每个缓冲区的大小
large_client_header_buffers 4 256k;
# 客户端请求body部分读超时时间
client_body_timeout 3m;
# 客户端请求header部分读超时时间
client_header_timeout 3m;
2.11.4 防止range请求攻击
HTTP 1.1协议允许在一个请求中指定任意个数的子range的请求。无限制的多重范围请求容易受到拒绝服务攻击的影响,因为请求许多重叠的数据范围所需的工作量与尝试为请求的数据的多个部分提供服务所消耗的时间、内存和带宽相比微不足道。服务器应该忽略、合并或拒绝过分的范围请求,例如请求超过两个重叠范围或在单个集合中请求许多小范围,特别是当这些范围的请求没有明显原因的无序时。多部分范围请求不适用于支持随机访问。 nginx的核心模块提供了max_ranges参数用于限制一个请求最多可以有多少个子range。配置举例如下:
http{
server{
max_ranges 2;
......
}
}
2.11.5 禁止部分HTTP method
大部分的http请求只要GET、HEAD、POST就足够了,TRACE, DELETE, PUT and OPTIONS等这些method一般来说都是有一定危险性的,因此如果确认没有这些method的需求,应该明确禁止这些method的请求,配置举例如下:
location / {
limit_except GET HEAD POST { deny all; }
}
以上配置禁止除了GET、HEAD、POST之外的method的请求。
为了避免网站的资源被盗用,可以在nginx上配置referer限制,限制只有白名单中的网站可以引用本网站的内容。配置举例如下:
location /path/to/resource {
valid_referers none blocked example.com;
if ($invalid_referer) {
return 403;
}
# 其他相关配置...
}
2.11.7 ua限制
为了保护服务器免受爬虫、脚本和其他自动化的网页获取方法的攻击,可以明确拒绝部分用户代理。例如,像wget这样的应用程序可以检索整个文档根目录结构,使其成为有用的拒绝服务(DoS)攻击者,或者仅仅是访问网站上的安全文件。譬如通过下述配置禁止curl和wget的http请求: