Nginx源码安装自动升级脚本

一个自动升级源码安装的nginx的脚本

脚本可以获取当前nginx版本、官网最新版本
如果有更新,则会自动下载、解压,并根据当前的配置进行编译,安装
最后会重启nginx,并删除下载的文件


#!/bin/bash
nginx=/usr/sbin/nginx;
localVersion=`nginx -v 2>&1 | cut -c22-`
serverVersion=`curl https://nginx.org/en/CHANGES > .nginxupdate 2>/dev/null && sed -n 2p .nginxupdate | cut -d' ' -f4`
echo 当前安装版本为:$localVersion,最新版本为$serverVersion
rm -rf .nginxupdate
if [ "$localVersion" = "$serverVersion" ]; then
echo 当前已是最新版本
exit 0
fi
echo 正在下载最新版本$serverVersion
wget https://nginx.org/download/nginx-$serverVersion.tar.gz && tar -xzvf nginx-$serverVersion.tar.gz
cd nginx-$serverVersion
./configure `nginx -V 2>&1 | sed -n 5p | cut -d':' -f2-`
make && make install
$nginx -s stop && $nginx
cd ..
rm -rf nginx-$serverVersion.tar.gz
rm -rf nginx-$serverVersion
echo 升级完毕
$nginx -V

使用OpenSSL为Nginx签发自签名证书

本文介绍使用openssl命令为nginx生成自签名的证书。注:仅用于本地测试使用,自签名证书不被浏览器认可,不能应用于生产环境。

一、生成RSA私钥
openssl genrsa -out local.lyz810.com.key 2048
上述命令在当前目录下使用RSA2048算法生成一个文件名为local.lyz810.com.key的pem格式的私钥。

二、使用生成好的私钥签发证书
openssl req -new -x509 -days 3650 -key local.lyz810.com.key -out lyz810.com.crt
输入命令后,会提示输入一些信息,由于是测试使用,可以任意填写。
配置好后添加到nginx上,浏览器打开会提示证书错误,选择继续访问即可。

三、openssl genrsa用法说明
genrsa的语法如下:

openssl genrsa [-help] [-out filename] [-passout arg] [-aes128] [-aes192] [-aes256] [-camellia128] [-camellia192] [-camellia256] [-des] [-des3] [-idea] [-f4] [-3] [-rand file(s)] [-engine id] [numbits]

参数详解:
-help:打印帮助信息
-out filename:生成文件的名称
-passout arg:生成的文件的短语密码源
-aes128|-aes192|-aes256|-camellia128|-camellia192|-camellia256|-des|-des3|-idea:这些选项会在输出私钥文件前使用该参数的加密算法将私钥加密。如果没有这些参数中的一个,则生成的私钥没有密码保护。如果没有通过passout指定短语密码,则会提示输入短语密码用于加密。
-F4|-3:使用的公开指数,值为65537或3,默认为65537
-rand files(s):一个或多个包含用于随机数发生器播种的随机数据的文件或EGD socket。多个文件之间的分隔符根据系统不同而不同,Windows中是分号“;”,OpenVMS中是逗号“,”,其他系统中是冒号“:”。
-engine id:通过唯一的id指定一个引擎。
-numbits:私钥长度,默认是512位,处于安全建议1024以上,不过测试环境可以忽略。

四、openssl req用法说明
req的语法如下:

openssl req [-help] [-inform PEM|DER] [-outform PEM|DER] [-in filename] [-passin arg] [-out filename] [-passout arg] [-text] [-pubkey] [-noout] [-verify] [-modulus] [-new] [-rand file(s)] [-newkey rsa:bits] [-newkey alg:file] [-nodes] [-key filename] [-keyform PEM|DER] [-keyout filename] [-keygen_engine id] [-[digest]] [-config filename] [-multivalue-rdn] [-x509] [-days n] [-set_serial n] [-newhdr] [-extensions section] [-reqexts section] [-utf8] [-nameopt] [-reqopt] [-subject] [-subj arg] [-batch] [-verbose] [-engine id]

参数详解:
-help:打印帮助信息
-inform PEM|DER:私钥的格式,默认为PEM,第一步到处的证书即PEM格式,因此此参数省略。
-outform PEM|DER:输出的格式,含义同上,默认PEM。
-in filename:指定读取请求的文件名,只有在没有指定-new和-newkey参数时有效。如果没有指定文件名,则从标准输入中获取。
-passin arg:输入文件的密码源
-out filename:输出文件名
-passout arg:输出文件的密码源
-text:打印请求文件的文本格式
-subject:打印请求文件的主题(如果有-x509则为证书的主题)
-pubkey:输出公钥
-noout:禁止输出经过编码版本的请求
-modulus:打印请求中包含的公钥系数值
-verify:验证请求的签名
-new:该选项生成一个新的证书请求。它会让用户输入相关域的信息。如果没有key选项则会根据配置文件生成一个私钥。
-rand file(s):参见genrsa中该参数的说明
-newkey arg:该选项创建一个新证书请求和一个新的私钥。参数使用以下几种形式之一,rsa:nbits,nbits为比特位数,用于生成RSA密钥长度,如果忽略nbits,则使用配置文件中的默认大小。
其他所有算法支持-newkey alg:file格式,file是算法参数文件,通过genpkey -genparam命令或X.509证书适当的秘钥算法。
param:file:使用参数文件或证书文件生成的密钥文件,算法取决于参数。
algname:file:使用algname算法和file参数文件。
dsa:filename:使用filename文件中的参数生成DSA密钥。
-pkeyopt opt:value:设置公钥的选项opt的值value。
-key filename:指定私钥的文件名,私钥格式默认为PEM。
-keyform PEM|DER:指定私钥的格式
-keyout filename:在指定文件中写入新创建的私钥,如未指定,则使用配置文件中的设置。
-nodes:该参数指定后,如果生成私钥则不加密。
-[digest]:用于签名请求信息的摘要
-config filename:配置文件的位置,可选项。
-subj arg:设置主题,格式/type0=value0/type1=value1/type2=…,字符可以用\转义,没有空格会被跳过。
-multivalue-rdn:该选项会使-subj参数解释为完全支持多值RDN,例如/DC=org/DC=OpenSSL/DC=users/UID=123456+CN=John Doe,如果没有使用该参数,则UID的值是123456+CN=John Doe
-x509:该参数说明输出是一个自签名证书而不是证书请求。
-days n:有效期天数,默认为30天。
-set_serial n:输出自签名证书用的序列号,可以是十进制的值或0x开头的十六进制的值。
其他参数略。

nginx代理入门

本文通过几个的实例,介绍nginx配置代理的几种方式,以及全局代理的配置。

注:本文中所有实例均可以直接使用,不需要改变任何配置(包括域名,域名解析到127.0.0.1,注意:由于https证书自动续期的需要,仅国内线路的*.local.lyz810.com指向127.0.0.1,国外线路已变更为服务器的IP地址
原始站点(被代理站点)
测试后面所有实例时,请确保该源站配置存在

server {
    listen 80;
    server_name origin.local.lyz810.com;
    default_type text/plain;
    location / {
        return 200 "This is origin.local.lyz810.com$request_uri";
    }

    location =/setcookie/ {
        add_header Set-Cookie "testcookie=123; domain=origin.local.lyz810.com; path=/";
        return 200 "Please find result in response header";
    }

    location =/showcookie/ {
        return 200 $http_cookie;
    }
}

当访问origin.local.lyz810.com的任意页面,都可以看到This is origin.local.lyz810.com后面跟着url

实例一:
通过其他域名进行代理(如目前线上访问google.lyz810.com即可实现代理google.com.hk)
代理配置:

server {
    listen 80;
    server_name proxy.local.lyz810.com;

    location / {
        proxy_pass http://origin.local.lyz810.com/;
    }
}

访问proxy.local.lyz810.com的任意页面,会对应访问origin.local.lyz810.com的页面
如访问proxy.local.lyz810.com/test,返回This is origin.local.lyz810.com/test

实例二:
代理服务器上的url与源站url存在差异,访问proxy.local.lyz810/test/下的所有页面,要求返回origin.local.lyz810.com/hello/对应页面(test改为源站的hello)
代理配置:

server {
    listen 80;
    server_name proxy.local.lyz810.com;

    location /test/ {
        proxy_pass http://origin.local.lyz810.com/hello/;
    }
}

请注意hello后面的/,这个是必须有的,nginx在处理url时,会将location匹配的路径从访问url中去掉后拼接到proxy_pass指令后面
例如访问proxy.local.lyz810.com/test/abc.html,匹配location为/test/,把匹配的部分从url中去掉,还剩abc.html,直接拼接到proxy_pass后面,即http://origin.local.lyz810.com/hello/abc.html
一种典型的错误写法:

    ...
    location /test {
        proxy_pass http://origin.local.lyz810.com/hello/;
    }
    ...

上面的写法语法上没有问题,但是跟需求不匹配。也许你会发现,这个错误的写法在访问proxy.local.lyz810.com/test/abc.html时仍然返回This is origin.local.lyz810.com/hello//abc.html(nginx会对url进行标准化处理,两个/跟一个/访问的是同一个资源)
这是由于hello以/结尾,nginx会将/test后面的内容直接接到proxy_pass最后,造成2个/
但如果访问proxy.local.lyz810.com/test123/abc.html则会返回http://origin.local.lyz810.com/hello/123/abc.html,这个与预期不符。

实例三:
源站要设cookie,由于域名不同,跨域无法设置cookie,通过nginx可以解决这个问题。如访问origin.local.lyz810.com/setcookie/后会在origin.local.lyz810.com域下根目录种一个cookie,现在希望通过代理,访问源站,并在proxy.local.lyz810.com的/test上种上相同的cookie,配置如下:

server {
    listen 80;
    server_name proxy.local.lyz810.com;

    location / {
        proxy_pass http://origin.local.lyz810.com/;
        proxy_cookie_domain origin.local.lyz810.com $host;
        proxy_cookie_path / /test;
    }
}

访问proxy.local.lyz810.com/setcookie/,查看响应头(Set-Cookie:testcookie=123; domain=proxy.local.lyz810.com; path=/test)。
这种使用场景是使用一个域名代理另一个域名的页面,并可以代理认证信息。
我们知道,浏览器是按照域名携带cookie的,所以访问proxy.local.lyz810.com的时候只能带着proxy.local.lyz810.com的cookie访问源站,显然用户不可能自己在proxy.local.lyz810.com上添加cookie,所以我们需要把访问源站登录时,设置的cookie转换为代理域下的cookie,这样访问代理域就和访问源站域完全一样了,相当于镜像站。
通常做完整镜像站时,只需要将cookie的domain修改为代理域即可,path保持一致不需要配置。

实例四:
有时候,页面上的js会对域名做判断,上面所有的方法只能骗过源站的服务器,而不能骗过浏览器的location.hostname,这种情况下,nginx是无法完美解决,如果是开发调试,可以通过配置host来实现。
例如,我的项目地址是demo.lyz810.com,我负责前端开发,需要调用后端接口(后端接口都在/fetch/下)demo.lyz810.com/fetch/api.php?action=getJson(完全可以通过实例三的方法设置另一个域,然后做cookie共享,但我不喜欢开发和线上访问的不一样),那么可以通过下面的方式来实现:
1.设置host 127.0.0.1 demo.lyz810.com
2.nginx配置如下:

server {
    listen 80;
    server_name demo.lyz810.com;
    default_type text/html;
    location /fetch/ {
        proxy_pass http://133.130.97.238/fetch/;
        proxy_set_header Host $host;
    }

    location / {
        return 200 "Please open console to see result<script src='/fetch/api.php?action=getJson'></script>";
    }
}

访问demo.lyz810.com,打开浏览器的控制台,看到发送请求/fetch/api.php返回了信息(这个是线上真实的接口数据),本例中使用了return指令直接返回了一段html代码,正常开发时,此处一般是由root指令指定的静态文件目录,也就是我们的工程目录。
几点说明:
1.proxy_pass需要填写服务器的IP地址,因为本机设置了host,并且nginx在本机部署,所以会受hosts的影响
2.proxy_set_header是nginx访问线上服务器时携带的请求头,因为上面写的是IP,而服务器不能直接通过IP访问(服务器上挂了那么多站点,你用IP访问,它也不知道返回哪个站点),加上Host这个请求头,服务器就知道返回哪个站点下的资源。

实例五:
终极代理大法一,访问本机8888端口,代理任何网站(需要配hosts文件)
代理配置:

server {
    listen 8888;
    server_name _ default;
    resolver 119.29.29.29;
    location / {
        proxy_pass https://$host$request_uri$is_args$args;
    }
}

绑定hosts:127.0.0.1 www.sogou.com,访问http://www.sogou.com:8888/web?query=lyz810&_asf=www.sogou.com可以看到结果,这里用80端口也是完全可以的,hosts文件只要指向本机就可以,如果是局域网内其他机器访问,可以直接配置hosts文件指向nginx服务器的IP,可以通过nginx代理任意站点。
注意:server_name 后面的default表示如果没有匹配到其他的域名,就用当前server,nginx配置文件中同一个端口最多有1个default的server,否则后面设置的default会无效。proxy_pass中域名使用了变量,所以需要配置resolver,即dns服务器地址,这里用的是腾讯云提供的公共dns。此例中代理走https协议,因为目前大部分网站支持https,这里暂且用https,后面有更灵活配置方式。
这种配置的一种应用是共享账号,可以登录某个网站后,将cookie记下来,配置在服务端的proxy_set_header Cookie …中,其他人访问该代理服务器,不需要登录就可以访问公共账号登录的内容。

实例六:
全局代理,无需配置hosts

server {
    listen 80;
    server_name proxy.local.lyz810.com;
    resolver 119.29.29.29;

    location ~ ^/([^/]*)(.*)$ {
        proxy_pass $scheme://$1$2$is_args$args;
    }
}

注意:这种方法只适合于代理任意已知地址,由于网页中很少使用相对地址,所以一般不能直接通过这种代理访问网站,仅限开发调试使用。
这里给出的例子并不完美,转发使用了$scheme根据访问的协议进行同协议转发,但只监听了80端口,所以此处一定为http,可以同时监听443并开启ssl,同时设置证书,已超出本文的内容,不再赘述。
这种配置也有应用场景,目前用于统一代理某些已知接口,不需要每次修改配置文件,只需要写好正确的地址,即可拿到数据。
线上应用在proxy.lyz810.com上,通过此服务器进行跨域管理,所有需要跨域访问的内容交给服务器去访问,返回的结果通过add_header增加跨域头,这样就可以让应用轻松的跨域,具体即实现方法以后介绍。

Nginx配置WordPress与HSTS

本文介绍HSTS基本概念,以及在nginx上配置HSTS的方法,配置站点基于WordPress。

本博客(https://blog.lyz810.com)已开启HSTS,读者可以尝试访问http协议,并观察浏览器的行为。
一、背景介绍
HSTS(HTTP Strict Transport Security)是HTTP严格传输安全,它是全站HTTPS时的一个更安全的策略,并且对网站性能有一定的优化。
全站HTTPS是一个必然的趋势,目前全站HTTPS通常的做法是同时开启80和443端口,当请求访问的是http协议时,通过rewrite将请求301重定向到https协议的对应URI。这么做的缺点是当用户访问http协议的页面时,总会有一次301重定向,增加网络请求以及服务器负担。
使用HSTS后,浏览器在第一次访问http协议时,会通过rewrite重定向到https协议,并根据https协议头中设定的相关响应头缓存下HSTS配置,下次用户再访问该站点下的任意http页面,浏览器会自动通过307跳转到https对应的URI上,不需要向服务器多发送一次请求。
HSTS配置方法如下:
1.当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议响应头中包含Strict-Transport-Security字段。非加密传输时设置的HSTS字段无效。
2.Strict-Transport-Security有3个参数,max-age表示有效期(如max-age=31536000表示未来的一年内,访问该域名会强制使用https,其中数字表示一年的秒数);includeSubDomains表示子域名也强制使用https;preload主要用于加入preload列表使用。
preload列表是一个站点的列表,他将会被通过硬编码写入 Chrome 浏览器中,列表中的站点将会默认使用 HTTPS 进行访问,此外,Firefox 、Safari 、IE 11 和 Edge 也同样一份 HSTS 站点列表,其中包括了 Chrome 的列表
加入preload列表的条件:

  • 有一张有效的证书
  • 重定向所有的 HTTP 流量到 HTTPS ( HTTPS ONLY )
  • 全部子域名的流量均通过 HTTPS ,如果子域名的 www 存在的话也同样需要通过 HTTPS 传输。
  • 为https协议添加的响应头Strict-Transport-Securit内容必须满足:max-age必须大于18周,必须指定includeSubDomains和preload,如果从当前https站点重定向到其他的站点,那个站点也必须启用HSTS

加入HSTS preload list:https://hstspreload.appspot.com/
二、Nginx配置

server {
  ......
  if ( $https != "on" ) {#https变量的值在https协议下为on,否则为空字符串
     rewrite ^(.*) https://$host$1 permanent;#301定向到https协议的相同URI,主要是首次访问本站的请求使用
     break;
  }

  location / {
     try_files $uri $uri/ /index.php$args;#此行是WordPress使用的,它会将请求都交给/index.php处理
     add_header Strict-Transport-Security max-age=86400000;#添加响应头,过期时间1000天,即1000天之内,浏览器会自动将本站http协议转为https协议
  }

  location ~ \.php$ {
     fastcgi_pass localhost:9000;#fastcgi监听9000端口,其他fastcgi配置写在了公用配置文件中
     add_header Strict-Transport-Security max-age=86400000;#由于正则的优先级更高,所以所有动态的页面均会走这里,需要添加响应头
   }
}

三、使用HSTS的弊端
用户只要访问一次站点,就会在max-age指定的时间内,强制访问https协议,在此期间,如果网站https出现了问题(如证书配置错误),用户就无法进行访问,没有配置HSTS时,证书错误用户可以选择忽略错误继续访问。
如果想要在过期时间内再次开启http访问,是无法做到的(对没有访问过站点的用户可行,只要访问过,过期时间前用户不能访问http协议),如果加入到HSTS Preload List中,就更没有办法启用http访问。
四、浏览器支持度
Chromium和Google Chrome从4.0.211.0版本开始支持HSTS
Firefox 4及以上版本
Opera 12及以上版本
Safari从OS X Mavericks起
Internet Explorer和Microsoft Edge从Windows 10开始支持

Nginx loaction优先级探究

本文通过配置实例来探究Nginx中location的优先级,在Nginx官方文档中,有优先级的说明,我们通过实例对此进行验证,加深理解。

实验声明:
return指令
该指令可以直接返回一个状态码及文本信息,由于是实验环境,没有必要建立真实的目录来对比location的命中,也免去了查看日志等繁琐的步骤。
因此,此实验均使用return指令来返回结果,用到的指令格式为return code text

实验环境
本文中使用的server_name采用的域名test.local.lyz810.com为真实域名,绑定IP为127.0.0.1,所以读者可以完全复制到本机实验。另外,*.local.lyz810.com域名做了泛解析,全部解析到127.0.0.1,读者可以任意选用该域名的子域名进行各种实验。

location类型

  1. 精确匹配:=
  2. 正则大小写敏感:~
  3. 正则大小写不敏感:~*
  4. 前缀匹配(高优先级):^~
  5. 前缀匹配:/

location优先级
以下是官网中给出的优先级:

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

翻译:
Nginx首先检查使用前缀定义的location,会选择并记住一个最长匹配的location作为候选。
检查正则表达式,根据出现在配置文件中的顺序。
遇到第一个匹配的正则表达式就停止搜索。
如果没有匹配的正则表达式,就使用最开始候选的location。

由此可以看出,正则表达式优先级最高,多个正则表达式按从上到下就先匹配到的为准,其次是前缀location。
注意,上面是普通前缀与正则之间的比较,所有location中精确匹配优先级最高。

实验一
目的:验证精确匹配优先级最高
原理:让所有类型的location出现在一个server中,并将精确匹配放到最后,消除顺序可能带来的影响,观察是否命中精确匹配location
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 prefix;
  }
  
  location ~ "/test\d{3}$" {
    return 200 regular;
  }

  location =/test123 {
    return 200 exact;
  }
}

结果:访问/test123,返回exact.说明精确匹配优先级最高

实验二
目的:验证正则的优先级高于前缀
原理:访问一个同时满足前缀和正则的页面,观察返回结果。为了排除顺序的影响,提供两个不同顺序。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 prefix;
  }
  
  location ~ "/test\d{3}" {
    return 200 regular;
  }

  location ~ "/welcome\d{3}$" {
    return 200 regular;
  }

  location /welcome123 {
    return 200 prefix;
  }
}

结果:访问/test123和/welcome123都返回regular,说明正则优先级高于前缀,并且与正则和前缀出现的顺序无关。

实验三
目的:验证前缀匹配只与匹配长度有关,与顺序无关。
原理:通过不同顺序的前缀匹配,观察输出结果。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 test123;
  }
  
  location /test1234 {
    return 200 test1234;
  }

  location /welcome1234 {
    return 200 welcome1234;
  }

  location /welcome123 {
    return 200 welcome123;
  }
}

结果:访问/welcome1234和/test1234分别返回welcome1234和test1234,与顺序无关,只与匹配长度有关。

实验四
目的:比较高优先级前缀(^~)与正则的优先级
原理:将正则location置于最前,高优先级前缀location置于后面,通过观察输出结果,比较优先级。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location ~ "/test\d{3}" {
    return 200 regular;
  }

  location ^~ /test123 {
    return 200 prefix;
  }
}

结果:访问/test123返回prefix,证明^~优先级高于正则

实验五
目的:一个配置文件了解location优先级
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location =/test12345 {
    return 200 =/test12345;
  }

  location /test12 {
    return 200 /test12;
  }

  location /test1234 {
    return 200 /test1234;
  }

  location ^~ /test123 {
    return 200 "^~/test123";
  }

  location ~ "/test\d{3}$" {
    return 200 "~/test\d{3}";
  }

  location ~ "/test\d{4}$" {
    return 200 "~/test\d{4}";
  }

  location ~ "/test\d*" {
    return 200 "~/test\d*";
  }
}

结果:
1.访问/test12返回~/test\d*,匹配项/test12、/test\d*,正则优先级高,故命中正则。
2.访问/test123返回^~/test123,匹配项/test12、^~/test123、~/test\d{3}、~/test\d*,先对前缀进行比较,发现有2个前缀/test12和^~/test123,后者长,用后者,然后发现前缀匹配带^~,不再查找正则,故命中^~/test123。
3.访问/test1234返回~/test\d{4},匹配项/test12、/test1234、^~/test123、~/test\d{4}、~/test\d*,先对前缀进行比较,发现有3个前缀/test12、/test1234、^~/test123,根据匹配长度,选中/test1234,再对正则进行匹配,~/test\d{4}是第一个匹配的正则,而之前选出的前缀中不带^~,故命中第一个匹配的正则。
4.访问/test12345返回=/test12345,匹配项=/test12345、/test12、/test1234、^~/test123、~/test\d*,有精确匹配存在,其他的都靠边站。
5.访问/test123456返回~/test\d*,匹配项/test12、/test1234、^~/test123、~/test\d*,前缀选择/test1234,无^~,命中第一个(唯一一个)匹配的正则表达式。
6.访问/welcome/test1234返回~/test\d{4},匹配项~/test\d{4}、~/test\d*,正则可以匹配path的一部分,而前缀只能从头开始匹配。
7.访问/test12345?params=1返回=/test12345,location匹配的是path部分,不包括后面的queryString。

总结
location匹配的算法描述如下(实验的出的结论,非源码中的算法):
1.令pathname=url路径部分,findPrefix=””
2.令prefixList=所有前缀location,regularList=所有正则location,exactList=所有精确匹配location
3.遍历exactList,查找location =pathname,如果找到,则命中,算法结束
4.遍历prefixList,如果匹配pathname则与findPrefix的长度进行比较
4.1如果长度大于findPrefix,令findPrefix=当前location
4.2如果长度小于findPrefix,不做任何操作,继续遍历
4.3如果长度等于findPrefix,在配置载入的时候,会报location重复的错误(如同时存在/123和^~/123),没有该分支
5.判断findPrefix是否带^~前缀,如果带,命中findPrefix,算法结束
6.遍历regularList,如果匹配pathname,命中当前location,算法结束
7.如果findPrefix不为空,命中findPrefix,算法结束
8.输出404

在CentOS7上的nginx中部署Let’s Encrypt免费证书

本文介绍如何在CentOS7上的nginx中部署Let‘s Encrypt免费证书。

之前一直使用的是沃通免费证书,最近看到网上的一些消息,担心Firefox和Chrome会取消它的根证书,所以准备逐步替换为Let’s Encrypt免费证书。目前已在https://demo.lyz810.com中使用,未来可能会逐步将其他域替换为Let’s Encrypt免费证书。
下面将操作步骤记录一下,以便后续替换时查阅。

一、安装certbot
文档:https://certbot.eff.org/#centosrhel7-nginx
$ sudo yum install epel-release
$ sudo yum install certbot

二、为域名申请一个证书
-w后面是站点根目录
-d后面是站点域名,如果多个域名,可以使用多个-d参数,每个-d参数跟一个域名,-d之间用空格分开
certbot certonly --webroot -w /website/lyz810-main/demo/ -d demo.lyz810.com

1.提示输入邮箱,用于紧急通知以及密钥恢复
2.阅读文档,选Agree即可

如果成功证书和私钥会保存在/etc/letsencrypt/live/demo.lyz810.com/中

三、nginx配置证书
ssl_certificate /etc/letsencrypt/live/demo.lyz810.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/demo.lyz810.com/privkey.pem;
重启nginx服务器

四、证书自动续期
证书有效期为90天,所以需要写一个定时任务

#minute   hour    day  month  week    command
0         0,12    *    *      *       certbot renew > /var/log/certbot.log & echo certbot last renew at `date` >> /var/log/certbot.log

在每天0点和12点会更新一次证书,并将结果保存到/var/log/certbot.log日志中。
注意:
如果设置cron或systemd任务,建议一天执行两次(如果没有到需要更新的时间段内,运行此命令不会更新证书)

Referrer策略与防盗链

Referrer策略用于控制浏览器在何种情况下发送referrer信息。该策略可以保护用户的隐私,但也可以使得目前绝大多数站点的防盗链机制失效。

一、介绍
从一个文档发出的请求,以及从文档导航到其他页面,都会有一个Referer头。出于以下原因,我们有时会希望浏览器不发送referer头:
1.隐私
一个社交网站会有每个用户的简介页面,用户会在他们的个人主页中添加一些链接。社交网站可能不希望泄露用户的个人主页URL给被链接的网站(因为个人主页URL可能会泄露其主人的身份信息)。
一些社交网站可能想通知其他网站该链接是从社交网站发起的,但不想泄露包含用户信息的链接(例如,微博中的链接希望告诉对方该链接是在微博中连接过来的,但不希望告诉对方从谁的微博连接过来)。

2.安全
一个网站应用使用https和基于URL的会话标识。应用也许希望链接其他站点的https资源但不想泄露位于URL中的用户会话标识符。
或者,应用可以使用一些url自身的能力(如重置密码的链接、通过链接直接登录账号等)。控制referrer有助于阻止这些有特殊能力的url泄露referrer头。
注意,有其他方式避免这些url的泄露,控制referrer并不足以控制所有的泄露情况。

3.引用
基于HTTPS的博客可能希望连接到一个HTTP上的博客并收到引用链接。

二、referrer策略
Referrer策略包含以下值:

  1. 空字符串
  2. no-referrer
  3. no-referrer-when-downgrade
  4. same-origin
  5. origin
  6. strict-origin
  7. origin-when-cross-origin
  8. strict-origin-when-cross-origin
  9. unsafe-url

下面会详细讲解每种Referrer策略。
1.no-referrer
最简单的策略是“no-referrer”,表示所有的请求都不带referrer。
http://www.lyz810.com/demo/referrer/index.php?referrer=no-referrer

2.no-referrer-when-downgrade
主要针对于受TLS保护的URL(如https),简单的说就是https的页面中,当连接的资源也是https的,则发送完整的referrer,如果连接的资源是http的,就不发送referrer
https://www.lyz810.com/demo/referrer/index.php?referrer=no-referrer-when-downgrade
此例中,音乐可以播放,因为他是http的,不发送referrer,iframe可以显示referrer,因为是https协议。
这个是在没有特别指定referrer策略时,浏览器的默认行为。

3.same-origin
对于同源的链接,会发送referrer,其他的不会。同源意味着域名需要相同,example.com和not.example.com是非同源的。
http://www.lyz810.com/demo/referrer/index.php?referrer=same-origin
上面的例子中可以看到,音乐无法播放了(因为是他站资源),而iframe嵌套的同源页面仍然可以读到referrer。

4.origin
这个策略对于任何资源来说只发送源的信息,不发送完整的url。
http://www.lyz810.com/demo/referrer/index.php?referrer=origin
此例中,音乐无法播放,因为它发送了referrer,iframe中显示的referrer只包含源的信息,不包含完整的url。

5.strict-origin(浏览器可能不支持)
这个策略类似于origin和no-referrer-when-downgrade的合体,如果一个https页面中链接到http的页面或资源,则不会发送referrer。http页面链接以及https链接到https都只发送来源页面的源信息。
https://www.lyz810.com/demo/referrer/index.php?referrer=strict-origin
此例中,音乐正常播放,因为是http的资源,不发送referrer,而iframe中只有源信息。

6.origin-when-cross-origin
该策略在同源的链接中发送完整的URL,其他情况仅发送源信息。相同的域名,http和https协议被认为是非同源的。
http://www.lyz810.com/demo/referrer/index.php?referrer=origin-when-cross-origin
此例中,音乐不能播放,发送源信息,iframe显示完整url。

7.strict-origin-when-cross-origin(浏览器可能不支持)
对于同源请求,发送完整的URL;对于同为https的,只发送源信息;对于http页面只发送源信息;https页面中的http请求不发送referrer。

8.unsafe-url
这个主要是解决https页面中的http资源不发referrer的问题,它会使在https页面中http资源发送完整的referrer。
https://www.lyz810.com/demo/referrer/index.php?referrer=unsafe-url
此例中,音乐不能播放,虽然页面是https,资源是http,但unsafe-url使得浏览器仍发送referrer。

9.空字符串
空字符串表示没有referrer策略,默认为no-referrer-when-downgrade。

三、用法
Referrer策略可以通过以下方法声明:
1.通过http请求头中的Referrer-Policy字段
2.通过meta标签,name为referrer
3.通过<a>、<area>、<img>、<iframe>、<link>元素的referrerpolicy属性。
4.通过<a>、<area><link>元素的rel=noreferrer属性
5.通过隐式继承

四、用法举例
1.http请求头
Referrer-Policy: no-referrer
2.meta标签
<meta name=”referrer” content=”no-referrer” />
3.referrerpolicy属性
<a href=“http://example.com” referrerpolicy=“origin”>
4.rel=noreferrer属性

五、注意事项
Referrer策略还有其他历史遗留的值:
1.never等价于no-referrer
2.default等价于no-referrer-when-downgrade
3.always等价于unsafe-url
4.不建议使用上面三个值,建议使用后面的新值

六、兼容性
IE:不支持(IE高版本中隐式支持default,https页面拉取的http资源不会加referrer)
Edge:仅支持较早版本的值(never、always、origin、default)
Firefox:36+
Chrome:21+
Safari:7.1+(仅支持较早版本的4个值)
Opera:15+
iOS Safari:8+(仅支持较早版本的4个值)

七、关于防盗链
目前大部分网站采用的是判断referrer是否是当前域名或指定白名单域名下的url。而没有referrer的请求都会放行。
referrer策略普及后,单从referrer判断防盗链的方法会失效,所以需要考虑其他的技术手段实现防盗链机制。

nginx中文文档-ngx_stream_split_clients_module

此页面版本:2016-08-23,57bc4b4b-98b
ngx_stream_split_clients_module模块(1.11.3+)创建用于A/B测试(也叫作分离测试)的变量。

示例配置

stream {
    ...
    split_clients "${remote_addr}AAA" $upstream {
                  0.5%                feature_test1;
                  2.0%                feature_test2;
                  *                   production;
    }

    server {
        ...
        proxy_pass $upstream;
    }
}

split_clients

语法:split_clients string $variable { … }
默认:—
上下文:stream

为A/B测试创建变量,例如:

split_clients "${remote_addr}AAA" $variant {
               0.5%               .one;
               2.0%               .two;
               *                  "";
}

原始字符串的值通过MurmurHash2进行哈希。在给出的示例中,哈希值从0到21474835(0.5%)对应的$variant变量值为“.one”,哈希值从21474836到107374180(2%)对应的值为“.two”,哈希值从107374181到4294967295对应的值为“”(空字符串)。

nginx中文文档-ngx_stream_geoip_module

此页面版本:2016-08-23,57bc4b4b-c73
ngx_stream_geoip_module模块(1.11.3+)创建变量,变量值依赖于客户端的IP地址,使用预编译的MaxMind数据库。

当使用的数据库支持IPv6时,IPv4的地址会被映射为IPv6的地址。
该模块默认不会构建,需要使用–with-stream_geoip_module参数进行编译。
该模块需要MaxMind GeoIP库。

示例配置

stream {
    geoip_country         GeoIP.dat;
    geoip_city            GeoLiteCity.dat;

    map $geoip_city_continent_code $nearest_server {
        default        example.com;
        EU          eu.example.com;
        NA          na.example.com;
        AS          as.example.com;
    }
   ...
}

geoip_country

语法:geoip_country file
默认:—
上下文:stream

指定一个数据库用于决定依赖于客户端IP的国家。下面的变量在使用数据库时可用:
$geoip_country_code
两个字母的国家代码,例如“RU”、“US”

$geoip_country_code3
三个字母的国家代码,例如“RUS”、“USA”

$geoip_country_name
国家名称,例如“Russian Federation”、“United States”

geoip_city

语法:geoip_city file
默认:—
上下文:stream

指定一个数据库用于决定依赖于客户端IP的国家区域以及城市。下面的变量在使用数据库时可用:

$geoip_area_code
电话区号(仅美国)
这个变量可能包含过时的信息,因为相应的数据库字段可能会过时。

$geoip_city_continent_code
两个字母的大陆编码,例如“EU”、“NA”

$geoip_city_country_code
两个字母的国家编码,例如“RU”、“US”

$geoip_city_country_code3
三个字母的国家编码,例如“RUS”、“USA”

$geoip_city_country_name
国家名称,例如“Russian Federation”、“United States”

$geoip_dma_code
美国DMA区域编码(也成为地铁编码),依据Google AdWords API

$geoip_latitude
纬度

$geoip_longitude
经度

$geoip_region
两个标识符的国家行政区代码(行政区、地域、州、省、联邦等),例如“48”、“DC”

$geoip_region_name
国家行政区名称(行政区、地域、州、省、联邦等),例如“Moscow City”、“District of Columbia”

$geoip_city
城市名称,例如“Moscow”、“Washington”

$geoip_postal_code
邮政编码

geoip_org

语法:geoip_org file
默认:—
上下文:stream

指定一个数据库,用于决定依赖于客户端IP地址的组织。下列变量在使用数据库时可用:

$geoip_org
组织名称,例如“The University of Melbourne”

nginx中文文档-ngx_stream_geo_module

此页面版本:2016-08-10,57ab4631-be8
ngx_stream_geo_module模块(1.11.3+)根据客户端IP地址创建变量。

示例配置:

geo $geo {
    default        0;

    127.0.0.1      2;
    192.168.1.0/24 1;
    10.1.0.0/16    1;

    ::1            2;
    2001:0db8::/32 1;
}

geo

语法:geo [$address] $variable { … }
默认:—
上下文:stream

描述指定变量的值与客户端IP地址之间的依赖关系。默认情况下,地址从$remote_addr变量中获取,但是它可以从另外的变量中获取,例如:

geo $arg_remote_addr $geo {
    ...;
}

由于变量在使用时才会解析,可以在geo中声明一个很大的变量映射表,不会引起任何额外的开销。
如果变量的值不是一个合法的IP地址,则使用“255.255.255.255”。
地址可以指定为CIDR地址块格式,也可以为范围。
下面特殊的参数同样受支持:
delete
删除指定的网络。

default
为那些没有匹配任何指定IP地址的客户端地址设置一个默认值。当地址是以CIDR方式指定,“0.0.0.0/0”和“::/0”用于替换默认值。当默认值没有指定,则默认值为空字符串。

include
包含一个含有地址和值的文件。可以有多个包含。

ranges
声明地址定义为一个范围。这个参数应该在最先出现。为了加快载入的速度,地址应该以升序排列。

例子:

geo $country {
    default        ZZ;
    include        conf/geo.conf;
    delete         127.0.0.0/16;

    127.0.0.0/24   US;
    127.0.0.1/32   RU;
    10.1.0.0/16    RU;
    192.168.1.0/24 UK;
}

conf/geo.conf文件包含以下内容:

10.2.0.0/16    RU;
192.168.2.0/24 RU;

最匹配的值将被使用。例如,地址127.0.0.1会使用“RU”,而不是“US”。

带范围的例子:

geo $country {
    ranges;
    default                   ZZ;
    127.0.0.0-127.0.0.0       US;
    127.0.0.1-127.0.0.1       RU;
    127.0.0.1-127.0.0.255     US;
    10.1.0.0-10.1.255.255     RU;
    192.168.1.0-192.168.1.255 UK;
}