nginx
Table of Contents
1. NGINX
NGNIX 是著名的 Web 服务器,常常还被用作“反向代理”、“负载均衡”和“HTTP 缓存”服务器。
nginx 由一个主进程(master process)和一个或多个工作进程(worker processe)组成。主进程负责读取配置文件和管理工作进程,而工作进程负责处理请求。nginx 采用事件驱动模型(如 select,poll,epoll,kqueue 等)来实现高性能。
1.1. 启动、停止、重新加载配置
要启动 nginx,执行 nginx 可执行程序即可。当 nginx 启动后,可通过 -s
参数来停止它或者让其重新加载配置。nginx 的命令参数参见表 1。
命令行参数 | 含义 |
---|---|
-?, -h | Print help. |
-v | Print version. |
-V | Print NGINX version, compiler version and configure parameters. |
-t | Don’t run, just test the configuration file. NGINX checks configuration for correct syntax and then try to open files referred in configuration. |
-q | Suppress non-error messages during configuration testing. |
-s signal | Send signal to a master process: stop, quit, reopen, reload. (version >= 0.7.53) |
-p prefix | Set prefix path (default: “/usr/local/nginx/”). (version >= 0.7.53) |
-c filename | Specify which configuration file NGINX should use instead of the default. |
-g directives | Set global directives. (version >= 0.7.4) |
下面是 nginx -g
参数的实例:
$ nginx -g "pid /var/run/nginx.pid; worker_processes `sysctl -n hw.ncpu`;"
nginx -s
参数的含义如表 2 所示。
命令 | 含义 |
---|---|
nginx -s stop | fast shutdown |
nginx -s quit | graceful shutdown |
nginx -s reload | reloading the configuration file |
nginx -s reopen | reopening the log files |
2. NGINX 配置文件
NGIINX 默认的配置文件名为“nginx.conf”,默认查找配置文件的目录为“/usr/local/nginx/conf/”、“/etc/nginx/”或者“/usr/local/etc/nginx/”。
2.1. 配置文件基本格式
NGINX 的配置文件由多个“section”组成,每个“section”的基本格式为:
<section> { <directive1> <parameters1>; <directive2> <parameters2>; }
说明 1:全局的设置一般放在所有其它“section”的前面(即配置文件开始位置),且不用 {}
包围,后面将介绍。
说明 2:相同的指令可以出现在不同的 section 中(如 include 指令可以出现在任何 section 中)。在官方文档中,每个指令的“Context”属性说明了当前指令可用在哪些 section 中。如 error_log 指令可以出现在“main/http/mail/stream/server/location”section 中:
Syntax: error_log file [level]; Default: error_log logs/error.log error; Context: main, http, mail, stream, server, location
参考:
NGINX 所有指令说明:Alphabetical index of directives
2.1.1. nginx.conf 实例
下面是 nginx.conf 的一个实例:
user www www; worker_processes 2; pid /var/run/nginx.pid; # [ debug | info | notice | warn | error | crit ] error_log /var/log/nginx.error_log info; events { worker_connections 2000; # use [ kqueue | epoll | /dev/poll | select | poll ]; use kqueue; } http { include conf/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$gzip_ratio"'; log_format download '$remote_addr - $remote_user [$time_local] ' '"$request" $status $bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$http_range" "$sent_http_content_range"'; client_header_timeout 3m; client_body_timeout 3m; send_timeout 3m; client_header_buffer_size 1k; large_client_header_buffers 4 4k; gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_types text/plain; output_buffers 1 32k; postpone_output 1460; sendfile on; tcp_nopush on; tcp_nodelay on; send_lowat 12000; keepalive_timeout 75 20; #lingering_time 30; #lingering_timeout 10; #reset_timedout_connection on; server { listen one.example.com; server_name one.example.com www.one.example.com; access_log /var/log/nginx.access_log main; location / { proxy_pass http://127.0.0.1/; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 10m; client_body_buffer_size 128k; client_body_temp_path /var/nginx/client_body_temp; proxy_connect_timeout 70; proxy_send_timeout 90; proxy_read_timeout 90; proxy_send_lowat 12000; proxy_buffer_size 4k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k; proxy_temp_path /var/nginx/proxy_temp; charset koi8-r; } error_page 404 /404.html; location /404.html { root /spool/www; } location /old_stuff/ { rewrite ^/old_stuff/(.*)$ /new_stuff/$1 permanent; } location /download/ { valid_referers none blocked server_names *.example.com; if ($invalid_referer) { #rewrite ^/ http://www.example.com/; return 403; } #rewrite_log on; # rewrite /download/*/mp3/*.any_ext to /download/*/mp3/*.mp3 rewrite ^/(download/.*)/mp3/(.*)\..*$ /$1/mp3/$2.mp3 break; root /spool/www; #autoindex on; access_log /var/log/nginx-download.access_log download; } location ~* \.(jpg|jpeg|gif)$ { root /spool/www; access_log off; expires 30d; } } }
2.2. main 模块和 events 模块
main 模块和 events 模块都是全局有效的。下面是配置例子:
user nginx; # 运行Nginx的用户 worker_processes 4; # nginx工作进程数,建议设置为CPU总核心数 error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; # pid文件路径 events { use epoll; # 指定事件模型,select|poll|kqueue|epoll|/dev/poll,参考http://nginx.org/en/docs/events.html worker_connections 1024; # 1个工作进程最大的连接数 }
2.3. http 模块下多个 server 的定位
假设有下面配置:
http { server { listen 80; server_name example.org www.example.org; ... } server { listen 80; server_name example.net www.example.net; ... } server { listen 80; server_name example.com www.example.com; ... } }
这 3 个 server 都监听在 80 端口,将通过 server_name 来定位配置。当请求数据的 HTTP Header 的 Host 字段为 example.org 或者 www.example.org 时,将使用第 1 个 server 配置;当 Host 字段为 example.net 或者 www.example.net 时,将使用第 2 个 server 配置;当 Host 字段为 example.com 或者 www.example.com 时,将使用第 3 个 server 配置。
如果请求数据的 HTTP Header 的 Host 字段为上面 6 个域名以外的情况(比如是另一个域名,或者是 IP,或者根本都没有 Host 字段等情况时),nginx 将使用 default server,而默认第 1 个 server 就是 default server。
当然,我们也可以显式地定义一个 default server,方法是在 listen 指令后面加上 default_server,如:
http { server { listen 80; server_name example.org www.example.org; ... } server { listen 80; server_name example.net www.example.net; ... } server { listen 80; server_name example.com www.example.com; ... } server { listen 80 default_server; # 如果 Host 和前面几个 server 中的 server_name 无法匹配,则会使用这个配置 server_name _; return 403; # 403 forbidden } }
2.4. 负载均衡策略
负载均衡模块用于从“upstream”指令定义的后端服务器列表中选取一台服务器。
Nginx 默认支持下面负载均衡策略:
- 加权轮询(默认),依次把请求发送给不同服务器,每个服务器的权值值默认为 1(weight=1)。
- ip_hash,按客户端 ip 的 hash 结果分配服务器,来自同一个 ip 的客户端固定访问一个后端服务器,这可以解决动态网页存在的 session 共享问题。
- hash:基于用户定义键的哈希。
- least_conn,最少活动连接,该机制把请求转发给连接数较少的后端服务器。
- least_time,最少平均响应时间。
2.4.1. Http 反向代理实例(加权轮询策略)
下面是 Http 反向代理实例,它采用默认的“加权轮询”策略,所有 Http 请求会依次发送给“192.168.0.101:8080, 192.168.0.102:8080, 192.168.0.103:8080”三台服务器,但“192.168.0.101:8080”具有更高的优先级(它的 weight 值为 2)。
user nginx; worker_processes 4; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { upstream app { ## 把下行取消注释即可使用 ip_hash 负载均衡策略 ## ip_hash; ## 把下行取消注释即可使用 least_conn 负载均衡策略 ## least_conn; server 192.168.0.101:8080 weight=2; server 192.168.0.102:8080; server 192.168.0.103:8080; } server { listen 80; location / { proxy_pass http://app; } } }
2.5. location 指令
location 指令用于设置对不同的 URI 使用不同的配置,它的基本语法为:
Syntax: location [ = | ~ | ~* | ^~ ] uri { ... } location @name { ... } Default: — Context: server, location
location 后可接下面修饰符:
= 表示精确匹配,具有最高的优先级;
~* 表示不区分大小写的正则匹配;
~ 表示区分大小写的正则匹配;
^~ 表示常规的匹配,匹配符合以后,停止往下搜索正则。
假设有下面配置:
server { listen 8080; location = / { # [ configuration A ] return 200 'config A\n'; } location / { # [ configuration B ] return 200 'config B\n'; } location /documents/ { # [ configuration C ] return 200 'config C\n'; } location ^~ /images/ { # [ configuration D ] return 200 'config D\n'; } location ~* \.(gif|jpg|jpeg)$ { # [ configuration E ] return 200 'config E\n'; } location = /images/abc.jpg { # [ configuration F ] return 200 'config F\n'; } }
下面是一些请求 URL 和会匹配的配置:
# URI # 会匹配的配置 / configuration A /index.html configuration B /documents/document.html configuration C /images/1.gif configuration D(不使用configuration E,因为 ^~ 比正则优先级高) /documents/1.jpg configuration E /images/abc.jpg configuration F(精确匹配,具有最高的优先级)
参考:http://nginx.org/en/docs/http/ngx_http_core_module.html#location
2.5.1. location 和 proxy_pass
location 指令中可以包含 proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, memcached_pass, grpc_pass 等指令,表示把请求相应的其它服务器处理。
下面介绍 location 和 proxy_pass 结合使用的规则,可分为两种情况。
情况一:proxy_pass 的 URL 中除 hostname 和 port 外,还包含路径部分(即下面例子中的 remote)。
location /name/ { proxy_pass http://127.0.0.1/remote/; }
这时,location 中匹配的那部分 path 会被删除,替换为 proxy_pass 中的 path。比如,原始请求 /name/other/path
,会变为“http://127.0.0.1/remote/other/path” (注意:原始请求中的 name 变为了 remote)。
情况二:proxy_pass 的 URL 中只有 hostname 和 port(即不包含路径部分)。
location /some/path/ { proxy_pass http://127.0.0.1; }
这时,location 中 path 原封不动地接到 proxy_pass 的 hostname 和 port 后面。比如,原始请求 /some/path/etc
,会变为“http://127.0.0.1/some/path/etc”。
参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
2.6. https 证书配置
要配置 https 证书,设置下面 3 个 ssl 相关的指令即可:
server { listen 443; server_name www.example.com; ssl on; ssl_certificate www.example.com.crt; ssl_certificate_key www.example.com.key; ... }
还有一种方式:不使用 ssl on
指令,在 listen 指令的端口后面加上 ssl
关键字即可:
server { listen 443 ssl; server_name www.example.com; ssl_certificate www.example.com.crt; ssl_certificate_key www.example.com.key; ... }
参考:http://nginx.org/en/docs/http/configuring_https_servers.html
2.7. 限流(ngx_http_limit_req_module)
每个 API 都有访问上限,为了防止过大的访问频率导致系统不可用,我们往往需要对 API 访问进行限流。 常见的限流算法有漏桶算法(Leaky Bucket)和令牌桶算法(Token Bucket)。 这两者的主要区别在于“漏桶算法”能够强行限制数据的传输速率;而“令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的阈值,它更适合于具有突发特性的流量。
NGINX 的模块 ngx_http_limit_req_module 实现了“漏桶算法”,可以用来对 API 访问进行限流。
2.7.1. 限流实例
下面是对“/search/”访问进行限流的例子:
http { limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; ... server { ... location /search/ { limit_req zone=one burst=5; } ...
限流主要由指令 limit_req_zone 和 limit_req 进行配置,它有两个重要配置(rate 和 burst),后面将对其进行说明。
2.7.1.1. limit_req_zone
limit_req_zone 的语法为:
limit_req_zone $variable zone=name:size rate=rate;
它的作用是设置一块共享内存域(NGINX 所有工作进程可访问的内存区域)用来保存键值的状态参数。特别是保存了当前超出请求的数量。键值就是指定的变量“$variable”。如:
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
例子中,键值是客户端 IP;区域的名称为 one(名称可以随便改,使用指令 limit_req 时需要指定区域名称),内存区域大小为 10m,平均处理的请求频率不能超过每秒一次。
注 1:使用$binary_remote_addr 变量(而不是变量$remote_addr 变量)作为键值,可以将每条状态记录的大小减少到 64 个字节,这样 10m 内存可以保存更多的记录。
注 2:rate 可以设置为每秒处理请求数和每分钟处理请求数,其值必须是整数,如果你需要指定每秒处理少于 1 个请求,如 2 秒处理一个请求,可以写为“30r/m”。
2.7.1.2. limit_req
limit_req 的语法为:
limit_req zone=name [burst=number] [nodelay];
参数 zone 用于设置使用哪个配置区域来做限制,与上面 limit_req_zone 里的 name 对应。
参数 burst(爆发)的意思是设置一个大小为 number 的缓冲区当有大量请求过来时,超过了访问频次限制的请求可以先放到这个缓冲区内。
参数 nodelay,如果设置上,超过访问频次而且缓冲区也满了的时候就会马上返回 503,而不会等待;如果不设置,则所有请求会等待排队。
假设 limit_req_zone 中,配置了 rate=1r/s,则配置:
limit_req zone=one;
表示每秒钟严格地只能通过一个请求,多余的请求会等待。
而配置:
limit_req zone=one burst=5;
表示可以支持的最大爆发数为 5(每秒钟平均还是只能通过一个请求)。比如,某一秒钟突然来了 5 个请求,则这 5 个请求都可以被通过,但后面的 4 秒钟不会再通过请求了(有请求的话,会等待)。
3. NGINX Tips
3.1. 配置文件实例:静态资源服务器
如何使用 nginx 配置一个静态资源服务器呢?
增加一个配置文件(如“/etc/nginx/conf.d/test.conf”,或其它会加载的位置),内容如下:
server { listen 8000; root /home/user1/html; }
3.2. 统计返回 404 的请求数
如何从 access_log 中统计出返回 404 的请求数?
如果没有定制过 access_log 的格式,默认使用的是“combined”格式,它的定义如下:
log_format combined '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';
按照上面定义,返回的状态码位于“按空格分开字段”的第 9 个位置:
46.41.221.96 - - [12/Mar/2018:10:30:22 +0000] "GET /manager/html HTTP/1.1" 404 169 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:28.0) Gecko/20100101 Firefox/28.0" ^ ^ ^ ^ ^ ^ ^ ^ ^ | | | | | | | | | 1 2 3 4 5 6 7 8 9
我们使用 awk
可以过滤出状态码为 404 的行(第 9 个域为 404),使用 wc
统计行数即可:
$ awk '$9 ~ /404/' /path/to/access.log | wc -l 329
3.3. 按天生成 access log 文件
如何让 nginx 按天生成 access log 文件呢?
方法一:利用定时任务,把日志文件移动到新位置,再发送 USR1 信号让 nginx 重新打开日志文件(相当于执行 nginx -s reopen
)。
新建文件“/usr/local/nginx/logs/nginxLogRotate.sh”,内容如下:
#!/bin/bash LOGS_PATH=/usr/local/nginx/logs YESTERDAY=$(date -d "yesterday" +%Y-%m-%d) mv ${LOGS_PATH}/access.log ${LOGS_PATH}/access_${YESTERDAY}.log mv ${LOGS_PATH}/error.log ${LOGS_PATH}/error_${YESTERDAY}.log ## 向nginx主进程发送USR1信号。nginx收到USR1信号会重新打开日志文件 kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid)
在“/etc/crontab”中添加下面定时任务:
0 0 * * * root /usr/local/nginx/logs/nginxLogRotate.sh
上面条目表示,每天 00:00 以 root 身份执行脚本“/usr/local/nginx/logs/nginxLogRotate.sh”。
参考:https://blog.csdn.net/wangkai_123456/article/details/71056758
方法二(仅适用于 access_log 指令,不适用于 error_log 指令)。
在相应的 server block 中配置下面内容:
# 在内置变量$time_iso8601中(如2014-05-04T18:12:02+02:00),找出当前日期 if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") { set $year $1; set $month $2; set $day $3; } access_log /var/log/nginx/$year-$month-$day-access.log;
注意:上面方法仅适用于 access_log 指令,error_log 指令中不支持使用变量。
参考:https://www.cambus.net/log-rotation-directly-within-nginx-configuration-file/
3.4. 重定向 http 到 https
使用 return 可以把 http 请求重定向到 https,如:
server { listen 80; server_name my.domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl; server_name my.domain.com; [....] }
注:使用 rewrite 指令也可以实现相同的目标。
3.5. NGINX 实现 TCP/UDP 转发(stream)
要 NGINX 实现 TCP/UDP 转发,配置 stream
块即可,如:
# 四层负载均衡 stream { upstream app { server 192.168.0.100:8000; } # tcp server { listen 8000; proxy_pass app; } # udp server { listen 8000 udp; proxy_pass app; proxy_responses 0; } } # 七层负载均衡 http { upstream app { server 192.168.0.100:8080; server 192.168.0.101:8080; } server { listen 8080; location / { proxy_pass http://app; } } }
4. NGINX 调试
4.1. stat failed (13: permission denied)
Nginx 日志中出现下面错误:
stat() "/username/test/static/index.html" failed (13: permission denied)
这个问题很可能的原因是 Nginx 的 worker 进程对应用户没有 x
访问权限,可能通过下面命令来确认这个问题(假设用户名为 nginx):
$ sudo -u nginx stat /username/test/static/index.html stat: cannot stat '/username/test/static/index.html': Permission denied
解决办法是每层目录加上 x
权限,如:
chmod +x /username/ chmod +x /username/test/ chmod +x /username/test/static/
参考:https://stackoverflow.com/questions/25774999/nginx-stat-failed-13-permission-denied