题记


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

https://www.zhoulujun.cn/ <https://www.zhoulujun.cn/>

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

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

http://zhoulujun.cn/index.html <http://www.zhoulujun.cn/index.html>

https://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&****
<https://www.zhoulujun.cn/?index.phpa=1&b=2&****>

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


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



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

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

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


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

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

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

*
/                           任何请求都会匹配


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

具体可以参考:Nginx Location 路径匹配优先级 <https://www.jianshu.com/p/5b4067f9fbcc>

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
<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 <https://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运维百科全书
<https://www.zhoulujun.cn/html/tools/nginx/2018_0618_8124.html> -nginx文章列表
<https://www.zhoulujun.cn/index.php?m=content&c=index&a=lists&catid=163> -
周陆军的个人网站 <https://www.zhoulujun.cn/>,链接
https://www.zhoulujun.cn/html/tools/nginx/2018_0618_8124.html
<https://www.zhoulujun.cn/html/tools/nginx/2018_0618_8124.html>如果不妥之处,请告知,谢谢!


参考文章:

最新版 nginx内置变量 大全
<http://www.cnphp.info/nginx-embedded-variables-lasted-version.html?utm_source=tuicool&utm_medium=referral>

Nginx 中last和break 及 permanent 和 redirect 的爱恨情仇
<http://blog.51cto.com/unixman/1711943>

nginx rewrite规则 <https://www.cnblogs.com/cgli/archive/2011/05/16/2047920.html>

Nginx路径匹配规则详解 <http://www.open-open.com/lib/view/open1416193256961.html>

Nginx虚拟目录alias和root目录 <https://www.cnblogs.com/kevingrace/p/6187482.html>

Nginx Location 路径匹配优先级 <https://www.jianshu.com/p/5b4067f9fbcc>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信