Nginx优化指南

这不是一个全面的指南。这是一个简单的预览。

Posted by     BY   杨晓明 on September 12, 2017

前言

大多数的Nginx安装指南都只是告诉我们,yum install nginx,修改这里或那里的几行配置,好了,你已经有了一个Web服务器了!当然,在大多数情况下,一个常规安装的nginx对于一般的网站来说已经能很好地工作了。如果真的想挤压出nginx的性能,我们必须更深入一些。如往常一样,本文结合了我自己的工作经验+网络上的资料(感谢他们–乐于分享的人)。那么开始吧!

1. 隐藏版本号与隐藏软件

在访问网站某些错误页面或服务器宕机时,会显示当前使用的nginx版本号,这给了不怀好意的人可乘之机,自然应该避免发生。

(1). 隐藏Nginx版本号 配置方法为:在nginx配置文件的http标签内加入server_tokens off参数,也可以放到server标签和location标签中,如下:

http {
    ......
    server_tokens off;
    ......
}

(2). 更改源码隐藏软件名称
从网上看到有人这么做。但是我认为,抛开开源的问题不谈,既然使用别人的软件,那么保留其Logo与名称是对作者的基本尊重吧,况且在没有版本号的前提下一个软件名称我想也不会带来什么安全问题。本文不提倡此做法,故也不提供修改方法。

2. worker进程用户优化

默认情况下,nginx服务启动,使用的用户和组默认都是nobody。使用专门的nginx用户更加安全。

配置方法为:在nginx.conf中(也可以编译安装时指定)最外层加入:

user nginx nginx;

3. 配置worker进程数

worker_processes参数决定了Nginx对外提供web服务时的worder进程数。最优值取决于许多因素,包括(但不限于)CPU核的数量、存储数据的硬盘数量及负载模式。不能确定的时候,将其设置为可用的CPU内核数是一个好的选择。

修改nginx.conf配置文件最外层:

worker_processes 4; #设置为“auto”将尝试自动检测

此外,还可以配置CPU亲和力,不同cpu设置如下: 四核cpu服务器:

worker_processes    4;  
worker_cpu_affinity 0001 0010 0100 1000;

八核cpu服务器:

worker_processes    8;
worker_cpu_affinity 0001 0010 0100 1000 0001 0010 0100 1000;

4. Nginx事件处理模型优化

nginx的连接处理机制在不同的操作系统上采用不用的IO模型,在linux下,nginx使用epoll的IO多路复用模型,在freebsd使用kqueue的IO多路复用模型,在solaris使用/dev/pool方式的IO多路复用模型,在windows使用的icop等等。根据系统类型不同选择不同的事务处理模型,选择有[ kqueue | rtsig |epool |dev/pool |select |pllo ]

我使用的是Centos6.9,因此将Nginx的事件处理模型调整为epool模型。在配置文件最外层添加:

use epoll;

5. 调整worker单个进程允许的客户端最大连接数

worker_connections参数的值根据服务器性能和程序的内存来指定(一个进程启动使用的内存根据程序确定),是单个进程的最大链接数

实际de 最大链接数是worker进程数乘以这个数。
Max_client=worker_processes*worker_connections

在配置文件最外层配置:

worker_connections 2048;

6. 配置worker进程最大打开文件数

相当于系统ulimit -HSn。配置参数不是越大越好,最好设为服务器承受的极限点。依然在配置文件最外层:

worker_rlimit_nofile 65535;

7. 使用高效的文件传输模式

tcp_nopush参数可以允许把http response header和文件的开始放在一个文件里发布,积极的作用是减少网络报文段的数量。
详见: Nginx官方文档
在http标签配置:

http {
    ......
    include mime.type;                      #媒体类型
    default_type application/octet-stream;  #默认媒体类
    sendfile        on;
    tcp_nopush      on;                      #只有在sendfile开启模式下有效
    ......
}

8. 设置连接超时时间

此参数保护服务器资源。建立连接也是要消耗资源的,我们一般断掉那些连接上但是不做事连接。php网站建议短连接,PHP程序建立连接消耗的资源和时间要少。JAVA网站建议长连接,JAVA程序建立连接消耗的资源和时间要多。 在http标签设置:

http {
    keepalive_timeout 60;
    #设置客户端连接保持会话的超时时间,超过这个时间,服务器会关闭该连接。
    tcp_nodelay on;
    #打开tcp_nodelay,在包含了keepalive参数才有效
    client_header_timeout 15;
    #设置客户端请求头读取超时时间,如果超过这个时间,客户端还没有发送任何数据,Nginx将返回“Request time out(408)”错误
    send_timeout 15
    #指定响应客户端的超时时间。这个超过仅限于两个连接活动之间的时间,如果超过这个时间,客户端没有任何活动,Nginx将会关闭连接。
}

9. 上传文件大小限制(动态应用)

nginx.conf中http字段添加如下参数,具体大小根据业务做调整。即http协议原理中请求报文的请求主体,此功能在php参数中还有设置。

http {
    client_max_body_size 10m;
}

10. fastcgi调优(配合PHP引擎动态服务)

fastcgi是静态服务和动态服务之间的一个接口。

(1). 参数详解:有的只能放在http标签

参数 说明
fastcgi_connect_timeout 300; 指定链接到后端FastCGI的超时时间
fastcgi_send_timeout 300; 向FastCGI传送请求的超时时间,这个值是指已经完成两次握手后向FastCGI传送请求的超时时间
fastcgi_read_timeout 300; 指定接收FastCGI应答的超时时间,这个值是指已经完成两次握手后接收FastCGI应答的超时时间
fastcgi_buffer_size 64k; 指定读取FastCGI应答第一部分需要用多大的缓冲区,这个值表示将使用1个64KB的缓冲区读取应答的第一部分(应答头),可以设置为gastcgi_buffers选项指定的缓冲区大小
fastcgi_buffers 4 64k; 指定本地需要用多少和多大的缓冲区来缓冲FastCGI的应答请求,如果一个php脚本所产生的页面大小为256KB,那么会分配4个64KB的缓冲区来缓存,如果页面大小大于256KB,那么大于256KB的部分会缓存到fastcgi_temp指定的路径中,但是这并不是好方法,因为内存中的数据处理速度要快于磁盘。一般这个值应该为站点中php脚本所产生的页面大小的中间值,如果站点大部分脚本所产生的页面大小为256KB,那么可以把这个值设置为“8 16K”、“4 64k”等
fastcgi_busy_buffers_size 128k; 建议设置为fastcgi_buffer的两倍,繁忙时候的buffer
fastcgi_temp_file_write_size 128k; 在写入fastcgi_temp_path时将用多大的数据库,默认值是fastcgi_buffers的两倍,设置上述数值设置小时若负载上来时可能报502 Bad Gateway
fastcgi_cache oldboy_ngnix; 表示开启FastCGI缓存并为其指定一个名称。开启缓存非常有用,可以有效降低CPU的负载,并且防止502的错误放生,但是开启缓存也可能会引起其他问题,要很据具体情况选择
fastcgi_cache_valid 200 302 1h; 用来指定应答代码的缓存时间,实例中的值表示将2000和302应答缓存一小时,要和fastcgi_cache配合使用
fastcgi_cache_valid 301 1d; 将301应答缓存一天
fastcgi_cache_valid any 1m; 将其他应答缓存为1分钟
fastcgi_cache_min_uses 1; 请求的数量
fastcgi_cache_path ; 定义缓存的路径

(2). 参数配置如下: 修改nginx.conf配置文件,在http标签中添加如下:

http {
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;
    #fastcgi_temp_path /data/ngx_fcgi_tmp;
    fastcgi_cache_path /data/ngx_fcgi_cache;
    levels=2:2
    keys_zone=ngx_fcgi_cache:512m
    inactive=1d max_size=40g;
}

缓存路径,levels目录层次2级,定义了一个存储区域名字,缓存大小,不活动的数据在缓存中多长时间,目录总大小.在server location标签添加如下:

server {
    ......
    location ~ .*\.(php|php5)?$ {
        ......
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi.conf;
        fastcgi_cache ngx_fcgi_cache;
        fastcgi_cache_valid 200 302 1h;
        fastcgi_cache_valid 301 1d;
        fastcgi_cache_valid any 1m;
        fastcgi_cache_min_uses 1;
        fastcgi_cache_use_stale error timeout invalid_header http_500;
        fastcgi_cache_key http://$host$request_uri;
        ......
    }
    ......
}

(3). fastcgi资料:

  • http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_cache
  • http://kb.cnblogs.com/page/96115/
  • http://www.linuxyan.com/web-server/78.html

11. 配置gzip压缩功能

(1). 优点

  • 节约带宽,省钱
  • 传输速度快,用户体验好

(2). 使用模块 nginx依赖ngx_http_gzip_module模块。apache使用的是mod_deflate压缩功能。

(3). 需要压缩的内容 纯文本(js,css,html),对于图片,视频,FLASH什么的不压缩,gzip_types参数控制,因为压缩占用cpu啊。

(4). 对应参数含义

参数 说明
gzip on; 开启压缩功能
gzip_min_length 1k; 设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取,默认值是0,不管页面多大都进行压缩,建议设置成大于1K,如果小与1K可能会越压越大
gzip_buffers 4 32k; 压缩缓冲区大小,表示申请4个单位为32K的内存作为压缩结果流缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果
gzip_http_version 1.1; 压缩版本(默认1.1,前端为squid2.5时使用1.0)用于设置识别HTTP协议版本,默认是1.1,目前大部分浏览器已经支持GZIP解压,使用默认即可
gzip_comp_level 9; 压缩比例,用来指定GZIP压缩比,1压缩比最小,处理速度最快,9压缩比最大,传输速度快,但是处理慢,也比较消耗CPU资源。
gzip_types text/css text/xml application/javascript; 用来指定压缩的类型,‘text/html’类型总是会被压缩
gzip_vary on; varyheader支持,改选项可以让前端的缓存服务器缓存经过GZIP压缩的页面,例如用Squid缓存经过nginx压缩的数据

(5). 具体配置 在http标签中配置:

http {
    gzip on;
    gzip_min_length  1k;
    gzip_buffers     4 32k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types  text/css text/xml  application/javascript;
    gzip_vary on;
}

12. 配置expires缓存功能

对于图片,CSS,JS等元素更改的机会较少,特别是图片,这时可以将图片设置在浏览器本地缓存365天或更长,CSS,JS,html等代码缓存10天,这样用户第一次打开页面后,会在本地缓存上述内容,提高了以后打开的页面加载速度,节省服务端大量贷款,此功能同apache的expires。这里通过location,将需要缓存的扩展名列出来,然后指定缓存时间。

expire功能优点

  • expires可以降低网站购买的贷款,节约成本
  • 同时提升用户访问体验
  • 减轻服务的压力,节约服务器成本,甚至可以节约人力成本,是web服务非常重要的功能。

expire功能缺点:

  • 被缓存的页面或数据更新了,用户看到的可能还是旧的内容,反而影响用户体验。解决办法:
  • 缩短缓存时间,例如:1天,不彻底,除非更新频率大于1天
  • 对缓存的对象改名

配置: (1). 根据文件扩展名进行判断,添加expires功能 在server字段添加
范例1:

server {
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
    expires      3650d;
}

范例2:

server {
    location ~ .*\.(js|css)?$ {
    expires      30d;
}

(2). 根据目录及其他进行判断,添加expires功能

13. nginx日志相关优化与安全

如同其他服务一样,日志切割必不可少。 (1) .配置日志切割脚本并写入计划任务

#!/bin/sh
cd /app/logs
mv www_access.log www_access_$(date +%F -d -1day).log
mv bbs_access.log bbs_access_$(date +%F -d -1day).log
mv blog_access.log blog_access_$(date +%F -d -1day).log
/application/nginx/sbin/nginx -s reload
cat >>/var/spool/cron/root<<EOF
00 00 * * * /bin/sh /server/scripts/cut_nginx_log.sh >/dev/null 2>&1

对于健康检查或某些(图片,js,css)日志,一般不记录日志,因为在统计PV时是按照页面计算,而且日志写入频繁会消耗磁盘IO,降低服务器性能。

(2). 不记录不需要的访问日志

location ~ .*\.(js|jpg|JPG|jpeg|JPEG|css|bmp|gif|GIF)$ {
    access_log off;
}

(3). 日志权限设置 不需要再日志目录给nginx用户读或者写许可。因为nginx的master进程是root,无需担心权限不够写不进去日志。假设日志目录为/app/logs,访问日志的权限设置:

chown -R root.root /app/logs
chmod -R 700 /app/logs

14. 站点目录及文件URL访问控制

主要用于防止恶意解析。

(1). 根据扩展名限制程序和文件访问 作用:禁止目录下指定文件被访问,或者禁止指定目录下所有内容被访问。
最佳应用场景:集群的共享存储,本来就应该只是资源文件,禁止指定扩展名程序被执行,例如:.php,.sh,.pl。

范例1:nginx配置限制指定目录下的php程序被解析,需要放在php解析location的下面

location ~ ^/images/.*\.(php|php5|.sh|.pl|.py)$ {
    deny all;
}
# Only allow these request methods 
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
    return 444;
}
location ~ ^/static/.*\.(php|php5|.sh|.pl|.py)$ {
    deny all;
}
location ~* ^/data/(attachment|avatar)/.*\.(php|php5)$ {
    deny all;
}

范例2:Nginx下配置禁止访问*.txt文件。

location ~* \.(txt|doc)$ {
    if (-f $request_filename) {
        root /data/www/www;
        #rewrite …..可以重定向到某个URL
        break;
    }
}
location ~* \.(txt|doc)$ {
    root /data/www/www;
    deny all;
}
location ~ ^/(static)/ {
    deny all;
}
location ~ ^/static {
    deny all;
}
location ~ ^/(static|js) {
    deny all;
}
location /admin/ { return 404;}
location /templates/ { return 403;}

(2). 限制来源ip访问 使用ngx_http_access_module限制ip访问>例如:phpmyadmin数据库web客户端,仅仅为内部开发人员使用。

范例1:禁止某目录让外界访问,但允许某ip访问改目录,且支持php解析。

location ~ ^/oldboy/ {
    allow 202.111.12.211;
    deny all;
}

范例2:限制及指定ip或ip段访问

location / {
    deny 192.168.1.1;
    allow 192.168.1.0/24;
    allow 10.1.1.0/16;
    deny all;
}

其他写法

if ( $remote_addr = 10.0.0.7 ) {
    return 403;
}

阅读更多:官网资料

案例3:nginx做反向代理的时候限制客户端IP(使用if来控制)

if ( $remote_addr = 192.168.1.31 ) {
    return 403;
}
if ( $remote_addr = 218.247.17.130 ) {
    set $allow_access_root 'true';
}

(3). 限制使用网站ip访问网站,防止别的域名被恶意解析到我们的IP。

  • 方法1:ngnix第一个虚拟主机设置403。(默认主机)
    server {
      listen 80 default_server;
      server_name _;
      return 403;
    }
    
  • 方法2:添加301跳转,也是第一个虚拟主机
    server {
      listen 80 default_server;
      server_name _;
      rewrite ^(.*) http://www.yxmblog.top/$1 permanent;
    }
    

    更多阅读:Nginx的所有变量

15. 配置nginx图片及目录防盗链

(1). 什么是盗链

盗链是指服务提供商自己不提供服务的内容,通过技术手段绕过其它有利益的最终用户界面(如广告),直接在自己的网站上向最终用户提供其它服务提供商的服务内容,骗取最终用户的浏览和点击率。受益者不提供资源或提供很少的资源,而真正的服务提供商却得不到任何的收益。 —维基百科

(2). 解决思路 最简单的办法就是判断URL的refer,如果不是特定地址,返回一个特定页面 。
HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理。利用referer并且针对扩展名rewrite重定向:

location ~* \.(jpg|gif|png|swf|flv|wma|wmv|asf|mp3|mmf|zip|rar)$ {
    valid_referers none blocked *.yxmblog.top;
    if ($invalid_referer) {
        rewrite ^/ http:/www.yxmblog.top/img/error.jpg;
    }
}

Tips:防盗链跳转的地址,不能再是设置防盗链的虚拟主机地址,要用第三个虚拟主机,要不就成死循环了!

说明:设定一个location,如果访问为指定的扩展名文件,则进行判断,如果不是来自指定的网站,则重写为错误图片返回。

# Stop deep linking or hot linking
location /images/ {
    valid_referers none blocked www.example.comexample.com;
    if ($invalid_referer) {
        return   403;
    }
}

16. 错误页面优雅显示

默认的错误页面总是不太优雅,如何优雅的实现呢?

server {
    listen       80;
    server_name  www.yxmblog.top;
    location / {
        root   html/www;
        index  index.html index.htm;
    }
    error_page  403  /403.html;             #此路径相对于root  html/www
    #error_page 404  http://www.google.com; #还可以使用url重定向
    #error_page 500  502 503 504  /50x.html;
}

附:状态码列表

17. 防爬虫优化

Robots协议也称为爬虫协议、爬虫规则、机器人协议,是网站国际互联网界通行的“道德规范”,其目的是保护网站数据和敏感信息、确保用户个人信息和隐私不被侵犯。“规则”中将搜索引擎抓取网站内容的范围做了约定,包括网站是否希望被搜索引擎抓取,哪些内容不允许被抓取,而网络爬虫可以据此自动抓取或者不抓取该网页内容。如果将网站视为酒店里的一个房间,robots.txt就是主人在房间门口悬挂的“请勿打扰”或“欢迎打扫”的提示牌。这个文件告诉来访的搜索引擎哪些房间可以进入和参观,哪些不对搜索引擎开放。 —智库百科

我们的某些站点或网页不希望被爬取,一般有以下两种方法:
方法1:根据$http_user_agent获取客户端agent,然后判断是否允许或者返回指定页面。添加如下内容可防止爬虫(网站的热度会受到影响):

if ($http_user_agent ~* "qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot")
{
    return 403;
}

方法2:网站更目录下增加Robots.txt,放在站点根目录下。 robots.txt是搜索引擎中访问网站的时候要查看的第一个文件。robots.txt文件告诉蜘蛛程序在服务器上什么文件是可以被查看的。

当一个爬虫访问一个站点时,它会首先检查该站点根目录下是否存在robots.txt,如果存在,搜索机器人就会按照该文件中的内容来确定访问的范围;如果该文件不存在,所有的爬虫将能够访问网站上所有没有被口令保护的页面。百度官方建议,仅当您的网站包含不希望被搜索引擎收录的内容时,才需要使用robots.txt文件。如果您希望搜索引擎收录网站上所有内容,请勿建立robots.txt文件。 Robots协议是国际互联网界通行的道德规范,基于以下原则建立:

  • 搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权;
  • 网站有义务保护其使用者的个人信息和隐私不被侵犯。

当然,如果搜索引擎不遵守约定的Robots协议,那么通过在网站下增加robots.txt也是不起作用的。(在正式环境中,可以适当允许搜索引擎抓取收录)

18. 防DOS攻击

控制单个ip的并发请求防止DOS攻击:使用limit_conn_zone参数进行控制,控制单个ip或域名的访问次数,限制连续访问。在http标签添加控制,可添加多个,在server或location中使用。

http {
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=perserver:10m;
    server {
        limit_conn perip 10;
        limit_conn perserver 100;
    }
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    server {
        location /download/ {
            limit_conn addr 1;
        }
    }
}

19. 为web服务增加用户身份验证(适合内部网址)

其实在这篇安装与配置Nginx中就有提到。主要应用场景:内部使用的网址,例如phpmyadmin客户端。
配置文件:

server{
    listen 80;
    server_name status.etiantian.org;
    location / {
        stub_status on;
        access_log off;
        auth_basic "login training";
        auth_basic_user_file /application/nginx/conf/htpasswd;
    }
}

创建密码文件:

htpasswd -cb /usr/local/nginx/conf/htpasswd xmy 123456
chmod 400 /usr/local/nginx/conf/htpasswd
chown nginx /usr/local/nginx/conf/htpasswd

后记

有了上述19条优化,一般来说已经很不错。当然还是那句话»优化无止境«。欢迎留言评论,指出文章错误与建议。(不过评论框貌似国内不科学上网的话加载不出来…万恶的GFW!)

参考资料

Nginx优化指南—[译者]:Garfielt, yale8848, tnjin
Nginx防爬虫处理—席飞剑