Master Nginx读书笔记

一些模块

Nginx代码的强大和精巧很好的体现在其模块机制上,在编译的时候指定需要的模块开启指定的功能,避免了最终二进制文件的冗余,书的开始提到了一些模块的作用特此记录

—with-http_ssl_module:用以开启Nginx对于https模式的支持,需要有OpenSSL库的支持

—with-http_relip_module:书中给的解释是如果需要在http头中传递客户端的真是ip,那么需要启用这个模块,刚好最开始学习Nginx模块编程的时候有使用到这个例子特别说明一下必须要开启这个模块才可以在模块开发的时候从header中通过r->header_in.x_real_ip取到请求client的真实ip,否则只能拿到上游ip :)

—with-http_secure_link_module:该模块提供一种机制,讲一个哈希连接指向某个url(在使用云db的时候在登录提供的phpadmin界面时有使用到这个模块,防扫描)

—with-http_stub_status_module:启用这个模块后会手机Nginx自身的状态信息,输出的状态信息可以使用rrd等工具进行绘制

ps:使用–with可以指定和某些模块编译,相应的使用–witchout可以关闭某些默认编译的模块

Nginx 配置指南

Nginx基本的配置格式如下

{

;

} ps:Nginx的配置文件是自有格式,不是json

Nginx的一些常用全局配置

1
2
3
4
5
6
7
8
9
10
user www;#表示worker的用户
worker_process 12;#指定worker启动的数量一般是cpu数量,对于io密集型服务建议cpunum * (1.5 or 2)
error_log /var/log/Nginx/error.log level;#配置Nginx错误日志写入地址,当全局配置之后若其他section没有配置该项则使用全局,在Nginx模块开发中涉及到读配置文件会有各种merge_conf的参数,比较绕,第二个参数表明了日志等级,需要注意的是(debug/info/notice/warn/error/crit/alert/emerg)只有在编译Nginx的时候指定--with-debug才可以使用
#当初在调试一个上游服务过早断开的bug的时候使用过这个参数,会打出非常详尽的日志,当时在使用其他level的日志输出时返回的http code全部是200表示正常返回,但是明明就是没有好吗!都是只返回了一半,当把log leve改为debug之后,那满屏幕的日志甚至包含了内存的调试输出,终于找到那个问题请求其实是504,显示是上游服务异常,才定位到了问题
pid /var/run/Nginx.pid ;#存储Nginx pid文件的地址
events{
use /dev/poll ;#在不同操作系统会不同,这里主要是系统层面并行处理io的选项,因为记不清楚了,linux高校的应该是epoll,Nginx与Apache效率差距巨大的原因也在这里,异步阻塞io
worker_connections 2048;#一个work能够接受的最大并发连接数,可能需要修改系统参数 ulimit
}
}

Nginx配置支持西永include语法,并支持通配符,前提是include的文件格式正确,同时Nginx -t -c conf可以检查配置文件是否配置正确

Nginx http server 部分配置

client_body_buffer_size 这个变量直接影响了处理客户端请求时候分配的缓存大小,进而可能会影响性能,同时在配置Nginx lua模块取post data的时候如果这个字段设置的不对,那么是无法取到request_body这个字段的,链接

client_body_in_file_only 用于调试或者是进一步处理客户端请求体,会将客户端请求body强制写入文件

一般来说http配置紧跟在全局配置指令之后

1
2
3
http{
#...
}

server部分

default_server 定义请求header对应的服务,如果为空则会将不带Host的请求导入给该server,一般用于处理不带Host头的请求

1
2
3
4
5
> server {
> listen 80;
> return 444;
> }
>

支持使用通配符进行配置

如果在域名前面加上波浪号~例如:

server_name~^www\.example\.com$;

server_name~www(\d+)\.example\.(com)$;#该格式用以利用捕获,后续可以在配置中使用$1进行使用

对于server_name匹配虚拟服务器的优先顺序如下:

  1. 匹配ip地址和listen端口
  2. 将Host头作为一个字符串匹配server_name指令
  3. 将Host头字段与server_name指令值字符串开始部分进行匹配
  4. 将Host头字段与server_name指令个字符串尾部分开始进行匹配
  5. 将Host字段与server_name指令值进行正则表达式匹配
  6. 如果以上所有部分失败则将转向default_server
  7. 如果所有匹配失败并且不包含server_name那么转向第一个server的listen

因此推荐总是设置default_server用已处理不加Host的请求

location部分

location多用于进行服务器内部重定向使用,并且location是支持嵌套使用的,有如下两种定义形式

1
2
3
4
5
6
7
8
#1
location [modifier] uri{
...
}
#2
location @name {
...
}

需要注意的是location用以在server部分进行重定向,所以只能在server级别进行定义

modifier

= 使用精确匹配并停止搜索

~ 区分大小写的正则匹配

~* 不区分大小写的正则匹配

^~如果该location是最佳匹配,那么对于匹配这个location的字符串不再进行正则呸呸 注意注意注意,这不是一个正则表达式匹配,他的目的是为了优先于正则表达式进行匹配

当一个请求进入时,会先检测uri匹配一个最佳的location

  1. 没有正则表达式的location会被作为最佳匹配,独立于含有正则表达式的匹配
  2. 在配置文件中按照查找顺序进行正则表达式匹配,查找到第一个正则表达式进行处理
验证

考虑如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server{
listen 88;

location / {
return 448;
}
location ~ ^/loca.* {
return 447;
}
location ~ ^/loc.* {
return 446;
}
location ^~ /locall {
return 445;
}
location = /locall {
return 449;
}
location = /local {
return 443;
}
}
1
2
3
4
5
6
wget http://192.168.31.14:88/locall # 449  最精确匹配
wget http://192.168.31.14:88/local #443 因为是最精确匹配
wget http://192.168.31.14:88/localllll #445 以开头匹配
wget http://192.168.31.14:88/locblllll #446 正则匹配
wget http://192.168.31.14:88/lllll #448 没有匹配到因此落到 /
wget http://192.168.31.14:88/locaddddd #447 正则匹配且因为顺序在446之前

mail部分先跳过了

暂时空,后续可以利用联系搭建自己的邮件服务器

Nginx作为反向代理

配置样例

1
2
3
location /uri {
proxy_pass http://localhost:999/urlnew;
}

需要注意的是,当location中包含有正则匹配时会有如下两个问题

1
2
3
4
5
6
7
8
location ~ ^/local {
proxy_pass http://localhost:8080/foreign;
#这是错误的,需要把后面的/foreign去掉
}
#如果想要在location中使用正则同时在proxy_pass中使用uri,那么必须在location对正则部分进行匹配并在proxy_pass部分进行使用
location ~ ^/test/(.*) {
proxy_pass http://localhost:8080/tttt/$1
}#这样是正确的 :)

以上配置在进行Nginx -t的时候是不会测试通过的会报错“proxy_pass” cannot have URI part in location given by regular expression, or inside named location, or inside “if” statement, or inside “limit_except” block in,开始有一些不理解,后来找了一下资料找到了:

It tells you that the URI in the proxy pass directive can’t be used in a regex location. This is because Nginx can’t replace the part of the URI matching the regex in the location block with the one passed in the proxy_pass directive a generic way.

Simply imagine your location regex is /foo/(.*)/bar, and you specify proxy_pass http://server/test, Nginx would have to map your location regex to another one on an upper level because you don’t want to end with /foo/test/bar/something but with /test/something. So that’s not possible natively.

s上面这段话是重点,如果允许在proxy中正则匹配并转发给某个uri是有可能涉及到其中的逻辑的改变发生意想不到的情况,例如如果有location ~ /foo/(.*)/bar proxy_pass http://server/test 那么在访问http://server/foo/test/bar的时候会给重定向到/http://server/test/bar ,这是很怪异的行为/

So for this part using the following should work :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {

[ ... ]

location ~ ^/smx/(test|production) {
rewrite ^/smx/(?:test|production)/(.*)$ /cxf/$1 break;
proxy_pass http://localhost:8181;
}

location ~ ^/es/(test|production) {
rewrite ^/es/(?:test|production)/(.*)$ /$1 break;
proxy_pass http://localhost:9200;
}

}

However, it won’t be possible to rewrite redirects to match the location block URI pattern because it rewrites the current URI being processed, making it impossible to change the Location header based on the initial request before rewrite.

同样的

1
2
3
4
5
location / {
rewrite /(.*)$ /index.php?page=$1 break;
proxy_pass http://localhost:8080/index;
#这样不会报错,但是访问的话最终的/index是不生效的,所以其实正确的是以8080结尾
}

Nginx代理模块

一些关于超时的配置就不在这里写了,都一样的connect,send,write

proxy_cookie_domain 可以用于替代上游服务器传来的Set-Cookie头中的domain

proxy_cookie_path 同上,只不过替代的是path

proxy_ignore_client_abort 设置为on那么当客户端断开连接后并不会关闭Nginx与上游服务器的连接,为off的情况下客户端断开连接之后Nginx日志中都不会有,因为Nginx只会记录完整的请求日志

proxy_redirect 重写上游服务器的location和refresh,对于某些应用有效,但是一般情况下配置为off

proxy_set_body发送到上游服务器的请求体可以使用该指令修改

proxy_set_header 使用该指令重写发往上游服务器的header字段

在配置过程中这些配置是允许放置在其他文件中然后在location中进行include的,并且可以再location中,include之后进行重写覆盖例如

1
2
3
4
5
location /test {
include proxy.conf;
client_max_body_size 500m;
...
}

upstream模块

与proxy模块紧密搭配的是upstream模块,该模块主要用来定义上游服务器,包括权重、类型

ip_hash 服务器轮训方式,使用哈希值确保客户端均匀连接到服务器,基于ip地址计算,会将同一ip地址映射到同一上游服务器

keepalive 表示每个worker简称缓存的到上游服务器的连接数,使用http连接时,proxy_http_version应该是1.1,并且将proxy_ser_header Connection “”设置为空

least_conn 激活负载均衡算法,将请求分配到连接最少的那台上游服务器

server 为upstream定义一个服务器地址,带有端口号ip或者是套接字,并且后面跟一个可选参数,包括:weight表示权重,max_fails表示失败的最大次数,超出后会将其设为down,fail_timeout响应的最大时间,backup作为备胎,down表示不接受任何连接

相应的还有memcached模块使用memcached_pass进行转发

不建议使用if

建议将if转化为不同的location,参考链接,因为可能会发生不可预测的问题,一般来说只建议在判定然后return的时候使用if

error处理

不是特别清楚。。。p76

Nginx作为反向代理

Nginx作为反向代理的好处

实现安全隔离

  1. 将服务器对外的访问统一收到Nginx的服务端口,方便进行访问权限控制
  2. Nginx使用非root启动,限制使用www之类的用户
  3. 对流量进行加密,弃用ssl模块

使用ssl对流量进行加密

开启该功能需要打开—with_http_ssl_module支持,同时安装ssl证书和密钥。

目前比较火的并且免费的证书颁发结构推荐Let’s Encrypt,关于该项目的介绍就不再赘述,需要说明的是申请的免费证书会在一段时间后过期,但是有流程可以renew,对于个人博客网站十分合适:),感谢这些开源人士推广https所做的努力

以下为样例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server{
listen 443 default ssl;#使用listen指令的ssl参数激活SSL模块
server_name www.example.com;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 SSLv3;
ssl_ciphers RC4:HIGH:!aNULL;!MD5:@STRENGTH;
ssl_session_cache shared:WEB:10m;#设置为shared这样所有的worker进程使用同一个协商密钥,节省开销
ssl_certificate /usr/local/etc/ngnx/www.example.com.crt;
ssl_certificate_key /usr/local/etc/Nginx/www.example.com.key;#key的文件权限应该设置为仅能够被master仅成都去
location /{
proxy_set_header X-FORWARDED-PROTO https;#通知上游服务器使用了https协议
proxy_pass http://upstream;
}
}

使用ssl进行客户端身份验证

还有一种情况是应用程序需要使用从客户端发送来的ssl证书信息,需要将信息传递给应用横须,可以使用Nginx增加一个头

1
2
3
4
location /ssl {
proxy_set_header ssl_client_cert $ssl_client_cert;
proxy_pass http://upstream;
}

这个$ssl_client_cert变量包含了客户端的ssl证书,为pem格式。我们可以功过将同名的头作为变量传递给上游服务器,而不是将整个客户端证书传递到上有服务区。Nginx还可以做一些提前的检查工作确认客户端是有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server{
...
ssl_client_certificate /usr/local/etc/Nginx/ClientCertCAs.pem;
ssl_crl /usr/local/etc/Nginx/ClientCertCRLs.crl;
ssl_verify_client on;
ssl_verify_depth 3;
error_page 495 = @noverify;
error_page 496 = @nocert;
location @noverify{
proxy_pass http://insecure?status=notverified;
}
location @nocert {
proxy_pass http://insecure?status=nocert;
}
location / {
if ($ssl_client_verify = FAFILED){
return 495;
}
proxy_pass http://secured;
}
}

上述配置主要包含以下几个验证客户端ssl证书的部分

  • ssl_client_certificate指定了PRM编码的根CA证书,该证书视为有效的客户端证书签名
  • ssl_crl指令的参数指定了证书撤销列表,有证书颁发机构负责签发,用于客户端证书签名。这个crl需要分别下载并保持更新
  • ssl_verify_client指令指明我们需要检查ssl客户端的有效性
  • ssl_verify_depth指令负责宣布一个证书无效之前需要检查多少位的签名,因为ssl证书可能有一个或者多个中间CA签名,在ssl_client_certificate路径中指定的证书不是中间证书就是根CA证书
  • 如果客户端证书验证过程中出现一些错误那么Nginx将会返回一个非标准的错误码459,同时我们设定一个error_page用于匹配该错误代码并且重定向请求道一台单独的服务器来处理这个请求,在proxy_pass的location中包含了对于ssl_client_verify变量的值得检查,确保无效证书返回这个代码

如果应用程序在证书中需要一些其他信息,例如授权的用户,Nginx可以再头中传递这些信息

1
2
3
4
location / {
proxy_set_header X-Http-AUTH $ssl_client_s_dn;
proxy_pass http://secured;
}

这样,我们运行在上有的服务器就可以使用这个头授权客户端访问不同的区域,ssl_client_s_dn包含了客户端的证书DN的subject值,应用程序可以使用这个信息进行数据库或者目录查找匹配用户

基于ip地址阻断流量

该模块需要编译时增加—with-http_geoip_module来解析ip地址的地理位置

geoip_country /usr/local/etc/geo/GeoIP.dat;

如果客户端的ip在该数据库文件中,那么变量$geoip_country_code的值将会陪设置为两个字母的国家码,同时还有一个geo模块需要使用,考虑如下配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
http{
geoip_country /usr/geo.dat;
geo $exclusions {
default 0;
127.0.0.1 1;
...
}
server {
if ($geoip_country_code = "CH"){
set $exclusions 1;
}
}
location / {
if (exclusions = "0"){
return 403;
}
proxy_pass http://upstream;
}
}
}

不仅可以通过ip来进行过滤,还可以通过一段时间请求的次数来进行阻止访问

便于横向拓展服务器

考虑定义上游服务器这种形式

1
2
3
4
5
upstream app{
server ip1;
server ip2;
server ip3 down;
}

当上游服务处理性能不够的时候增加起来很方便,同时如果要给ip3进行升级处理将其设为down即可

反向代理服务器性能调优(关于Nginx各种proxy_buffer的介绍还是需要看一下官方文档)

proxy_buffer_size 设置缓冲大小,该缓冲区用以来自上游服务器响应的第一部分

proxy_buffering 启用代理内容缓冲,当该功能禁用时,那么代理收到接收到的内容将会立刻返给客户端,proxy_max_temp_file_size参数被设置为0,peoxy_max_temp_file_size设置为0,调整proxy_buffering为0,确保在代理过程中不会有硬盘被使用但是仍然使用了缓存

proxy_buffers 用于相应上游服务器的缓冲数量和大小

proxy_busy_buffers_size在上游服务器服务响应时分配给发送相应的缓冲空间大小,典型的设置是将其设置为proxy_buffers的两倍

proxy_buffers的配置决定了我们Nginx能够保持的连接数量,考虑如下配置

1
2
3
...
proxy_buffers 8 4k;
...

那么假设服务器Nginx可用内存为768MB,那么能够保持的最大连接数为768 1024 1024 / 8 / 4 /1024 /2= 12288个连接数最后除以2的原因是代理在中间保持连接的缓存消耗是doubule的

对于Nginx来说其默认的配置是擅长处理客户端多且慢,服务端少且快的场景的,所以在对于Nginx的优化过程中其使用场景至关重要

Nginx的缓存配置

由于使用较少,暂时先忽略

gzip

1
2
3
4
5
6
7
http {
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain text/css ...;#指定压缩的文件类型
gzip_disable msie6;#对于ie6用户不启用
}

启用gzip压缩后,有可能发生文件截断的,原因是文件大小超过了gzip_buffer

1
2
3
4
5
6
7
http{
gzip on;
gzip_min_length 1024;#设置处理的最小文件是1024字节
gzip_buffer 40 4k;#设置最大处理的文件
gzip_comp_level 5;
gzip_types text/plain aaplication/x-javascript application/json;
}

Nginx HTTP部分(2017-09-08)

对于io可能是瓶颈的Nginx服务器需要将worker的数量设置的大于CPU核数,这里注意一下:)

server部分的相关指令

port_in_redirect 确定Nginx是否对端口指定重定向

server_name_in_redirect 在该上下文中任何有Nginx重定向的都是用该server_name(on/off)

server_token 禁止发送Nginx版本号(on/off),ps:除了可以使用这种方法隐藏版本号之外,还可以使用修改源码重新编译的方式迷惑对方修改/src/core/Nginx.h 里面的Nginx_VERSION、Nginx_VER

日志相关指令

access_log 第一个参数为日志路径,支持$1这种参数形式、第二个参数为可选值用来指定该日志使用前面使用的某种日志格式,第三个参数指明了写缓存的大小,使用中不能超过写文件系统的原子磁盘大小,如果为gzip那么缓冲日志将会被动态压缩,该功能需要zlib的支持,最后一个参数为flush,表明该日志flush到磁盘之前他们能够在缓存中的最大停留时间

例如:

server_name ~^(www\.)?(.+)$;

access_log /var/log/Nginx/$2/access.log.gz combined gzip=4 flush=1m; #开启gzip压缩之后log_format格式将不可选用

log_not_found 用来控制对于404的错误是否记录入日志默认为on

文件查找

例如要提供自动检索下载服务可以如下配置

1
2
3
4
5
6
server{
root /var/www/;
location /downloads{
autoindex on;
}
}

如果当客户端访问的某个文件可能不存在可以使用try_file进行多次尝试

1
2
3
location / {
try_files $uri $uri/ backup/$uri /genric-not-found.html;
}

为了安全起见可以禁止访问带有任何符号链接的路径,或者文件

1
2
3
4
5
6
server{
root ...;
disable_symlinks if_not_owner from=$document_root;
#disable_symlinks 确定讲一个文件返回给客户端之前是否是一个符号链接,参数主要包括 off表示禁止检查符号链接,on如果路径中某一部分是一个链接,那么拒绝访问,if_not_owner:如果路径中有一部分是一个连接,并且链接有不同的文件宿主那么禁止访问,from=part如果指定了部分路径那么到这部分的符号链接是不检查的,之后的部分会按照on 或者if_not_owner进行检查
#trf_file 对于给定的参数测试文件的存在性,如果前面的文件都没有找到,那么最后的条目将作为备用,所以一般确保最后一个路径或者明明location存在,或者通过=<status code>设置一个返回状态码
}

自定义nameserver (2017-09-09)

默认情况下Nginx使用系统的nameserver但是这个也是可以进行修改的,同时还支持对ttl的重写

1
2
3
server {
resolver 192.168.1.1 valid=300s;
}

与客户端进行交互相关配置

default_type 设置相应的默认类型,如果文件的mime类型不能陪types指令指定的类型正确匹配那么将会使用该指令指定的type

error_page 定义一个用于访问的uri,在遇到设置的错误代码时将由该uri提供访问,使用=号可以改变相应代码,如果=号的参数为空,那么相应代码来自于后面的uri,这种情况下必须有某种上游服务器提供

etag对于静态资源紧致自动产生ETag相应头,默认为on

if_modified_since 通过比较If-Modifyied-Since请求头的值控制如何修改响应时间,off表示忽略If-Modified-Since头,exact精确匹配(默认情况)before表示修改相应时间小于或者等于If-Modified-Since头

ignore_invalid_headers 禁止忽略无效名字的头(默认为on)。一个有效名字是有ASCII字母或者数字连字符号以及下划线(由underscores_in_headers)组成的

merge_slashes 禁止溢出多个斜线,默认值on,意味着Nginx会将两个或者多个/修改为一个

resursive_error_page 启用error_page指令(默认为off)实现多个重定向

types 设置MEME类型到文件拓展名的映射。Nginx在conf/mime.types文件中包含了大多数mime类型的映射,使用include载入该文件之后大多数情况下就足够了

underscores_in_headers 在客户端请求头中启用下户线字符

error_page指令是Nginx中最灵活的指令,通过该指令,当有任何条件的错误出现时我们都可以提供对应的页面,这个页面可以在本机上,也可以有应用程序服务器提供的动态页面,甚至在另外一个站点上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
http {
error_page 500 501 502 503 504 share/example/Nginx/50x.html;
server {
server_name example.com;
root /home/user/html;
error_page 404 /404.html;
location / {
error_page 500 501 502 503 504 = @error_handler;
}
location /microsite {
error_page 404 http://microsite.example.com/404.html;
}
location @error_handler{
default_type text/html;
proxy_pass http://127.0.0.1:8080;
}
}
}

limit指令

limit指令主要用于限制用户访问的连接数&以及访问频率,常用的访问频率控制指令如下:

limit_conn 指定某个共享内存中(由limit_conn_zone创建)键值对的最大连接数

limit_conn_log_level 当触发了连接时产生的日志等级

limit_conn_zone 指定并创建一块共享内存区域用以存放key,第一个参数为要存放的key,第二个参数为指定的名字以及大小

limit_rate 限制用户端下载内容的速率,限制的维度为连接级别,这也就意味着用户可以通过打开多个链接增加吞吐量

limit_rate_after 当传输的数据大于该单位时才启用limit_rate限制传输

limit_req 类似limit_conn,在共享内存(由limit_req_zone创建)对特定key设置并发请求能力的限制,并发数量可以通过第二个参数指定,如果要求在两个请求之间没有掩饰那么需要配置第三个参数nodelay,这里需要说一下nodelay:当用户达到limit时如果没有指定nodelay那么会进入一个请求队列进行等待相当于限定了最大并发数量,如果制定了nodelay那么不会进行等待而是直接返回503错误 测试链接

limit_req_log_level同上

limit_req_zone 指定用于存储的key 以及共享内存的名称大小第三个参数rate表示配置在受到限制之前,每秒的请求数量,或者每分钟的请求数 上面的测试链接也包含此配置项

1
2
3
4
5
6
7
http{
limit_conn_zone $binary_remote_addr zone=connections:10m;
limit_conn_log_level notice;
server{
limit_conn connections 10;
}
}
1
2
3
4
5
6
7
http{
limit_req_zone $binary_remot_addr zone=requests:10m rate=1r/s;
limit_req_log_level warn;
server{
limit_req zone=requests burst=10 nodelay;
}
}

这里需要注意的是上述每个请求的特征key为binary_remote_addr即ip地址的二进制表示一般来说是4个字节,32位相当于一个32位机器上面的int类型,做成key-value的形式的话肯定是比字符串这种remote_addr要快的并且节省内存

以及限制用户的带宽

1
2
3
4
location /downloads{
limit_rate_after 1m;#相当于只有1m以上的文件下载才会启用下载限速
limit_rate 500k;
}

观察一下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
http {
limit_conn_zone $binary_remote_addr zone=ips:10m;
limit_conn_zone $server_name zone=server:10m;
limit_req_zone $binary_remote_addr zone=requests:10m rate=1r/s;
limit_conn_log_level notice;
limit_req_log_level warn;
reset_timeout_connection on;
server {
limit_conn ips 10;
limit_conn servers 1000;
location /search{
limit_req zone=requests burst=3 nodelay;
}
location /downloads {
limit_conn connections 1;
limit_rate_after 1m;
limit_rate 500k;
}
}
}

约束访问

相关指令

allow 允许某个网络,ip完全访问

auth_basic 启用基于http基本认证,一字符串作为鱼的名字,如果设置为off那么表示auth_bisic不会继承上级配置

auth_basic_user_file 指定存储认证信息的文件,文件格式为:username:password(加密过得):comment 最后的comment可选部分

deny 禁止从ip,网络或者all来的访问

1
2
3
4
location /private{
allow 127.0.0.1;
deny all;
}
1
2
3
4
5
server{
server_name restricted.example.com;
auth_basic "restricted";
auth_basic_user_file conf/httppasswd;
}

需要指出的是密码字段是经过unix crypt()处理后的字符串

1
2
3
4
5
6
7
8
9
10
11
12
server {
server_name intranet.example.com;
location / {
auth_basic "intrent:please login";
auth_basic_user_file conf/httpasswd-intranet;
allow 192.168.1.0/24;
deny all;
satisfy any;#这种情况下192.168.1.0/24网段的用户访问是不需要密码的
#完整的官方文档:Allows access if all (all) or at least one (any) of the ngx_http_access_module, ngx_http_auth_basic_module, ngx_http_auth_request_module, or ngx_http_auth_jwt_module modules allow access.
#翻译过来就是all表示所有的访问控制模块都允许访问才会放行,any的话表示只要有一个满足条件允许访问就放行
}
}

流媒体文件

Nginx可以支持一定的流媒体文件解析,flv和mp4模块,为了使用相关功能需要在编译中指定—with-http_flv_module(支持flv模块)以及—with-http_mp4_module(支持mp4)

flv 表示在该location激活flv模块

mp4 在该location激活mp4模块

mp4_buffer_size 设置投递Mp4文件的初始缓冲大小

mp4_max_buffer_size 设置处理mp4元数据使用的最大缓冲

预定义变量

基于变量使得Nginx配置变得容易,不仅可以使用set还可以使用map自己设置指令

$arg_name 在请求中的name参数类似于arg_type

$args 所有的请求参数

$binary_remot_addr 客户端ip地址的二进制格式

$content_length 请求头Content-Length的值

$content_type $请求头Content-Type的值

$cookie_name cookie标签名字

$document_root 当前请求中root或者alias的值

$docuent_uri $uri的表名

$host 如果当前有Host则为请求头Host的值,如果没有这个头,那么该值等于匹配该请求的server_name

$hostname 运行Nginx的主机名称

$http_name 请求头name的值,如果头包含有破折号那么会转化为下划线,大写字母转化为小写字母

$https 如果连接是ssl加密的name该值为on否则为空值

$is_args 如果请求有参数那么该变量为?否则为空字符串

$limit_rate 指令limit_rate的值如果没有设置那么速率限制时使用该变量

$Nginx_version Nginx的版本号

$pid worker的pid

$query_string $args的别名

$realpath_root 当前请求中root和alias的值

$remote_addr 客户端地址

$remote_port 客户端建立连接的端口号

$remote_user 使用http基本认证时的用户名

$request 完整的请求从客户端收到,包括http请求的方法uri http协议

$request_body 请求体

$request_body_file 临时文件的路径

$request_completion 如果请求完成该变量为ok否则为空字符串

$request_filename当前请求的文件路径以及文件名,基于root或者alias + uri

$request_method 请求的方式

$request_uri 完整的请求的uri,包括参数

$scheme 请求的协议,不是http就是https

$sent_http_name 响应头的名字

$server_addr 接受请求的服务器的地址

$server_name 接受请求的虚拟主机的server_name

$server_port 接受请求的服务器的端口

$server_protocol 当前使用的http协议

Nginx与php-fpm

一系列的参数,p132

以及书中有一个配置drupal的示例配置 链接

Nginx 与uwsgi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
http{
upstream emperor{
127.0.0.1:3017;
}
server{
root /home/www/sites/$host;
location / {
location ~* ^.+\.$ {
root /home/www/sites/$host/static/styles;
expires 30d;
}
try_files $uri @django;
}
location @django {
root /home/www/apps/$host;
include uwsgi_params;
uwsgi_param UWSGI_FASTROUTER_KEY $host;
uwsgi_pass emperor;
}
location = /roots.text{
root /home/www/sites/$host/static;
access_log off;
}
location = /favicon.ico {
error_page 404 = @empty;
root /home/www/sites/$host/static;
access_log off;
expires 30d;
}
location @empty{
empty_gif;
}
location ~* ^.+\.py${
return 404;
}
}
}

Nginx高级配置 2017-09-17

缓存相关

观察如下Nginx缓存配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http{
proxy_cache_path /var/spool/Nginx/articles key_zone=ARTICLES:10m levels=1:2 inactive=1d;
proxy_cache_path /var/spool/Nginx/images keys_zone=IMAGES:128m levels=1:2 inactive=30d;
proxy_temp_path /var/spool/Nginx;
server {
location / {
proxy_cache_valid 1m;
}
location /articles {
proxy_cache_valid 1d;
}
location /img {
proxy_cache_valid 10y;
}
}
}

使用memche作为缓存

1
2
3
4
5
6
7
8
location /{
set $memcached_key $uri;
memcached_pass 127.0.0.1:11211;
}
location / {
set $memcached_key "$uri?$args";
memcached_pass 127.0.0.1:11211;
}

如果请求的key并不存在,那么Nginx可以一种方式通知应用服务器,将该k、v写入缓存以便于下次请求能够直接从内存调用,如果请求的key并没有被找到那么Nginx将会报告一个not found的错误,因此最好的方式是将请求传递给应用程序并使用error_page指令来实现

1
2
3
4
5
6
7
8
9
10
server {
location / {
set $memcached_key "$uri?$args";
memcached_pass 127.0.0.1:11211;
error_page 404 502 504 = @app;#记得这里使用=确保能够改写http code
}
location @app {
proxy_pass 127.0.0.1:8080;
}
}

一些memcached的模块执行

memcachedd_buffer_size 用于设置来自Memcached相应的缓存大小,同步发送给客户端

memecached_connect_timeout 设置Nginx等待memecached相应的最长时间

memcached_next_upstream 设定在什么条件下将请求传递给下一个服务器,有如下几种参数:error在同memecached通信时出错,timeout表示通信时发生超时,invalid_response memecached返回了空的或者无效的响应,not_found表示没有找到相应的key,off 禁止转发

memcached_pass 类似proxy_pass

memcached_read_timeout 、memcached_send_timeout

缓存文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
root /home/www;
location / {
location /index.html {
expires 5m;
}
location ~* /.*\.(js|css)${
expires 24h;
}
locatoin ~* /.*\.html${
expires 3d;
}
}
location /img {
expires max;
}
}#不同类型的文件设置的缓存时间

动态修改内容

  1. addition
  2. sub
  3. xslt

addition模块

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
root /home/www;
location / {
add_before_body /header;#在响应体之前添加子请求处理结果
add_after_body /footer;#在相应体之后添加子请求处理结果
}
location /header {
proxy_pass http://127.0.0.1:8080/header;
}
location /footer {
proxy_pass http://127.0.0.1:8080/footer;
}
}

sub模块

该模块作为一个过滤器用以替换一个文本为另外一个文本

1
2
3
4
5
location / {
sub_filter_once off;#表示全部替换
sub_filter_types text/css;添加相应的处理的mime类型
sub_filter '<img src="img/' '<img src="/img/';
}

xslt模块

这个主要用以转换xml p156

服务器端包含 SSI

p157

Nginx之中使用perl

p160

ps:貌似现在lua更流行?

创建安全连接

1
2
3
4
echo -n 'alphabet_soup.pdfsupersecret' | md5sum
8082202b04066a49a1ae8da9ec4feba1
echo -n 'time_again.pdfsupersecret' | md5sum
5b77faadb4f5886c2ffb81900a6b3a43
1
2
<a href="/downloads/8082202b04066a49a1ae8da9ec4feba1/alphabet_sout.pdf"> alphabet soup</a>
<a href="/downloads/5b77faadb4f5886c2ffb81900a6b3a43/time_again.pdf"> time again</a>
1
2
3
4
5
6
7
location /downloads/ {
secure_link_secret supersecret;
if ($secure_link = ""){
return 403;
}
try_files /downloads/$secure_link = 404;
}

生成图像

libgd库,可以支持图像的旋转调整大小 p165

跟踪网站的访问者

使用userid模块设置客户端cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
http {
log_format useridcomb '...';
server {
server_name .example.com;
access_log logs/example.com-access.log useridcomb;
userid on;
userid_name uid;
userid_domain example.com;
userid_path /;
userid_expires 365d;
userid_p3p 'policyref="/w3c/p3p.xml", CP="CUR ADM OUR NOR STA NID"';
}
}

userid on表示启用版本2的cookies并记录他们,v1 表示启动版本1,log表示不发送cookies但是记录进入的cookies,off表示不发送cookie也不记录到日志

userid_domain 配置域设置cookie

userid_expires 设置生存周期

userid_name 设置cookie的名字,默认为uid

useid_p3p 设置p3p头

userid_path 设置cookie的路径

userid_service 设置服务cookie标示如果是版本二那么cookies将会设置为服务器的ip地址

防止恶意代码的执行

考虑如下配置集齐注释部分

1
2
3
4
5
location ~* \.php {
#try_files $uri =404;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}

若没有注释的那一部分,则可能存在安全隐患例如,当用户上传了包含php代码的jpg文件aaa,这个时候请求www.example/aaa.jpg/123.php Nginx发现末尾是php于是将aaa.jpg/123.php传递给cgi程序,然后站点就被拿了。。。而如果加上try_file的话Nginx发现123.php这个文件并不存在,就直接返回404了 :)参考链接

Nginx try_files 里的一个坑

今天下午 blog 某管理员踩到了 Nginx try_files 的一个坑,导致 WordPress 博客采用了固定链接的页面无法访问。此问题持续半小时后修复。

原来的配置是这样的:

1
2
3
4
5
6
>         location / {
> try_files $uri $uri/ /index.php;
> index index.html index.htm index.php;
> }
> location ~ \.php$ { ... }
>

修改成了

1
2
3
4
5
6
>         location / {
> try_files $uri $uri/ /index.php =404;
> index index.html index.htm index.php;
> }
> location ~ \.php$ { ... }
>

增加的这个 =404,似乎是让 index.php 找不到的时候返回 404。但这么一改,形如 https://servers.blog.ustc.edu.cn/2014/09/ustc-telecom-link-failure/ 这样的链接返回的竟然是 index.php 的源码!这是怎么回事呢?

这要从 Nginx try_files 的工作原理说起。以 try_files $uri $uri/ /index.php; 为例,当用户请求 http://servers.blog.ustc.edu.cn/example时,这里的 $uri 就是 /example。try_files 会到硬盘里尝试找这个文件。如果存在名为 /$root/example(其中 $root 是 WordPress 的安装目录)的文件,就直接把这个文件的内容发送给用户。显然,目录中没有叫 example 的文件。然后就看 $uri/,增加了一个 /,也就是看有没有名为 /$root/example/ 的目录。又找不到,就会 fall back 到 try_files 的最后一个选项 /index.php,发起一个内部 “子请求”,也就是相当于 Nginx 发起一个 HTTP 请求到 http://servers.blog.ustc.edu.cn/index.php。这个请求会被 location ~ \.php$ { ... } catch 住,也就是进入 FastCGI 的处理程序。而具体的 URI 及参数是在 REQUEST_URI 中传递给 FastCGI 和 WordPress 程序的,因此不受 URI 变化的影响。

配置修改之后,/index.php 就不在 fall back 的位置了,也就是说 try_files 在文件系统里找到 index.php 之后,就会直接把文件内容(也就是 PHP 源码)返回给用户。这里的坑就是 try_files 的最后一个位置(fall back)是特殊的,它会发出一个内部 “子请求” 而非直接在文件系统里查找这个文件。

故障排查指南

常用的排错技巧包括:

  • 日志文件分析
  • 配置高级日志
  • 常见的配置错误
  • 操作系统限制
  • 性能问题
  • 使用动态模块

分析日志文件

不管是自己写的代码或者是其他服务,出问题之后第一反应必须是看日志,当Nginx出现异常是应该首先看的是相应的error_log,一般情况下其包含的错误信息对于排查问题已经很充分了,下面是一些常见的错误输出以及原因:

upstream prematurely closed connection while reading… 该错误表示与上游服务通信时发生了错误有可能是协议的问题例如应该使用fastcgi_pass而使用了proxy_pass,或者是那几个timeout相关的变量设置的时间太小了

client intended to send too large body…这个错误也很明显了,客户端尝试发送过大的文件,这个问题修复的方法就是去修改client_body_size 值得注意的是由于编码问题的原因,这里设置的大小应该比原文件大30%左右

proxy_pass cannot have uri part in location by regular expression…这个也写得很清楚啦,就是在proxy_pass不能在使用正则匹配的location中使用uri

mkdir() “/home/www/tmp” (2: No such file or directory)…这个错误也很明显了对吧?

unknown directive client_body_temp_path in /opt/nginx/conf/nginx.conf:6 client_body_temp_path本身是nginx的有效指令,为啥在这里提示不识别呢,原因是因为不同的指令在nginx生效的区块位置是不一样的,所以这个问题是因为把指令写错位置了

host not found in upstream “tickets.example.com” in /opt/nginx/conf/nginx.conf:22 dns服务解析失败了

unexpected “}” in /opt/nginx/conf/nginx.conf:40 很明显啦,nginx配置文件格式都不对的

unexpected end of file,expectin “}” in nginx.conf:21 同上,可能的原因是少写了;

unknown “exclusion” variable 表示$exclusion变量出现在了set map或者geo指令定义之前

配置高级日志记录

在某些情况下是有可能出现一些比较复杂的问题的,利用现有的日志找不到问题,这个时候就需要重新编译Nginx并且在编译时机上—with-debug选项打开日志的详细信息,打开该选项之后nginx内部分配内存、处理文件描述符这种十分细节的日志都会输出出来

之前遇到一个问题,Nginx在和上游通信的时候总是返回一部分结果,但是其http code总是200,这就很奇怪了,然后通过打开该选项,查看详细日志发现在中间有一段信息给出来的是http code 504,显示是上游服务提前关闭了连接!最终定位了问题和上游服务沟通调整了上游服务的响应参数,问题解决。

PS:该问题其实还挺复杂的,还涉及到了nginx的lua模块

高级日志的日志格式就不再赘述了,这里需要记一下nginx的无缝升级,当nginx应用于生产环境的时候,肯定不能使用restart这种指令这样使用会导致当前处理的网络连接强制断开,所以涉及到版本升级,或者是像刚刚说的,需要替换二进制文件查看详细的debug信息的时候就需要这种无缝升级的操作了

切换步骤

1
2
3
4
5
6
7
8
9
10
#向正在运行的进程发送USR2信号告诉它启动一个新的master进程同时会将nginx.pid文件重命名为nginx.pid.oldbin
#相应的nginx在写日志的时候将日志文件移走,同时发送USR1信号可以实现log rotatin的效果 :)
kill -USR2 `cat /var/run/nginx.pid`
#向旧的master进程发送WINCH信号,告知其停止接收新的请求并且当worker处理完当前请求后淘汰之
kill -WINCH `cat /var/run/nginx.pid.oldbin`
#向旧的master进程发送QUIT信号,一旦worker都处理完毕那么将仅有新的二进制服务接受网络请求
kill -QUIT `cat /var/run/nginx.pid.oldbin`
#在决定销毁旧的nginx进程之前,若发现新进程有问题那么可以进行回滚操作。。。简直。。。牛逼啊
kill -HUP `cat /var/run/nginx.pid.oldbin`
kill -QUIT `cat /var/run/nginx.pid`
日志级别
  • debug_core
  • debug_alloc
  • debug_mutex
  • debug_event
  • debug_http
  • debug_imap

以上表示对不同模块开启debug调试级别

使用日志文件进行调试

在我们自己编写代码的时候最最简单的调试方法就是print。。。来根据程序输出的日志顺序来定位问题,同样的nginx也可以这样,在不同的location定义不同的日志输出来定位请求处理的位置,例如如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
http {
log_format sendlog '.......';
log_format imagelog '.....';
log_format authlog '.....';
server{
server_name .example.com;
root /home/www;
}
location / {
access_log logs/example.com-access.log combined;
access_log logs/example.com-root_access.log sentlog;
rewrite ^/(.*)\.(png|jpg|gif)$ /images/$1.$2;
set $image_file $1;
set $image_type $2;
}
location /image {
access_log logs/example.com-images_access.log imagelog;
}
location /auth {
auth_basic "authorized area";
auth_basic_user_file conf/htpasswd;
deny all;
access_log log/examle.com-auth_acces.log authlog;
}
}

通过上述例子每一个location配置一个access_log指令,并配置了不同的日志格式,通过不同的日志输出我们就能知道请求到达了那个location借此定位问题:)

常用的配置错误

例如使用了if 代替try_files,考虑如下配置

1
2
3
4
5
6
7
8
9
10
server {
root /var/www/html;
location / {
if (! -f $request_filename){
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
break;
}
}
}

可能当初在配置该文件的时候还不支持try_files指令,在location中使用if时不推荐的,应该考虑如下配置

1
2
3
4
5
6
7
8
9
10
server {
root /var/www/html;
location / {
try_files $uri $uri/ @fastcgi;
}
location @fastcgi {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
}
}

以及使用if作为主机名切换,增加了处理成本

1
2
3
4
5
6
7
server {
server_name .example.com;
root /var/www/html;
if ($host ~* ^example\.com){
rewrite ^/(.*)$ http://www.example.com/$1 redirect;
}
}

改为如下

1
2
3
4
5
6
7
8
9
10
11
server {
server_name example.com;
return 301 $scheme://www.example.com;
}
server {
server_name www.example.com;
root /var/www/html;
location / {
...
}
}

考虑配置的继承关系

1
2
3
4
5
6
7
8
9
10
11
12
server {
server_name www.example.com;
location / {
root /var/www/html;
index index.php index.html index.htm;
}

location /ftp {
root /var/www/html ;
index index.php index.html index.htm;
}
}

以上配置每增加一个location就需要复制拷贝

1
2
3
4
5
6
7
8
9
10
11
server {
server_name www.example.com;
root /var/www/html;
index index.php index.html index.htm;
location / {
...
}
location /ftp {
...
}
}

操作系统的限制

主要包含文件描述符限制以及网络限制,因为在高并发的情况下是需要大量消耗这两种资源的

文件描述符限制

通过ulimit -n查看当前用户的可用数量,通过查看/proc/pid/limits文件确认进程可以打开的数量,通过修改/etc/security/limits.conf来调整,之后需要调整worker_rlimit_nofile指令使nginx应用

网络限制

网路限制主要涉及到了系统允许进程使用的端口号范围,通过查看/etc/sysctl.conf中定义的ip_local_port_range确认

性能问题

这个主要就是缓存了

使用stub status模块

nginx提供一个自检模块,该模块输出运行时的一些统计信息需要编译时指定—with-http_stub_status_module,并增加location

1
2
3
4
5
6
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}

输出为类似如下,reading表示当前有多少个连接长在读取请求头writing表示有多少个连接正在读取请求体,以及waiting表示有多少在等待类似keepalive,7表示简历的连接数,19表示处理的请求数

1
2
3
4
Active connections: 4 
server accepts handled requests
7 7 19
Reading: 0 Writing: 1 Waiting: 3

附录,主要包含一些感觉比较重要的配置

add_after/before_body

如下配置访问test.html的时候输出是:

1234This’s Test 890

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location /1234 {
default_type text/plain;
return 200 "1234";
}
location /890 {
default_type text/plain;
return 200 "890";

}
location /test.html {
index test.html;
add_before_body /1234;
add_after_body /890;

}

alias/root

主要区别以及用途已经有专门写了:)

autoindex

打开目录的浏览功能,例如多数ftp浏览器那种展示方式

autoindex_exact_size

默认为on显示文件的确切大小,单位是bytes,改为off后显示文件的大概大小单位为kb或者mb或者gb

client_body_in_file_only

打开情况下会将整个客户端的请求体存入一个文件,多用于debug的情况下

directio

该选项的参数为size|off,当读大于配置大小的文件时会使用o_DIRECT flag标记位,当用于处理大文件时尤其有用,在linux下面同样使用aio的情况下也需要设置该配置

directio_alignment

用于调整directio的队列大小平时一个512byte的大小就足够了,但是在xfs文件系统中需要增大到4K

empty_gif

1
2
3
location = /empty_gif {
empty_git;
}

这样写的话在访问/empty_gif的时候返回的是一个1x1像素的图片文件,貌似只有个统计的作用。。

env

官方文档如是说:

The NGINX environment variable is used internally by nginx and should not be set directly by the user.

error_page

定义不同的错误码的处理方式例如:

1
2
3
4
5
6
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
error_page 404 =200 /empty.gif; #表示httpcode改为200同时返回empty.gif
error_page 404 = /404.php;
error_page 404 = @fallback;
#最后两种表示将该错误请求传递给后面的uri或者服务进行处理并对httpcode进行改写

geo/geoip_city/geoip_country/geoip_org

分别表示了geo模块中代表的变量值

log_not_found

是否在日志中记录404记录

merge_slashes

对于////这种多个’/‘连在一起的uri是否进行处理

proxy_http_version

代理使用的http协议类型

proxy_ignore_client_abort

在进行转发时,当客户端断开连接时是否继续保持与上游服务器的连接

proxy_intercept_errors

主要用以控制当代理的请求回包的httpcode大于或者等于300的时候是否返回给客户端,或者是终端并且重定向到nginx或者error_page

proxy_next_upstream

控制在那种情况下将请求传送给下一个server,主要包含如下几种:

  • error 当与server建立连接时或者传输请求或者读取回包的时候发生了错误
  • timeout 当有超时情况发生时
  • invalid_header 当server返回了一个空或者不可用的response时
  • http_[500|502|503|504|403|404|429] 当有这些状态码时
  • non_idempotent 正常来说当非幂等的请求例如post,在被发往一个server之后是不会发给另外一个的,通过该选项来控制
  • off 禁止发往下一个server

proxy_pass_header

允许将其他金庸的header字段发送给client

proxy_pass_request_body

表名是否把原始的请求体传送给代理服务器

proxy_set_header

主要用于重新定义发送给服务器的header部分

real_ip_header

用于定义请求的header中那个区域用来存储客户端的地址

real_ip_recursive

用于控制如何处理多个ip串

secure_limit_secret

定义用于进行hash计算的字符串

sendfile

打开的情况下对于nginx作为下载服务器性能有提升

server_token

是否显示nginx版本信息

set_real_ip_from

控制从哪里设置客户端真是ip

ssl_certificate

指定ssl证书的位置

tcp_nodelay/nopush

主要控制在socket选项中是否打开TCP_NOPUSH/TCP_NODEPAY flag,前者主要是在keep-alive连接中,后者主要在sendfile打开的配置中使用

try_files

try_files比较复杂,既可以用以替换rewrite也可以用以避免PHP代码恶意上传,前面都有,这里再附上一篇链接参考

Nginx的配置语法灵活,可控制度非常高。在0.7以后的版本中加入了一个try_files指令,配合命名location,可以部分替代原本常用的rewrite配置方式,提高解析效率。

try_files指令说明

1
2
3
4
5
> try_files指令
> 语法:try_files file ... uri 或 try_files file ... = code
> 默认值:无
> 作用域:server location
>

其作用是按顺序检查文件是否存在,返回第一个找到的文件或文件夹(结尾加斜线表示为文件夹),如果所有的文件或文件夹都找不到,会进行一个内部重定向到最后一个参数。

需要注意的是,只有最后一个参数可以引起一个内部重定向,之前的参数只设置内部URI的指向。最后一个参数是回退URI且必须存在,否则会出现内部500错误。命名的location也可以使用在最后一个参数中。与rewrite指令不同,如果回退URI不是命名的location那么$args不会自动保留,如果你想保留$args,则必须明确声明。

1
2
> try_files $uri $uri/ /index.php?q=$uri&$args;
>

实例分析

示例一

try_files 将尝试你列出的文件并设置内部文件指向。

例如:

1
2
3
> try_files /app/cache/ $uri @fallback; 
> index index.php index.html;
>

它将检测$document_root/app/cache/index.php,$document_root/app/cache/index.html 和 $document_root$uri是否存在,如果不存在着内部重定向到@fallback(@表示配置文件中预定义标记点) 。
你也可以使用一个文件或者状态码(=404)作为最后一个参数,如果是最后一个参数是文件,那么这个文件必须存在。

示例二

例如nginx不解析PHP文件,以文本代码返回

1
2
> try_files $uri /cache.php @fallback;
>

因为这个指令设置内部文件指向到 $document_root/cache.php 并返回,但没有发生内部重定向,因而没有进行location段处理而返回文本 。
(如果加上index指令可以解析PHP是因为index会触发一个内部重定向)

示例三

跳转到变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> server {
> listen 8000;
> server_name 192.168.119.100;
> root html;
> index index.html index.php;
>
> location /abc {
> try_files /4.html /5.html @qwe; #检测文件4.html和5.html,如果存在正常显示,不存在就去查找@qwe值
> }
>
> location @qwe {
> rewrite ^/(.*)$ http://www.baidu.com; #跳转到百度页面
> }
>

示例四

跳转指定文件

1
2
3
4
5
6
7
8
9
10
> server {
> listen 8000;
> server_name 192.168.119.100;
> root html;
> index index.php index.html;
>
> location /abc {
> try_files /4.html /5.html /6.html;
> }
>

示例五

将请求跳转到后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> upstream tornado {
> server 127.0.0.1:8001;
> }
>
> server {
> server_name imike.me;
> return 301 $scheme://www.imike.me$request_uri;
> }
>
> server {
> listen 80;
> server_name www.imike.me;
>
> root /var/www/www.imike.me/V0.3/www;
> index index.html index.htm;
>
> try_files $uri @tornado;
>
> location @tornado {
> proxy_pass_header Server;
> proxy_set_header Host $http_host;
> proxy_set_header X-Real-IP $remote_addr;
> proxy_set_header X-Scheme $scheme;
>
> proxy_pass http://tornado;
> }
> }
>

常见错误

常见错误一

try_files 按顺序检查文件是否存在,返回第一个找到的文件,至少需要两个参数,但最后一个是内部重定向也就是说和rewrite效果一致,前面的值是相对$document_root的文件路径。也就是说参数的意义不同,甚至可以用一个状态码 (404)作为最后一个参数。如果不注意会有死循环造成500错误。

1
2
3
4
5
> location ~.*\.(gif|jpg|jpeg|png)$ {
> root /web/wwwroot;
> try_files /static/$uri $uri;
> }
>

原意图是访问http://example.com/test.jpg时先去检查/web/wwwroot/static/test.jpg是否存在,不存在就取/web/wwwroot/test.jpg

但由于最后一个参数是一个内部重定向,所以并不会检查/web/wwwroot/test.jpg是否存在,只要第一个路径不存在就会重新向然后再进入这个location造成死循环。结果出现500 Internal Server Error

1
2
3
4
5
> location ~.*\.(gif|jpg|jpeg|png)$ {
> root /web/wwwroot;
> try_files /static/$uri $uri 404;
> }
>

这样才会先检查/web/wwwroot/static/test.jpg是否存在,不存在就取/web/wwwroot/test.jpg再不存在则返回404 not found

常见错误二

Nginx try_files $query_string为空的解决办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
> server {
> listen 80;
> server_name localhost.dev;
> index index.php index.html index.htm;
> set $root_path '/var/www/phalcon/public';
> root $root_path;
> location / {
> try_files $uri $uri/ /index.php;
> }
> location ~ \.php$ {
> try_files $uri =404;
> fastcgi_split_path_info ^(.+\.php)(/.+)$;
> fastcgi_pass 127.0.0.1:9000;
> fastcgi_index index.php;
> fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params;
> }
> location ~* ^/(css|img|js|flv|swf|download)/(.+)$ {
> root $root_path;
> }
> location ~ /\.ht {
> deny all;
> }
> }
>

发现PHP无法获取$_GET信息

1
2
> try_files $uri $uri/ /index.php;
>

改为

1
2
> try_files $uri $uri/ /index.php?$query_string;
>

即可解决

worker_cpu_affinity

已位运算的格式将nginx的worker绑定到各个cpu上面

1
2
3
4
worker_processes    4;
worker_cpu_affinity 0001 0010 0100 1000;
worker_processes auto;
worker_cpu_affinity auto;

cpu进行worker绑定的意义在于减少cpu切换导致的缓存miss以及开销

worker_priority

定义worker的优先级,格式和linux的nice一样

worker_processes

worker的数量

worker_connections

每个worker保持最大的连接数

working_directory

worker的工作路径

Nginx rewrite规则指南

首先rewrite模块,支持正则表达式,其格式为

1
2
rewrite ^/images/([a-z]{2})/({a-z0-9}{5})/(.*|)\.(png|jpg|gif)$ /data?file=$3.$4 last;
#rewrite 匹配部分 重写的uri部分 last|break|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
http {
log_format imagelog '... $image_file $image_type ...';
rewrite_log on;
#打开rewrite日志可以用来debug
server {
root /home/www;
location / {
error_log logs/rewrite.log notice;
rewrite ^/images/([a-z]{2})/({a-z0-9}{5})/(.*|)\.(png|jpg|gif)$ /data?file=$3.$4;
#这里rewrite最后没有跟last的原因是因为如果写了的话,下面两个变量就不能被正确设置了
set $image_file $3;
set $image_type $4;
}
location /data {
access_log logs/images.log imagelog;
root /data/images;
try_files /$arg_file /image404.html;
}
location = /image404.html {
return 404 "image not found\n";
}
}
}
指令 说明
break 结束矗立在同一区段中找到的rewrite模块指令
if 评估条件if(connection) {…},条件可以是:1.变量名(空或者字符串)2.字符串比较,使用”=” “!=” 3.正则表达式匹配”~” “~*“ “!~” “!~*“ 4.文件是否存在”-f” “! -f” 5.目录是否存在 “-d” “ ! -d” 6.文件、目录、或者符号链接存在性 “-e” “! -e” 7.文件可执行性 “-x” “! -x” 好像shell有木有
return 停止处理并且像客户端返回指定的代码
rewrite 第三个参数:1. last:停止处理rewrite模块并且搜索改变后的uri匹配的location 2.break:停止处理rewrite模块指令 3.redirect:返回一个临时重定向当uri没有以一个sceme开始的时候使用该标志 4.permanent:返回一个永久重定向
rewrite_log 对rewrite使用notice级别的日志记录error_log
set 为一个变量赋值
unitilized_variable_warn 控制是否记录没有初始化变量的警告信息

利用rewrite可以使站点更加规范,uri更加整齐并且有利于站点被发现,例如有如下一堆uri

  • /
  • /home
  • /home/
  • /home/index
  • /home/index/
  • /index
  • /index.php
  • /index.php/

使用

1
rewrite ^/home(/index)?|index(\.php)?\?)/?$ $scheme://$host/ permanent;

当rewrite规则导致内部重定向,或者只是客户端调用规则自身定义的location时尤其要注意避免rewrite循环,例如,规则在server去店内定义并且有last标志,但是如果在locatin中应用定义,那么必须使用beak标志

1
2
3
4
5
6
server {
rewrite ^(/image)/(.*)\.(png|jpg|gif)$ $1/$2/$3 last;
location /image/{
rewrite ^(/image)/(.*)\.(png|jpg|gif)$ $1/$3/$2.$3 break;
}
}

看完之后还是有点迷糊,于是又搜了一下看了一下rewrite考虑如下例子

1
2
3
4
5
location /download/ {
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 ;
rewrite ^(/download/.*)/movie/(.*)\..*$ $1/avi/$2.mp3 ;
rewrite ^(/download/.*)/avvvv/(.*)\..*$ $1/rmvb/$2.mp3 ;
}

如果此时访问到location /download/acg/moive/UBW.avi是会匹配到第二行然后死循环,这个时候就需要把flag设置为break才能正常访问,照这么说,岂不是全都用breka就好了?当然不是,例如对于wordpress来说,如果写成break,在访问到’/‘下面之后rewrite到/index.php这个时候是返回的就是index.php的源码

同样,从另一方面来思考的话:

利用nginx的rewrite的指令,可以实现url的转向,对于rewrtie有四种不同的flag,分别是redirect、permanent、break和last。其中前两种是跳转型的flag,后两种是代理型。跳转型是指有客户端浏览器重新对新地址进行请求,代理型是在WEB服务器内部实现跳转的。

  • redirect:302跳转到rewrtie后面的地址。
  • permanent:301永久调整到rewrtie后面的地址,即当前地址已经永久迁移到新地址,一般是为了对搜索引擎友好。
  • last:将rewrite后的地址重新在server标签执行。
  • break:将rewrite后地址重新在当前的location标签执行。

使用root或proxy_pass指定源,last,break都可以,但是结果可能会有差别,后面用例子说明;使用alias指定源,必须使用last。假如有如下配置:

1
2
3
4
5
6
7
8
9
10
> 	location / {
> root /var/www/html;
> index index.html index.htm;
> rewrite "/x/t.html" /y/t.html break;
> }
>
> location /y/ {
> root /var/www/html/other;
> }
>

当请求/x/t.html,符合rewrite规则,所以进行调整,调整的地址为/y/t.html,由于使用的flag是break,所以在“location /”中进行跳转,结果是/var/www/html/y/t.html。但如果flag为last,那么/y/t.html将在server标签中重新执行,由于存在一个“location /y/”,所以跳转被重新指定到“location /y/”标签,所以这个时候的结果为/var/www/html/other/y/t.html。

注意:使用last,如果配置不当,可能引起死循环。例如:

1
2
3
4
5
> 	location /x/ {
> proxy_pass http://my_proxy;
> rewrite "^/x/(.*)\.html$" /x/1.html last;
> }
>

转载请注明来源链接 http://just4fun.im/2017/09/27/Master-Nginx读书笔记/ 尊重知识,谢谢:)