首页 > tools > nginx > > 正文

Nginx葵花宝典—草根站长Nginx运维百科全书

发布人:zhoulujun    点击:

nginx,也没有那么容易,先从转发开始细讲,我觉得下面的内容,基本覆盖了一个资深程序员或运维的日常需求。这里把功能点都做了分类、笔记提示等,不足之处也请道友们补充。

题记

前段时间把网站迁移到腾讯云,之前是lamp,现在改为lnmp,自以为nginx功底还可以,开发这么多年,平常环境都有配置。但是,但是,最近读站点做SEO优化,发现nginx很多地方不会配。比如:

https://www.zhoulujun.cn/

https://www.zhoulujun.cn/index.html

https://www.zhoulujun.cn/index.php

http://zhoulujun.cn/index.html

https://zhoulujun.cn/index.html

……

这些页面均为重复页面,再看

https://www.zhoulujun.cn/?a=1&b=2&****

https://www.zhoulujun.cn/?index.phpa=1&b=2&****

以及CDN转发,功能切分多域名转发,负载均衡,路径优化,如此等……

nginx,也没有那么容易,先从转发开始细讲,我觉得下面的内容,基本覆盖了一个资深程序员或运维的日常需求。这里把功能点都做了分类、笔记提示等,不足之处也请道友们补充。闲暇之余,希望把nginx系统地梳理一遍

timg.jpg

nginx正则表达式在location匹配规则及优先级

  • =   精确匹配        严格匹配这个查询。如果找到,停止搜索

  • ~   正则匹配        为区分大小写的正则匹配

  • ^~ 优先前缀匹配 匹配路径的前缀,如果找到,停止搜索

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

  • !~和!~*                分别为区分大小写不匹配及不区分大小写不匹

  • /                           任何请求都会匹配

优先级: =, ^~, ~/~*, 无

具体可以参考:Nginx Location 路径匹配优先级

nginx文件及目录匹配

  • -f和!-f用来判断是否存在文件

  • -d和!-d用来判断是否存在目录

  • -e和!-e用来判断是否存在文件或目录

  • -x和!-x用来判断文件是否可执行


请求URI(路径)规范化。

所谓规范化,就是先将URI中形如“%XX”的编码字符进行解码,再解析URI中的相对路径“.”和“..”部分, 另外还可能会压缩相邻的两个或多个斜线成为一个斜线。

举例说明:若REQUEST_URI为//trip/t.php,则规范化后为/trip/t.php,Nginx将规范前的值存放在$request_uri中,而规范化后的值存放在$uri中。

其中,$request_uri和$uri为Nginx内嵌变量。

请求URI路径匹配

首先需要明确Nginx中将路径匹配分为两类:

  • 前缀路径匹配,即前缀字符串定义的路径,如上配置文件中“/,/static/js/,/static/css/,/api,/trip/”

  • 正则表达式路径匹配,即使用正则表达式需要在路径开始添加“~*”前缀 (不区分大小写),或者“~”前缀(区分大小写)。如上配置文件中“/\.ht,^/~([^/]+)(/?.*)$,\.do$,/trip/, \.php$,\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar||bmp|rtf|js|mov)”

其次为了根据请求URI查找路径,需要明确路径匹配的顺序:

Nginx首先检查前缀字符串定义的路径 (前缀路径),在这些路径中找到能最精确匹配请求URI的路径。然后Nginx按在配置文件中的出现顺序检查正则表达式路径,匹配上某个路径后即停止匹配并使用该路径的配置否则使用最大前缀匹配的路径的配置


举例说明:请求/trip/t.php,首先进行前缀路径匹配,最精确的前缀路径为/trip/,接下来进行正则表达式匹配,匹配到\.php$,从而进行location ~ \.php$ { }处理请求。反之若请求的是/trip/t.html,由于没有正则表达式匹配到该URI,故匹配最精确的前缀路径匹配,即进入location /trip/ { }处理请求。若想不论是请求/trip/t.php,还是/trip/t.html,都匹配到/trip/进行处理,则可以使用location ^~ /trip/ { },这样Nginx就不会再检查正则表达式了。


Nginx虚拟目录alias和root目录

nginx是通过alias设置虚拟目录,在nginx的配置中,alias目录和root目录是有区别的:

  • 1)alias指定的目录是准确的,即location匹配访问的path目录下的文件直接是在alias目录下查找的;

  • 2)root指定的目录是location匹配访问的path目录的上一级目录,这个path目录一定要是真实存在root指定目录下的;

  • 3)使用alias标签的目录块中不能使用rewrite的break(具体原因不明);另外,alias指定的目录后面必须要加上"/"符号!!

  • 4)alias虚拟目录配置中,location匹配的path目录如果后面不带"/",那么访问的url地址中这个path目录后面加不加"/"不影响访问,访问时它会自动加上"/";

  •     但是如果location匹配的path目录后面加上"/",那么访问的url地址中这个path目录必须要加上"/",访问时它不会自动加上"/"。如果不加上"/",访问就会失败!

  • 5)root目录配置中,location匹配的path目录后面带不带"/",都不会影响访问。

一般情况下,在nginx配置中的良好习惯是:

  • 1)在location /中配置root目录;

  • 2)在location /path中配置alias虚拟目录。


Nginx指令详解

if指令

使用环境:server,location

该指令用于检查一个条件是否符合,如果条件符合,则执行大括号内的语句。If指令不支持嵌套,不支持多个条件&&和||处理。

return指令

语法:returncode ;

使用环境:server,location,if;

该指令用于结束规则的执行并返回状态码给客户端

Set指令

语法:setvariable value ; 默认值:none; 使用环境:server,location,if;

该指令用于定义一个变量,并给变量赋值。变量的值可以为文本、变量以及文本变量的联合。

示例:set$varname "hello world";

Uninitialized_variable_warn指令

语法:uninitialized_variable_warnon|off

使用环境:http,server,location,if

该指令用于开启和关闭未初始化变量的警告信息,默认值为开启。

rewrite 指令

语法:rewriteregex replacement flag

使用环境:server,location,if

该指令根据表达式来重定向URI,或者修改字符串。指令根据配置文件中的顺序来执行。注意重写表达式只对相对路径有效。


rewrite参数 flag标志位

在server块下,会优先执行rewrite部分,然后才会去匹配location块 

server中的rewrite break和last没什么区别,都会去匹配location,所以没必要用last再发起新的请求,可以留空

location中的rewirte:

不写last和break - 那么流程就是依次执行这些rewrite 

使用last和break实现URI重写,浏览器地址栏不变

  • break - url重写后,直接使用当前资源,不再执行location里余下的语句,完成本次请求,地址栏url不变 

  • last - url重写后,马上发起一个新的请求,再次进入server块,重试location匹配,超过10次匹配不到报500错误,地址栏url不变。牢记:使用last会对server标签重新发起请求

使用redirect 和permanent 实现URI重写,浏览器以返回的新地址重新发起请求

  • redirect – 返回302临时重定向,地址栏显示重定向后的url,爬虫不会更新url(因为是临时) 

  • permanent – 返回301永久重定向, 地址栏显示重定向后的url,爬虫更新url

last 和 break 总结如下:

1、last 和 break 当出现在location 之外时,两者的作用是一致的没有任何差异

注意一点就是,他们会跳过所有的在他们之后的rewrite 模块中的指令,去选择自己匹配的location

        rewrite url1 url2 last; ①

        rewrite url3 url4 last; ②

        rewrite url5 url6 last; ③

        location ~  url2     ④  

        location ~  url4     ⑤

        location ~  url6     ⑥

当① 这条rewrite 规则生效后,它后面的②和③ 将被跳过不做判断,而去直接选择 后面的location。

这里可能有一个疑问,那些指令输入rewrite 模块中的指令呢? 若是使用nginx本身,你就要到官网上去查询了。

但如果你使用的是tengine ,可以使用tengine -V 。会将你想要的信息列举出来。


放在server块rewrite语句前面 :如果是直接请求某个真实存在的文件,则用break语句停止rewrite检查 

    if (-f $request_filename) { 

        break; 

    }

2、last 和 break 当出现在location 内部时,两者就存在了差异。

   last: 使用了last 指令,rewrite 后会跳出location 作用域,重新开始再走一次刚刚的行为break: 使用了break 指令,rewrite后不会跳出location 作用域。它的生命也在这个location中终结。

        rewrite xxx1 yyy last; ⑦

        rewrite xxx2 yyy last; ⑧

        rewrite xxx3 yyy last; ⑨

        rewrite xxx4 yyy last; ⑩

        location ~  url1 {

            rewrite url1 url2 last; ①

        }

        location ~  url2  {

            rewrite url3 url4 break; ②

            fastcgi_pass 127.0.0.1:9000;

        }

以上事例:

第一个location 中的 rewrite 指令处理完成之后,会跳出location ,再重新判断rewrite 7 ~ 9 的规则。

第二个location 中的 rewrite  指令处理完成之后,不会跳出location, 更不会重新判断rewrite 7 ~ 9 的规则。而只能将

信息传递给后面的fastcgi_pass 或者proxy_pass 等指令

牢记:使用last会对server标签重新发起请求

  • 如果location中rewrite后是对静态资源的请求,不需要再进行其他匹配,一般要使用break或不写,直接使用当前location中的数据源,完成本次请求 

  • 如果location中rewrite后,还需要进行其他处理,如动态fastcgi请求(.php,.jsp)等,要用last继续发起新的请求

  • 使用alias指定源:必须使用last

  • 使用proxy_pass指令时,需要使用break标记。


permanent 和 redirect 总结如下:

permanent: 大家公认的信息 ,永久性重定向。请求日志中的状态码为301

redirect: 大家公认的信息 ,临时重定向。请求日志中的状态码为302

从实现功能的角度上去看,permanent 和 redirect 是一样的。不存在哪里好,哪里坏。也不存在什么性能上的问题。

但从SEO(或者是百度爬你的网站时)。 类似于这样的东西,会对你到底是永久性重定向还是临时重定向感兴趣。了解不到,需要深入,就google 吧。

last 和 break VS permanent 和 redirect 

在 permanent 和 redirect  中提到了 状态码 301 和 302。 那么last 和 break 想对于的访问日志的请求状态码又是多少呢?

答案为: 200

这两类关键字,我们能够眼睛看到的差异是什么呢? 我举个例子说明吧:

当你打开一个网页,同时打开debug 模式时,会发现301 和 302 时的行为是这样的。第一个请求301 或者 302 后,浏览器重新获取了一个新的URL ,然后会对这个新的URL 重新进行访问。所以当你配置的是permanent 和 redirect ,你对一个URL 的访问请求,落到服务器上至少为2次

而当你配置了last 或者是break 时,你最终的URL 确定下来后,不会将这个URL返回给浏览器,而是将其扔给了fastcgi_pass或者是proxy_pass指令去处理。请求一个URL ,落到服务器上的次数就为1次。




nginx内置变量

内置变量存放在  ngx_http_core_module 模块中,下面我来把这些变量分类记忆下,这里包括日常运维的内置变量讲解

nginx地址栏系统内置变量匹配


https://www.zhoulujun.cn/index.php?m=content&c=index&a=lists&catid=58的匹配顺序为例:

$scheme 请求使用的Web协议, “http” 或 “https”

$host 请求中的主机头(Host)字段,如果请求中的主机头不可用或者空,则为处理请求的server名称(处理请求的server的server_name指令的值)。值为小写,不包含端口。

$hostname  主机名,机器名使用 gethostname系统调用的值


$document_uri 与$uri相同。请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如”/foo/bar.html”。

$document_root 当前请求的文档根目录或别名——当前请求在root指令中指定的值。


$args 这个变量等于GET请求中的参数。$query_string 与$args相同。例如,foo=123&bar=blahblah;这个变量只可以被修改

$arg_name请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name

$is_args 如果$args设置,值为"?",否则为""。


$cookie_COOKIE cookie COOKIE的值。

$cookie_name cookie名称

nginx服务端参数内置变量匹配


$server_protocol 服务器的HTTP版本, 通常为 “HTTP/1.0” 或 “HTTP/1.1”

$server_nam 服务器名,如www.zhoulujun.cn

$server_addr 服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中。

$server_port 服务器端口

$status HTTP响应代码 (1.3.2, 1.2.2)

$https 如果开启了SSL安全模式,值为“on”,否则为空字符串。

nginx客户端参数内置变量匹配

$remote_addr 客户端的IP地址。

$remote_port 客户端的端口。

$remote_user 用于HTTP基础认证服务的用户名,已经经过Auth Basic Module验证的用户名。

$request代表客户端的请求地址

$request_filename 当前连接请求的文件路径,由root或alias指令与URI请求生成。

$realpath_root当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。

$request_body 客户端的请求主体,此变量可在location中使用,将请求主体通过proxy_pass, fastcgi_pass, uwsgi_pass, 和 scgi_pass传递给下一级的代理服务器。这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义。

$request_body_file 客户端请求主体信息的临时文件名。将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off 。

$request_completion 如果请求成功,设为"OK";如果请求未完成或者不是一系列请求中最后一部分则设为空。

$request_method 这个变量是客户端请求的动作,通常为GET或POST。包括0.8.20及之前的版本中,这个变量总为main request中的动作,如果当前请求是一个子请求,并不使用这个当前请求的动作。


$http_HEADER   HTTP请求头中的内容,HEADER为HTTP请求中的内容转为小写,-变为_(破折号变为下划线),例如:$http_user_agent(Uaer-Agent的值), $http_referer...;

$http_name匹配任意请求头字段; 变量名中的后半部分“name”可以替换成任意请求头字段,如在配置文件中需要获取http请求头:“Accept-Language”,那么将“-”替换为下划线,大写字母替换为小写,形如:$http_accept_language即可。

$sent_http_HEADER  HTTP响应头中的内容,HEADER为HTTP响应中的内容转为小写,-变为_(破折号变为下划线),例如: $sent_http_cache_control, $sent_http_content_type...;


nginx运维及系统状态内置变量匹配

$nginx_version 当前运行的nginx版本号。

$time_iso8601 服务器时间的ISO 8610格式 (1.3.12, 1.2.7)

$msec 当前的Unix时间戳 (1.3.9, 1.2.6)

$pid工作进程的PID


$limit_rate 用于设置响应的速度限制

$binary_remote_addr 二进制码形式的客户端地址。

$body_bytes_sent 传送页面的字节数


$connection TCP连接的序列号 (1.3.8, 1.2.5)

$connection_requests TCP连接当前的请求数量 (1.3.8, 1.2.5)

$content_length 请求头中的Content-length字段。

$content_type 请求头中的Content-Type字段。


nginx常用配置案例参考



#多目录转成参数

abc.domian.com/sort/2 => abc.domian.com/index.php?act=sort&name=abc&id=2

    if ($host ~* (.*)\.domain\.com) {

        set $sub_name $1;

        rewrite ^/sort\/(\d+)\/?$ /index.php?act=sort&cid=$sub_name&id=$1 last;

    }

#目录对换

/123456/xxxx -> /xxxx?id=123456

        rewrite ^/(\d+)/(.+)/ /$2?id=$1 last;

#ie用户使用重定向到/nginx-ie目录下:

    if ($http_user_agent ~ MSIE) {

        rewrite ^(.*)$ /nginx-ie/$1 break;

    }

#目录自动加“/”

    if (-d $request_filename){

        rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent;

    }

#禁止多个目录

    location ~ ^/(cron|templates)/ {

        deny all;

        break;

    }

#错页面如40x.html,50x.html设置

    error_page  404 403  /40x.html;

    # 承接上面的location。

    location = /40x.html {

    # 放错误页面的目录路径。

        root  /data/wwwroot/zhoulujun/;


    }

#只充许固定ip访问网站,并加上密码

        root  /opt/htdocs/www;

        allow   208.97.167.194;

        allow   222.33.1.2;

        allow   231.152.49.4;

        deny    all;

        auth_basic “C1G_ADMIN”;

        auth_basic_user_file htpasswd;

#将多级目录下的文件转成一个文件,增强seo效果

/job-123-456-789.html 指向/job/123/456/789.html

        rewrite ^/job-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /job/$1/$2/jobshow_$3.html last;

        

#域名跳转

    server {

        listen       80;

        server_name  jump.88dgw.com;

        index index.html index.htm index.php;

        root  /opt/lampp/htdocs/www;

        rewrite ^/ http://www.88dgw.com/;

        access_log  off;

    }

#去掉php页面

    if ($request_uri ~* "^(.*/)index\.php$") {

        return 301 $1;

    }

#index跳转到域名下

    location  /index.html {

        root /;

        rewrite ^/index.html$ / permanent;

    }

# Remove trailing slash. 去除末尾斜杠

    if (!-d $request_filename) {

        rewrite ^/(.+)/$ /$1 permanent;

    }

# Clean Double Slashes

    if ($request_uri ~* "\/\/") {

      rewrite ^/(.*) /$1 permanent;

    }

# 旧站资源转发,移除一个zhoulun目录 

    location  /zhoulujun/html/ {

        root /;

        rewrite ^/zhoulujun/html/(.*)$ /html/$1 permanent;

    }

    location  /zhoulujun/uploadfile/ {

        root /;

        rewrite ^/zhoulujun/uploadfile/(.*)$ /uploadfile/$1 break;

    }

#多域名转向

    server {

        server_name  www.7oom.com/  www.divmy.com/;

        index index.html index.htm index.php;

        root  /opt/lampp/htdocs;

        if ($host ~ “c1gstudio\.net”) {

            rewrite ^(.*) http://www.7oom.com$1/ permanent;

        }

    }

#三级域名跳转

        if ($http_host ~* “^(.*)\.i\.c1gstudio\.com$”) {

            rewrite ^(.*) http://top.88dgw.com$1/;

            break;

        }

#域名镜向

    server {

        listen       80;

        server_name  mirror.c1gstudio.com;

        index index.html index.htm index.php;

        root  /opt/lampp/htdocs/www;

        rewrite ^/(.*) http://www.divmy.com/$1 last;

        access_log  off;

    }

#某个子目录作镜向

        location ^~ /zhaopinhui {

        rewrite ^.+ http://zph.divmy.com/ last;

        break;


#正则匹配 

    location  = / {

      # 精确匹配 / ,主机名后面不能带任何字符串

      [ configuration A ]

    }

    

    location  / {

      # 因为所有的地址都以 / 开头,所以这条规则将匹配到所有请求

      # 但是正则和最长字符串会优先匹配

      [ configuration B ]

    }

    

    location /documents/ {

      # 匹配任何以 /documents/ 开头的地址,匹配符合以后,还要继续往下搜索

      # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条

      [ configuration C ]

    }

    

    location ~ /documents/Abc {

      # 匹配任何以 /documents/Abc 开头的地址,匹配符合以后,还要继续往下搜索

      # 只有后面的正则表达式没有匹配到时,这一条才会采用这一条

      [ configuration CC ]

    }

    

    location ^~ /images/ {

      # 匹配任何以 /images/ 开头的地址,匹配符合以后,停止往下搜索正则,采用这一条。

      [ configuration D ]

    }

    

    location ~* \.(gif|jpg|jpeg)$ {

      # 匹配所有以 gif,jpg或jpeg 结尾的请求

      # 然而,所有请求 /images/ 下的图片会被 config D 处理,因为 ^~ 到达不了这一条正则

      [ configuration E ]

    }

    

    location /images/ {

      # 字符匹配到 /images/,继续往下,会发现 ^~ 存在

      [ configuration F ]

    }

    

    location /images/abc {

      # 最长字符匹配到 /images/abc,继续往下,会发现 ^~ 存在

      # F与G的放置顺序是没有关系的

      [ configuration G ]

    }

    

    location ~ /images/abc/ {

      # 只有去掉 config D 才有效:先最长匹配 config G 开头的地址,继续往下搜索,匹配到这一条正则,采用

        [ configuration H ]

    }


参考文章:

最新版 nginx内置变量 大全

Nginx 中last和break 及 permanent 和 redirect 的爱恨情仇

nginx rewrite规则

Nginx路径匹配规则详解

Nginx虚拟目录alias和root目录

Nginx Location 路径匹配优先级