nginx配置
nginx配置说简单也简单,说复杂也复杂,入门简单,精通难(怎么感觉有点像javascript
?),主要是nginx
自身有很多专属的语法和命令以及让人捉摸不透的运行规则,不系统的学习一下很容易出错,有时候即使工作很多年的同学想配置一些特殊场景时可能也会折腾半天才能搞定。
一个nginx配置文件大体长下面这样,绝大部分配置都写在http
里面(省略了很多默认配置):
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name localhost;
charset utf-8;
location / {
root html;
index index.html index.htm;
}
location /test {
proxy_pass https://haoji.me;
}
}
}
一般开发者需要经常打交道的大部分配置都在server
和location
里面。
一些注意事项:
- 注释采用
#
; - 所有配置必须以
;
结尾,不写会报错; - 每个命令注意允许使用的上下文,例如有些命令可以写在
http
、server
、location
等多个上下文,有的则只能出现在某一种上下文里面;
常见命令简要说明:
include
:引入外部文件,当配置文件较多时可以分开写在不同文件,然后一次性引入即可,例如include servers/*;
default_type
:设置默认内容类型;server_name
:域名,支持空格配置多个;listen
:监听端口;proxy_pass
:转发;root
:指定根目录;rewrite
:重写;
location配置
location
粗略看只有4种配置:精确匹配、^~
开头匹配、正则匹配、/
匹配,细分有7种,优先级从高到低依次是:
=
:精确匹配,优先级最高;/test/aaa/bbb.html
:完整路径匹配,如果访问路径和location配置完全相等,那么这条配置自然会优先匹配,它的优先级仅次于精确匹配;^~ /test
开头:匹配以/test
开头的地址,如果匹配成功会停止向后继续搜索;~
:区分大小写的正则匹配,如果匹配成功会停止向后继续搜索;~*
:不区分大小写的正则匹配,如果匹配成功会停止向后继续搜索,2种正则匹配优先级相同;/test
:匹配以/test
开头的地址,匹配成功后还会继续往后匹配,直至结束,以最后一个匹配为准;/
:匹配以/
开头的地址,由于任何地址都是以/
开头,所以所有地址都会命中这个规则,但它优先级最低,一般放在最后做backup;
很难记?记住以下2点就可以了:
- 整体优先级:
精确匹配
>完整路径匹配
>^~
>正则匹配
>/
起始路径 >/
; - 除了
/
开头配置是匹配成功还会继续往后匹配之外,其它所有配置都是成功就立即停止;
再说明白一点:
- 不同类型的规则,不管先后顺序如何,始终按照上述优先级来;
- 同种类型的规则则分情况不同:
- 多个正则之间、多个
^~
之间,第一个匹配生效,与顺序有关; - 都是/开头时,最长匹配生效,和先后顺序无关;
- 多个正则之间、多个
被网上这篇文章坑了很久,这篇文章关于正则部分写错了。
关于正则写法,除了开头结尾不需要/
之外,和JS的正则差不多,要实现精确匹配的话也需要^
和$
配合,例如:
# 访问 /ggg/test/aaa/bbb.txt时返回222
# 访问 /test/aaa/bbb.txt时返回111
location ~ ^/test/aaa/.*\.txt$ {
return 200 '111';
}
location ~ /test/aaa/.*\.txt$ {
return 200 '222';
}
所以,可以把^~
看成一种特殊的正则,只不过它的优先级高于正则,^~
能实现的,普通正则一定也能实现:
# 下面2中效果完全相同,唯一不同是优先级不同
# 访问 /test/aaa/bbb.txt 时返回222
location ~ ^/test/aaa/ {
return 200 '111';
}
location ^~ /test/aaa/ {
return 200 '222';
}
测试
示例一,访问/hello.json
时,第二个生效(返回222
):
location /hello.json {
default_type text/html;
return 200 '111';
}
location =/hello.json {
default_type text/html;
return 200 '222';
}
示例二,访问/test/aaa/bbb.json
时,第二个生效(返回222
):
# 虽然222在后面,但是由于^~优先级更高,所以第二个生效
location ~ /test/aaa {
return 200 '111';
}
location ^~ /test/aaa {
return 200 '222';
}
示例三,访问/test/aaa/bbb.json
时,第1个生效(返回111
):
# 多个正则之间,第一个匹配生效,与顺序有关
# 虽然越往后匹配越精确,但是由于是正则,匹配到第一个就停止匹配
location ~ / {
return 200 '111';
}
location ~ /test/aaa {
return 200 '222';
}
location ~ /test/aaa/.*\.(gif|jpg|jpeg)$ {
return 200 '333';
}
示例四,访问/test/aaa/bbb.json
时,第2个生效(返回222
):
# 不管二者如何交换顺序,始终都是第二个生效
location /test/aaa {
return 200 '111';
}
location ~ /test/aaa {
return 200 '222';
}
示例五,访问/test/aaa/bbb/ccc.json
时,第2个生效(返回222
):
# 都是/开头时,最长匹配生效,和先后顺序无关
location /test/ {
return 200 '111';
}
location /test/aaa/ccc {
return 200 '222';
}
location /test/aaa/ {
return 200 '333';
}
location / {
return 200 '444';
}
rewrite
先来说说rewrite
和proxy_pass
的区别:
- 前者只能重写相同域名,后者可以转发任意网址;
- 有待补充
proxy_pass
proxy_pass
的作用是用来转发一个地址,大概流程是:nginx在服务端帮你把内容请求好,然后再把请求到的内容原样吐给你,但是,proxy_pass
使用有几个限制:
- 如果location使用了正则,那么转发地址中不能包含
URI
,什么叫不能包含URI呢?就是只能domain+host
,例如,https://haoji.me/
中结尾的/
属于URI
,会报错,必须去掉结尾的/
;
如果违反第一点会报如下错误:
nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in /usr/local/etc/nginx/nginx.conf:45
所以,实际使用时经常会把proxy_pass
和rewrite
结合起来一起使用。
如果转发的domain
无法ping通,启动的时候会报错:
nginx: [emerg] host not found in upstream "localhost" in /usr/local/etc/nginx/nginx.conf:48
解决办法是:
添加dns到/etc/resolv.conf 或者是/etc/hosts,让其能够解析到IP
注意结尾是否有/
的区别,下面配置访问localhost/test
无法正常访问百度:
location /test {
proxy_pass https://www.baidu.com;
}
改成这样就正常了:
location /test {
proxy_pass https://www.baidu.com/;
}
常见配置总结
https配置
server {
listen 443 ssl;
server_name localhost;
ssl_certificate cert.crt;
ssl_certificate_key cert.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
转发
例如,将 http://www.test.com 转发到 http://127.0.0.1:6666 :
server {
listen 80;
server_name www.test.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# 代理连接超时设置为10秒钟,默认60秒太久了
proxy_connect_timeout 10;
proxy_pass http://127.0.0.1:6666;
}
}
跨域配置
如果还需要增加跨域配置,只需要再在上面加上:
server {
listen 80;
server_name www.test.com;
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With';
add_header 'Access-Control-Allow-Methods' 'POST, GET, OPTIONS';
# 省略其它部分...
proxy_pass http://127.0.0.1:6666;
}
}
永久跳转
例如,将带www的域名自动跳转到没有www的域名。
下面的例子是将 http://www.haoji.me 永久跳转到 http://haoji.me ,丢弃域名后面的路径:
server {
listen 80;
server_name www.haoji.me;
location / {
rewrite ^(.*) http://haoji.me permanent;
}
}
将 http://www.haoji.me 永久跳转到 https://haoji.me ,不丢弃域名后面的路径:
server {
listen 80;
server_name www.haoji.me;
location / {
rewrite ^(.*) https://haoji.me$document_uri permanent;
}
}
直接吐出内容
location /test {
default_type application/json;
return 200 '{"code": 0, "message": "ok"}';
}
泛二级域名配置
所谓泛二级域名配置,就是将 *.xxx.com
根据子域名的不同自动进行不同配置,而不需要手动一个个写。
基于转发实现的泛二级域名配置
下面的例子中,将 http://*.haoji.me/
转发到 http://127.0.0.1:8080/*/
http {
include mime.types;
default_type application/octet-stream;
# 使用变量来构造server地址时必须设置resolver
resolver 8.8.8.8;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name www.haoji.me www.test.com test.com;
location / {
rewrite ^(.*) http://haoji.me permanent;
}
}
server {
listen 80;
server_name demo.haoji.me;
location / {
root D:\Workspace\github\demo;
index index.html index.jsp;
}
}
server {
listen 80;
server_name ~^(.+)?\.haoji\.me$;
location / {
# 注意这里用localhost的话会报错,必须用127.0.0.1
proxy_pass http://127.0.0.1:8080/$1$request_uri;
}
}
}
正如上面所配置的,可能某几个特殊子域名需要特殊配置,所以可以在以上配置之前加上特殊配置(注意nginx的优先级,找到第一个匹配的就不会再继续往下匹配了)。
基于指定目录实现的泛二级域名配置
如下:
server {
listen 80;
server_name ~^(.+)?\.haoji\.me;
location / {
root D:\Workspace\github\test\$1;
index index.html index.jsp;
}
}
no resolver defined to resolve localhost
原因是Nginx0.6.18
以后的版本中启用了一个resolver指令,在使用变量来构造某个server地址的时候一定要用resolver指令来制定DNS服务器的地址,所以解决这个问题的方法很简单:
在nginx的配置文件中的http{}
部分添加一行resolver 8.8.8.8;
即可
server_names_hash_bucket_size
如果server_name
配置很多的话可能会报错如下:
could not build the server_names_hash, you should increase server_names_hash_bucket_size: 64
解决方法:在http{}
里面增加server_names_hash_bucket_size
设置,一般是2的指数,比如我这里512:
http {
include mime.types;
default_type application/octet-stream;
# 域名过多时需要配置这个参数
server_names_hash_bucket_size 512;
}
注意事项
结尾是否有“/”很重要;
nginx的全局变量
nginx内置了大量的$开头的全局变量,这些变量在有些时候会非常有用,而且网上很少有介绍的很全面的文章,比如我今天想找一个获取origin
的变量,找半天没找到,最后自己根据规律随便蒙一个$http_origin
竟然对了!
以下是我已亲自验证过的(测试版本:v1.11.8
):
$remote_addr
:客户端IP地址;http_host
:request.getHeader(‘Host’);http_origin
:request.getHeader(‘Origin’);http_referer
:request.getReferer(‘Referer’);
补充:
origin
和host
的区别是,我在A页面调用B页面的接口,那么A是origin,B是host;origin
和Referer
的区别是,前者是调用页面域名(如http://www.aaa.com ),后者则是不包括哈希的完整URL(如 http://www.aaa.com/index.html)
以下摘自网络,未亲自验证:
$args : #这个变量等于请求行中的参数,同$query_string
$content_length : 请求头中的Content-length字段。
$content_type : 请求头中的Content-Type字段。
$document_root : 当前请求在root指令中指定的值。
$host : 请求主机头字段,否则为服务器名称。
$http_user_agent : 客户端agent信息
$http_cookie : 客户端cookie信息
$limit_rate : 这个变量可以限制连接速率。
$request_method : 客户端请求的动作,通常为GET或POST。
$remote_addr : 客户端的IP地址。
$remote_port : 客户端的端口。
$remote_user : 已经经过Auth Basic Module验证的用户名。
$request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。
$scheme : HTTP方法(如http,https)。
$server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
$server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。
$server_name : 服务器名称。
$server_port : 请求到达服务器的端口号。
$request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
$uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
$document_uri : 与$uri相同。
参考 https://segmentfault.com/a/1190000002797606 和 http://www.cnblogs.com/AloneSword/archive/2011/12/10/2283483.html
关于nginx追加路径问题
以前还没特别注意,今天无意发现这个问题,nginx的root和proxy_pass在追加路径问题上处理不一样,具体见下面注释:
server {
listen 80;
server_name www.test.com;
location / {
root E:/Workspace/test/htdocs/;
index index.html;
}
location /test_root {
# 如果访问 www.test.com/test_root/aaa/index.html,实际访问的是 htdocs/test_root/aaa/index.html
# 经测试,无论test_root后面结尾是否加斜杠,还是htdocs结尾是否加斜杠,test_root 都会出现在真实访问路径里面
root E:/Workspace/test/htdocs/;
index index.html;
}
location /test_proxy1 {
# 如果访问 www.test.com/test_proxy1/aaa/index.html,实际访问的是 www.proxy.com/test_proxy1/aaa/index.html
proxy_pass http://www.proxy.com;
proxy_buffering off;
}
location /test_proxy2 {
# 如果访问 www.test.com/test_proxy2/aaa/index.html,实际访问的是 www.proxy.com//aaa/index.html
proxy_pass http://www.proxy.com/;
proxy_buffering off;
}
location /test_proxy3 {
# 如果访问 www.test.com/test_proxy3/aaa/index.html,实际访问的是 www.proxy.com/bbb/aaa/index.html
proxy_pass http://www.proxy.com/bbb;
proxy_buffering off;
}
}
总结:
- 通过root指定访问目录时,无论location结尾是否加斜杠,还是root结尾是否加斜杠,location中的地址始终会出现在真实访问地址里面;
- 通过proxy_pass代理时,如果只是指定域名,且结尾没有斜杠,那么location的地址也会出现在真实地址里面,除此之外,真实访问地址等于:
proxy_pass + (pathname - location中配置的地址)
;
使用nginx搭建代理服务器
非常简单,如下:
# 代理服务器
server {
listen 6587;
resolver 8.8.8.8;
location / {
proxy_pass http://$http_host$request_uri;
#allow 127.0.0.1;
#deny all;
}
}
假设IP是:192.168.1.111
,然后将浏览器代理设置成:192.168.1.111:6587
即可。
以360急速浏览器为例:
但是这种转发有一个很大缺点,就是不支持HTTPS,而且好像也不怎么稳定,一般不推荐使用nginx来做代理服务器。