Linux 运维手册之 NGINX 编译及优化

 Technique  comment

NGINX 的优化是网站体验中重要的一环,在机器性能固定的情况下,参数优化可提高网站加载速度,提升用户体验。

编译部分

基于系统及配置的优化受限于很多条件限制,比如内核版本、软件版本等,因而可以在编译时进行优化,优化更加底层,效果更加明显。

小贴士:若无特殊组件需求及对性能的极致要求,不推荐进行编译安装,强烈建议使用包管理进行安装。

基础参数

使用官方仓库进行安装后可看到官方的默认编译参数

root@domain:~# nginx -V
nginx version: nginx/1.14.2
built by gcc 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) 
built with OpenSSL 1.1.0f  25 May 2017 (running with OpenSSL 1.1.1b  26 Feb 2019)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.14.2/debian/debuild-base/nginx-1.14.2=. -specs=/usr/share/dpkg/no-pie-compile.specs -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-specs=/usr/share/dpkg/no-pie-link.specs -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'
[root@domain ~]# nginx -V
nginx version: nginx/1.14.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC) 
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'

小贴士:若在其他发行版上进行编译,建议先安装其官方版本后在其参数基础上进行修改后再进行编译操作。

附加组件

$ wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1.tar.gz

小贴士:下述 Lua 、 LuaJIT 等组件就是基于此组件开发的,因此属于基础依赖组件。

$ wget https://github.com/openresty/lua-nginx-module/archive/v0.10.15.tar.gz
$ wget https://luajit.org/download/LuaJIT-2.0.5.tar.gz

小贴士:此组件年久失修,不再推荐使用。

与常见的通用压缩算法不同,Brotli使用一个预定义的120千字节字典。该字典包含超过13000个常用单词、短语和其他子字符串,这些来自一个文本和HTML文档的大型语料库。预定义的算法可以提升较小文件的压缩密度。

使用 Brotli 替换 Deflate 来对文本文件压缩通常可以增加20%的压缩比,而压缩与解压缩速度则大致不变。

Brotli is a generic-purpose lossless compression algorithm that compresses data using a combination of a modern variant of the LZ77 algorithm, Huffman coding and 2nd order context modeling, with a compression ratio comparable to the best currently available general-purpose compression methods. It is similar in speed with deflate but offers more dense compression.

# git clone https://github.com/google/ngx_brotli.git
# cd ngx_brotli && git submodule update --init

小贴士:旧版本依赖的部分库文件需要优先进行手动编译,新版本已经无需手动编译 libbrotli 库文件。

# wget -c  https://github.com/openssl/openssl/archive/OpenSSL_1_1_1.tar.gz
# tar xzf OpenSSL_1_1_1.tar.gz
# mv openssl-OpenSSL_1_1_1 openssl

小贴士:为了支持 TLSv1.3 需要使用 OpenSSL v1.1.1 版。

# wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.7/gperftools-2.7.tar.gz

构建环境

编译 LuaJIT

$ tar xf LuaJIT-2.0.5.tar.gz
$ cd LuaJIT-2.0.5
$ make && sudo make install

编译 Libunwind

$ wget http://download.savannah.nongnu.org/releases/libunwind/libunwind-1.3.1.tar.gz
$ tar zxf libunwind-1.3.1.tar.gz
$ cd libunwind-1.3.1
$ CFLAGS=-fPIC ./configure
$ make CFLAGS=-fPIC
$ sudo make CFLAGS=-fPIC install

编译 tcmalloc

$ tar xf gperftools-2.7.tar.gz
$ cd gperftools-2.7
$ ./configure
$ make -j2 && sudo make install
$ sudo echo '/usr/local/lib' > /etc/ld.so.conf.d/local.conf
$ sudo ldconfig 

小贴士:若已经编译安装 libunwind ,则无需安装依赖,否则请安装 libunwind-devel(CentOS/RadHat)或 libunwind-dev(Debian/Ubuntu)。

编译 PageSpeed

NPS_VERSION=1.13.35.2-stable
cd
wget https://github.com/apache/incubator-pagespeed-ngx/archive/v${NPS_VERSION}.zip
unzip v${NPS_VERSION}.zip
nps_dir=$(find . -name "*pagespeed-ngx-${NPS_VERSION}" -type d)
cd "$nps_dir"
NPS_RELEASE_NUMBER=${NPS_VERSION/beta/}
NPS_RELEASE_NUMBER=${NPS_VERSION/stable/}
psol_url=https://dl.google.com/dl/page-speed/psol/${NPS_RELEASE_NUMBER}.tar.gz
[ -e scripts/format_binary_url.sh ] && psol_url=$(scripts/format_binary_url.sh PSOL_BINARY_URL)
wget ${psol_url}
tar -xzvf $(basename ${psol_url})  # extracts to psol/

完整参数

# ./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib64/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx \
--group=nginx \
--with-compat \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-openssl=../openssl \
--with-openssl-opt='enable-tls1_3 enable-weak-ssl-ciphers' \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module \
--with-mail --with-mail_ssl_module \
--with-http_xslt_module=dynamic \
--with-http_image_filter_module=dynamic \
--with-http_geoip_module=dynamic \
--with-http_perl_module=dynamic \
--with-google_perftools_module \
--with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' \
--with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' \
--add-dynamic-module=../ngx_devel_kit/ \
--add-dynamic-module=../ngx_lua_module/ \
--add-dynamic-module=../ngx_brotli \
--add-dynamic-module=../ngx_pagespeed

注意:enable-tls1_3 是让 OpenSSL 支持 TLS 1.3 的关键选项;而 enable-weak-ssl-ciphers 的作用是继续支持 3DES 等不安全的加密套件,如果准备支持 IE8,才需要加上这个选项。

其中的 --add-dynamic-module 参数表示将组件编译为动态组件,动态组件需要自行在配置文件中进行调用,否则请使用 --add-module 参数。参考格式如下:

load_module "modules/ngx_pagespeed.so";

配置变更

ssl_protocols              TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers                TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-128-CCM-8-SHA256:TLS13-AES-128-CCM-SHA256:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;

小贴士:如果你打算继续支持 IE8,可以去掉包含 3DES 的项目,建议仅支持 TLSv1.2 和 TLSv1.3 即可。

pid        /run/nginx.pid;
google_perftools_profiles /tmp/tcmalloc;

小贴士:使用 TCMalloc ,使用 lsof -n | grep tcmalloc 即可验证。

load_module  modules/ndk_http_module.so;
load_module  modules/ngx_http_lua_module.so;

小贴士:加载两个 Lua 支持额外模块。

    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;

小贴士:添加位置和参数功能与 gzip 一致。检查是否生效可打开网页,用浏览器开发者工具调试,在 Network 选项中,检查是否有 content-encoding:br 头信息。

编译问题

若出现以下报错

./configure: error: the GeoIP module requires the GeoIP library.
You can either do not enable the module or install the library.

解决

# yum install geoip-devel   # CentOS/RadHat
# apt install libgeoip-dev   # Debian/Ubuntu

若出现以下报错

./configure: error: perl module ExtUtils::Embed is required

解决

# yum install perl-ExtUtils-Embed  # CentOS/RadHat

若出现以下报错

/usr/bin/ld: cannot find -lperl
collect2: error: ld returned 1 exit status
objs/Makefile:2080: recipe for target 'objs/ngx_http_perl_module.so' failed
make[1]: *** [objs/ngx_http_perl_module.so] Error 1
make[1]: Leaving directory '/usr/local/src/nginx-1.15.9'
Makefile:8: recipe for target 'build' failed
make: *** [build] Error 2

解决

# apt install libperl-dev          # Debian/Ubuntu

若出现以下报错

./configure: error: the HTTP image filter module requires the GD library.
You can either do not enable the module or install the libraries.

解决

# yum install gd gd-devel   # CentOS/RadHat
# apt install libgd-dev     # Debian/Ubuntu

若出现以下报错

./configure: error: the HTTP XSLT module requires the libxml2/libxslt
libraries. You can either do not enable the module or install the libraries.

解决

# yum install libxslt-devel  # CentOS/RadHat
# apt install libxslt-dev  # Debian/Ubuntu

若出现以下报错

./configure: error: SSL modules require the OpenSSL library.
You can either do not enable the modules, or install the OpenSSL library
into the system, or build the OpenSSL library statically from the source
with nginx by using --with-openssl=<path> option.

解决

# yum install openssl-devel       # CentOS/RadHat
# apt install openssl libssl-dev   # Debian/Ubuntu

若出现以下报错

./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.

解决

# yum install pcre-devel  # CentOS/RadHat
# apt install libpcre3 libpcre3-dev # Debian/Ubuntu

若出现以下报错

./configure: error: You need a C++ compiler for C++ support.

解决

# yum groups install "Development Tools"  # CentOS/RadHat
# apt install build-essential # Debian/Ubuntu

若出现以下报错

./configure: error: the HTTP gzip module requires the zlib library.
You can either disable the module by using --without-http_gzip_module
option, or install the zlib library into the system, or build the zlib library
statically from the source with nginx by using --with-zlib=<path> option.

解决办法

# yum install zlib zlib-devel  # CentOS/RadHat
# apt install zlib1g-dev       # Debian/Ubuntu

若出现以下报错

./configure: error: module ngx_pagespeed requires the pagespeed optimization library.
Look in /usr/local/src/nginx-1.15.12/objs/autoconf.err for more details.

解决办法

# apt-get install build-essential zlib1g-dev libpcre3 libpcre3-dev unzip uuid-dev  # Debian/Ubuntu

配置问题

若出现以下报错

# nginx -t
nginx: [emerg] dlopen() "/etc/nginx/modules/ngx_http_lua_module.so" failed (libluajit-5.1.so.2: cannot open shared object file: No such file or directory) in /etc/nginx/nginx.conf:10
nginx: configuration file /etc/nginx/nginx.conf test failed

解决

# ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2

若出现以下报错

# nginx -t
nginx: error while loading shared libraries: libbrotlienc.so.1: cannot open shared object file: No such file or directory

解决

# 64 bit
$ ln -s /usr/local/lib/libbrotlienc.so.1 /lib64/
# 32 bit
$ ln -s /usr/local/lib/libbrotlienc.so.1 /lib/

优化部分

系统优化

文件句柄数限制了系统打开文件的最大限制,在访问用户多的情况下就会导致可用资源枯竭,因此需要优化。

# vi /etc/security/limits.conf

添加以下两行(添加至文件末尾)

* soft nofile 51200
* hard nofile 51200

配置优化

优化进程

NGINX 启动时会启动一个主进程(master)和若干个工作进程(worker),在高并发的情形下,工作进程数量(worker_processes)不足会导致吞吐量不足,以便保证响应大量用户的请求,一般情况下此数值推荐设置等于系统的处理器核心数。

Linux 查看 CPU 个数及总核数

# grep 'processor' /proc/cpuinfo | wc -l
worker_processes auto;

小贴士:此参数推荐改为 auto,自动检测系统核心数量。

处理器绑定,可将某个进程绑定至某个核心,可以更高效得完成任务,提高硬件资源的利用率。

worker_cpu_affinity auto;

小贴士:此参数推荐改为 auto,自动检测系统核心并进行绑定。

例子:

双核 CPU 推荐配置:

worker_processes    2;
worker_cpu_affinity 0101 1010;

四核 CPU 推荐配置:

worker_processes    4;
worker_cpu_affinity 0001 0010 0100 1000;

优化连接数

调整单个工作进程的允许连接数

events {
    worker_connections  1024;
}

小贴士:理论总并发数 = 工作进程数 * 单进程允许连接数 (此数据受机器的硬件限制,仅供参考)

优化文件读写

对于需要大量文件读写操作(主要是写入)的页面推荐使用 sendfile 参数,系统调用在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了数据在内核缓冲区和用户缓冲区之间的拷贝,操作效率很高,被称之为零拷贝。此功能可参考链接中的 Linux 中的零拷贝技术。

方式 简洁流程 详细流程
Read/Write 硬盘—>内核缓冲区—>用户缓冲区—>内核Socket缓冲区—>协议引擎 调用 read 函数,文件数据拷贝到内核缓冲区;read 函数返回,数据从内核缓冲区拷贝到用户缓冲区调用 write/send 函数,将数据从用户冲区拷贝到内核 socket 缓冲区数据从内核 socket 缓冲区拷贝到协议引擎中
Sendfile 硬盘—>内核缓冲区—>内核socket缓冲区—>协议引擎 sendfile系统调用利用DMA引擎将文件数据拷贝到内核缓冲区,之后数据被拷贝到内核socket缓冲区中,DMA引擎将数据从内核socket缓冲区拷贝到协议引擎中

小贴士:如上表格,可见 sendfile 没有用户态和内核态之间的切换,也没有内核缓冲区和用户缓冲区之间的拷贝,大大提升了传输性能。

实例配置

location /video/ {
    sendfile       on;
    tcp_nopush     on;
    aio            on;
}

小贴士:此部分参数可优化磁盘读写,防止出现阻塞,sendfile 参数推荐与 tcp_nopush 参数一起使用;tcp_nopush 可在 UNIX 上使用 TCP_NOPUSH 套接字选项或在 Linux 上使用 TCP_CORK 套接字选项。仅在使用 sendfile 时才可启用,启用该选项允许在 UNIX/Linux 中发送响应头和文件的开头,然后以完整数据包发送文件,可降低响应时间;aio 此选项可在 FreeBSD 和 Linux 上使用异步文件 I/O(AIO)方式。

优化资源传输

使用 Gzip 压缩参数可以压缩资源,节约传输流量及带宽。

小贴士:仅推荐在无 Brotli 等更优秀的压缩算法时启用。

实例配置

    location ~ .*\.(jpg|gif|png)$ {
        gzip              on;
        gzip_http_version 1.1;
        gzip_comp_level   2;
        gzip_types        text/plain application/json application/x-javascript application/css application/xml application/xml+rss text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        root /static/img;
参数 说明 默认 可用位置
gzip 是否启用数据压缩 off http, server, location, if in location
gzip_comp_level 数据压缩比率(等级),等级越高,压缩率越高,相应地消耗处理器资源也越高 1 http, server, location
gzip_http_version 使用压缩的协议 1.1 http, server, location

小贴士:不推荐在压缩比低的资源(比如常见视频格式及大部分有损图片格式)中使用压缩功能,节约的带宽有限,但是会明显提高系统资源的占用,得不偿失。

优化后端连接

NGINX 与后端程序如 PHP 可以使用 TCP/IP 方式连接或 UDS 方式连接。

连接方式 网络通信 缺点
TCP/IP 因走网络栈,会占用网卡,且性能较差
UDS(Unix Domain Socket) 高并发情况下不稳定,但受限于文件句柄

小贴士:因 Unix Domain Socket 实质上为文件,因此推荐将其生成在 /dev/shm/ 目录中(此目录为内存映射目录,文件实质会存在高速内存中),修改 PHP-FPM 的配置文件即可,注意每个请求都会请求此文件,因此该方式受限于系统文件句柄数限制。

在高并发情况下,Socket 方式下会频繁报错。

connect() to unix:/dev/shm/php-fcgi.sock failed (11: Resource temporarily unavailable) while connecting to upstream

解决方式有两种:

调高 NGINX/PHP-FPM 的 backlog 数值,在 NGINX 配置中修改以下配置即可。

default backlog=1024

禁止使用 IP 访问

背景:

Q:为什么要禁止 IP 访问页面呢?
A:这样做是为了避免他人把未备案的域名解析到自身服务器 IP ,而导致服务器被断网,防止此类事情的发生。

方案:

1)在 server 段里插入正则,例如:

listen       80;
server_name  www.domain.com;
if ($host != 'www.domain.com') {
    return 403;
}

2)添加一个 server 主机,例如:

server {
    listen 80 default;
    server_name _;
    return 403;
}

在一般情况下使用方案二比较简单,若域名申请了证书并配置了 HTTPS ,请使用 方案一。且在 443 主机内也需要配置。

隐藏版本信息

一般来讲漏洞只针对特定版本或者某些版本,因此需要隐藏服务器版本号。

修改配置文件的 HTTP 段内加入

server_tokens  off;

附录

相关链接

参考链接

回复