题记
前段时间把网站迁移到腾讯云,之前是lamp,现在改为lnmp,自以为nginx功底还可以,开发这么多年,平常环境都有配置。但是,但是,最近读站点做SEO优化,发现nginx很多地方不会配。比如:
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转发,功能切分多域名转发,负载均衡,路径优化,如此等……
![](/qrcode.jpg)
nginx,也没有那么容易,先从转发开始细讲,我觉得下面的内容,基本覆盖了一个资深程序员或运维的日常需求。这里把功能点都做了分类、笔记提示等,不足之处也请道友们补充。闲暇之余,希望把nginx系统地梳理一遍
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运维百科全书 -nginx文章列表 - 周陆军的个人网站,链接https://www.zhoulujun.cn/html/tools/nginx/2018_0618_8124.html如果不妥之处,请告知,谢谢!
参考文章: