目录

Nginx 随记(二)—— 配置

注意
本文最后更新于 2017-02-15,文中内容可能已过时。

Nginx配置文件

Nginx官方文档上有更为详细的配置说明,在此将我用到的部分进行介绍。 本文介绍的版本是目前最新的稳定版nginx-1.10.3内容基础,基本上临近的版本都可以适用。

Nginx安装完后,默认的总配置文件在/etc/nginx/nginx.conf修改前,建议先copy一份进行备份。 cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak 以下是初始的内容:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";

	# gzip_vary on;
	# gzip_proxied any;
	# gzip_comp_level 6;
	# gzip_buffers 16 8k;
	# gzip_http_version 1.1;
	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

	##
	# Virtual Host Configs
	##



	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
# 
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}

在此对配置文件中的一些配置项进行说明。其他没介绍到的地方以后会开新的博文介绍。这个配置文件若无必要的改动,保持默认即可,为了增加配置文件的可读性,我们具体的网站配置在另一个配置文件上。

第1行 user www-data 此处的www-data是Nginx要使用的Linux用户名,当后面配置Nginx进行静态资源缓存时,出现访问静态资源404等情况时,首先检查文件的权限能否被这个用户所读取,最起码是需要读和执行权限的。

gzip on;此处是打开gzip压缩,在用户和上游服务器之间进行gzip压缩有利于减少带宽。值得一提的是,如果上游服务器是IIS,并且在IIS里配置了对URL出入站深度重写的话,是不允许打开gzip压缩的,否则IIS无法解析html的内容并且改写里面的超链接、静态资源引用等地址。

第63行 include /etc/nginx/conf.d/*.conf; 这行是引入我们网站的配置文件,为此我们需要在/etc/nginx/conf.d/下面新建一个配置文件,文件名要以.conf结尾,比如yyq.conf

接下来就是配置我们网站了。 #网站配置 在/etc/nginx/conf.d/yyq.conf里最基础的内容如下:

server
	{
		listen 80;
		access_log  /var/log/nginx/myblogaccess.log;
		error_log   /var/log/nginx/myblogerror.log;
		proxy_ignore_client_abort on;
		charset utf-8;
		location /
        {
			proxy_set_header Host $host;
			proxy_set_header X-real-ip $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_pass http://localhost:1234/;
			#此处的地址是上游服务器Tomcat的地址,注意要带上末尾分号前的/号,否则会出现意想不到的结果O(∩_∩)O哈哈~,后面再讲
		}
	}

listen 80; 让Nginx去监听80端口,将用户的请求反向代理到下面配置的服务器上。

access_log error_log是指定我们网站的访问日志、Nginx代理过程中产生的错误日志存放路径

location /这是一个location块,此处的/是匹配所有URL,也是通常情况下需要匹配的。

proxy_set_header Host $host;经过了Nginx代理后,上游服务器无法知道用户请求时,Http协议里的Host是什么,在此把用户请求的Host带到上游服务器。

proxy_set_header X-real-ip $remote_addr;由于经过了代理,上游服务器同样是没办法知道用户IP的,获取到的都是Nginx所在的服务器内网IP。所以在此把用户真实的IP放到Http协议头里。

上游服务器,比如tomcat,在Java代码中可以通过HttpServletRequest.getHeader("X-real-ip")获取到具体IP,其中X-real-ip是可以自定义命名的,但按照惯例是命名如此,注意区分大小写,并且一定要和nginx配置文件里保持一致。

这样配置好后,用户在浏览器上访问Nginx所在的服务器,端口号80时,就可以通过反向代理的方式进行访问我们架设在本机localhost,端口号为1234上的Web服务器了。

接下来介绍Nginx核心的几个命令:locationrewriterootalias

location

语法:

location [=|~|~*|^~] /uri/ 
{
	...
}

[ ]表示可省。其中每个参数的含义如下:

= 表示精确匹配后面的URL路径,要求用户输入的路径必须和此处输入的完全相同,才能进入这个Location块处理。同时优先级是最高的。 ^~ 表示URL以某个常规字符串开头进行匹配。同时注意,nginx不对url做编码。

最常用的两项: ~ 表示区分大小写地进行正则匹配 ~* 表示不区分大小写地进行正则匹配

!~!~*是对上面两种写法正则匹配的取反,分别为区分大小写不匹配及不区分大小写不匹配

当有多个Location存在时,他们优先级=>^~>无正则的常规匹配 当匹配到一个Location时,就进入Location内部处理,若无rewrite等命令时,不会再去匹配其他Location。

echo调试输出

看了上面的匹配规则后,我是手痒想写几条来体验体验,但在这之前,我们遵循由简到繁的原则,用最简单的方式去验证我们写的规则是否正确,能否如期进行匹配。

为此,使用echo模块进行快速的调试输出。echo模块是国人开发的模块,默认不集成在nginx源码中,需要重新编译nginx源码并加入此模块才能被识别。

先来个Hello World

   location /hello {
     echo "hello, world!";
   }

每一行记得都必须有一个英文半角的分号;结束。

加入了这一段location后,配置文件如下:

server
	{
		listen 80;
		access_log  /var/log/nginx/myblogaccess.log;
		error_log   /var/log/nginx/myblogerror.log;
		proxy_ignore_client_abort on;
		charset utf-8;

		location /hello 
		{
			echo "hello, world!";
		}

		location /
        {
			proxy_set_header Host $host;
			proxy_set_header X-real-ip $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_pass http://localhost:1234/;
		}
	}

首先让nginx检查我们编写的配置文件有没有语法错误。 使用命令nginx -t检查,若显示OK,则继续使用/etc/init.d/nginx reload命令让nginx去reload我们的配置文件。 若检查不通过,请按照它给提示修改,通常是语法错误,逻辑错误才不会提示你呢 ( ̄┰ ̄*)

这样,我们做的修改才会生效。

服务器IP此处假定为192.168.1.100 然后用浏览器去访问 http://192.168.1.100/hello 看看是不是出现了hello, world! 有个比较麻烦的地方是,部分浏览器,比如Google Chrome浏览器,会将这样的echo内容当成一个文件去下载。 那么就要再这基础上稍微修改,让它告诉浏览器这是一个文本,不是一个文件。

 location /hello {
     default_type text/plain;
     echo "hello";
   }

/posts/20170215151913/20170215020225.jpg

我在我的服务器上进行测试,效果如图

这样,我们就实现了在nginx上向浏览器输出hello world。同时,我们也实现了一个最简单的常规匹配。

正则匹配

正则表达式贯穿整个nginx系统,nginx使用的是PCRE的正则表达式库。 关于正则表达式请见 wiki百科

继续阅读下面之前,建议先掌握正则表达式的写法。而不是一味的在网上随处复制、尝试。正则表达式别看标点符号乱七八糟混乱一片,当你耐下心来,多做几次尝试,或许你就会掌握其中的奥秘,并感慨原来正则是多么伟大的一个存在。 可参见 菜鸟教程

正则在线测试工具

location 正则匹配

现在来尝试一下在nginx中使用正则匹配。 想要匹配 /hello/1 这样的路径。末尾的数字1可以是其他任意长度、范围的数字。 修改刚才的location区段如下:

 location ~ ^/hello/\d+$ {
     default_type text/plain;
     echo "hello";
   }

我们在URL路径上的子目录加了一个子目录分隔符/以及正则表达式的元字符\d+。正则表达式匹配以^开始,$结束。

~标志表示区分大小写进行匹配,元字符\d表示匹配一个一位的数字,紧跟其后的+表示匹配一个或多个前面的元字符(\d),这样组合起来\d+就表示匹配任意数字了

这样,我们在浏览器输入/hello/888

/posts/20170215151913/20170215020220.jpg
就会看到nginx向我们say hello. 也表示成功匹配到这块location。

同样,如果我们要阻止一个非法的URL进来,通常是拒绝对一些文件的直接访问,可以使用deny all;

 location ~ ^/hello/\d+$ {
     deny all;
   }

这样,用户访问/hello/888时,就会被nginx拒绝,返回页面代码403

基本上只要经过工具测试过的正则表达式,直接复制放在nginx里使用是没有什么问题的。

rewrite

Nginx最强大的一个模块之一,就是rewrite。 rewrite是用来对URL正则匹配然后重写,有了它可以对URL进行优化、更好地优化SEO提升网站排名等。

语法 使用区段
rewrite regex replacement flag server, location, if
参数 说明
regex 正则表达式,指明要匹配的URL路径,无论在哪个区段使用rewrite,都是从根目录开始匹配
replacement 要替换的内容,会讲匹配到的路径重写成这个内容
flag 标志位
flag 说明
last 停止处理当前重写模块指令,之后搜索location与更改后的URI匹配。
break 完成当前设置的重写规则,停止执行其他的重写规则。
redirect 返回302临时重定向,如果替换字段用 http:// 开头则被使用。
permanent 返回301永久重定向。

通常在最开始的时候,会把rewrite写在server区段,让它能够匹配的范围是所有URL,然后经过重写后的URL会进入下面的location处理。

例子


Hello World

server
	{
		listen 80;
		access_log  /var/log/nginx/myblogaccess.log;
		error_log   /var/log/nginx/myblogerror.log;
		proxy_ignore_client_abort on;
		charset utf-8;

		rewrite /hi /hello last;

		location /hello
		{
			default_type text/plain;
			echo "hello";
		}

		location /
        {
			proxy_set_header Host $host;
			proxy_set_header X-real-ip $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_pass http://localhost:1234/;
		}
	}

继续之前的例子,我们对/hi这个URL重写成/hello。标志位是last,指明重写完后重新匹配其他location,在这里自然会匹配到下面的location /hello ,然后echo "hello";

这样,我们就可以实现通过访问/hi来访问/hello,而这样的重写,用户是完全不知情的,浏览器地址栏也不会变,因为不是重定向,只是在Nginx内部改变。

/posts/20170215151913/20170215120258.jpg

伪静态

将动态网页伪装成静态网页的话,对搜索引擎是非常友好的。因为静态网页不怎么需要改变,搜索引擎可以比较长久地缓存。同时抓取时,也比较方便。而对于搜索引擎,最重要的区别在于这个URL地址上。 一个带参数的URL地址,比如/hi?id=1很明显通常这表明是一个动态网页。而且参数只有一个,这样如果我们美化一下URL,把参数去掉变成/hi/1或者直接伪装成静态文件/hi/1.html,这样是很不错的。

如果不用nginx,那么就只能靠上游服务器它们自己去处理了,但这样做的话,首先因为上游是处理动态页面的程序,性能不如直接由nginx直接来得快。其次如果上游是一个集群服务器,配置时就比较麻烦了。 在tomcat中,可以使用 URL Rewrite 这个模块。

在Nginx中利用rewrite很轻松就可以做到伪静态。

继续上面的例子,在server区段中 rewrite ^/hi/(\d+)\.html$ /hi?id=$1 last;

上述正则表达式表示匹配 /hi/任意数字.html 这样的URL,其中的(\d+)括号( )表示括号内的字符串作为一个新的子字符串返回,那么表达式的匹配结果里就会有两个结果:

/posts/20170215151913/20170215130287.jpg

其中第0个结果表示完整的原字符串,只要成功匹配,就会有这个第0结果。 然后第1个结果正是我们用小括号括起来的子串,即1.html里的1 然后在rewrite的重写部分,使用$结果索引 获取匹配到的结果,这里使用$1表示取第1个结果。

因为正则表达式里的.是有特殊含义的,表示任意一个除了\n换行以外的字符,所以1.html里的文件后缀名分隔符.需要转义表达,因此写为\.

这样就会把带有参数的原始URL传给上游服务器了。我这里做了一个简单测试页面,上游服务器里动态页面获取参数后返回出来。

/posts/20170215151913/20170215130276.jpg

如果担心上游服务器可能配置出问题,无法判断是rewrite写错还是上游服务器代码写错的话,推荐把rewrite后面的标志位last改为redirect 这样,会请求一个临时重定向操作,当浏览器访问/hi/123.html时,如果重写成功,地址栏上会变成重写后的URL地址

/posts/20170215151913/20170215130246.jpg

这样,就可以很清楚地知道我们写的rewrite有没有正常工作。

当有多个参数需要伪静态时,比如一篇博客文章按照年月归档,那么可以这样写: rewrite ^/hi/(\d{4})/(\d{1,2})/(\d+)\.html$ /hi?y=$1&m=$2&id=$3 redirect; 其中,大括号{}表示前一个元字符重复的次数。\d{4}表示四个\d\d\d\d\d,即匹配年份的四位数字。 {1,2}表示至少重复1次,最多重复2次。即匹配月份的数字1位或者2位数。 然后,其实匹配结果的索引是按括号堆栈的顺序排列的。

当我们输入/hi/2017/1/123.html时,就会被替换成/hi?y=2017&m=1&id=123 这正是我们想要的多参数的情况。

alias

alias和root一样的功能,均是让nginx去访问一些静态文件。 其中alias本身的功能是对目录起一个别名。

例子

假设我们nginx服务器上/opt/yyq/hi这个目录下有一个1.jpg文件, 而我们这样最简单地使用alias

location /hi/
{
	alias /opt/yyq/hi/;
	#记得必须加上末尾分号前面的 / 斜杠。
}

当用户访问/hi/1.jpg时,nginx会直接在本机上的/opt/yyq/hi目录下寻找1.jpg,如果找到,将直接把这个图片返回给用户浏览器,不再将请求发往上游服务器。

正如其功能所说,alias只是一个目录的别名,nginx会很粗暴地将 URL/hi/换成本地路径/opt/yyq/hi/,然后把location匹配的url后半截 1.jpg给加到本地路径后面,最终形成/opt/yyq/hi/1.jpg这样的本地路径。

所以,如果alias末尾的分号前没有/的话,会形成/opt/yyq/hi1.jpg这样的错误路径,大家使用tail命令去观察nginx的错误日志文件时,就会看见。 tail -300f 文件路径 默认在/var/log/nginx目录里面,具体看server里的error_log配置项

同理,若/hi/yyq/1.jpg是这样的一个访问请求,使用刚才的alias,也一样会去寻找/opt/yyq/hi/目录下的yyq子目录里的1.jpg

所以,这可以非常方便用在静态资源文件上。

root

root也是同alias,但有点不同的是,root会比alias更加粗暴 (^__^)

location /hi
{
	root /opt/yyq/hello;
}

把刚才的例子里的alias改写成root后,当用户访问/hi时,nginx会将location里的整段url拼接到root指定的本地路径的后面,形成/opt/yyq/hello/hi这样的路径。

访问/hi/1.jpg

/posts/20170215151913/20170215140265.jpg

我们可以在错误日志上很清楚地看到路径被神奇地写成了/opt/yyq/hello/hi/1.jpg

这就是rootalias的最大不同之处,另外在使用root的时候,location最后面的斜杠有或者无(/hi//hi)以及root后面的路径末尾的斜杠有或者无(root /opt/yyq/hello/;root /opt/yyq/hello;)均是没有任何影响,都是同样的匹配结果,而相比alias就不一样了。

##使用root和alias的好处 我们知道nginx对静态资源的访问,无论是在高并发还是多连接的情况下,与后台的tomcat等程序相比,性能是相当出众的。 tomcat是基于Java虚拟机的,每当有一个请求到来,就会启动一个servlet,分配大量内存空间,却仅仅是为了一个硬盘上的静态资源文件,没有任何数据库调用、业务逻辑处理,就做了很多无用的事情。 有了nginx在前面罩着的话,tomcat就可以更加专注于处理动态页面,而不用再去处理静态资源。

/posts/20170215151913/20170215150261.PNG

同时,建议在静态资源的location里,除了有root或者alias外,再加上一个expires 7d;指示资源的缓存过期时间,其中7d表示缓存七天。

location /hi/
{
	alias /opt/yyq/hi/;
	expires 7d;
}

这样浏览器就会把这个资源缓存下来,当用户再次访问这个资源的时候,浏览器会去检查本地缓存,如果有,而且没过期,那么就不会再向服务器发一次GET请求,而是直接从本地获取,速度更加快了。 个人建议一些图片文件,恰巧也是不经常修改的,而且体积也很大,所以可以长时间缓存,几天一个月,而一些js、css文件等,文件比较小,也经常修改,缓存时间稍微短一点,比如1h一个小时。