Nginx

Nginx是非常流行、高性能高稳定性的web服务器,常用来作为前端的反向代理、负载均衡。Nginx很好地充当了网站胶水的作用。

1.安装

如果是新机器,想直接安装Linux + Nginx + PHP + MySQL,那么使用lnmp.org的脚本是最方便的,推荐。官网有详细的安装教程。如果只是想安装nginx,那么解压lnmp压缩包后,执行./install.sh nginx

下面是单独安装Nginx的教程。以CentOS为例,首先安装nginx依赖的PCRE/SSL/Zlib库:

yum install pcre pcre-devel
yum install openssl openssl-devel
yum install zlib zlib-devel

如果上面三行安装失败,可使用yum list installed | grep pcre 检查是否已经安装了pcre/openssl/zlib,如果已经安装,则单独安装三个devel即可,如yum install pcre-devel

然后到官网下载稳定版进行安装:
Nginx官方提供三种类型的版本:
Mainline version:Mainline 开发板,Nginx目前主力在做的版本
Stable version:最新稳定版,生产环境上建议使用的版本
Legacy versions:遗留的老版本的稳定版

wget  http://nginx.org/download/nginx-1.2.7.tar.gz 
tar zxvf nginx-1.2.7.tar.gz
cd nginx-1.2.7
./configure --prefix=/usr/local/nginx  # 推荐放在这个目录下
make && make install

安装时需要安装gcc,如果未安装会报以下错误

./configure: error: C compiler cc is not found

使用以下语句安装

yum install gcc

启动nginx

/usr/local/nginx/sbin/nginx #可以使用-c指定其他位置的配置文件

启动之后nginx监听在80端口,可用浏览器访问,注意防火牆。
如果无法访问,则需设置防火墙

#CentOS6.9.x64
iptables -I INPUT 5 -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
service iptables save
service iptables restart

停止nginx

ps aux | grep nginx # 获得nginx master process的pid号
kill -QUIT nginx_pid # 从容方式停止
kill -9 nginx_pid # 强制停止

常用的还有平滑启动的方式:

nginx -s reload

2.配置

Nginx的技术角色定位,是充当URL路由功能,像胶水一样粘合不同功能的动态服务器,nginx本身也适合作为web静态服务器。因此,一台典型的nginx的配置为(在http{}范围内):

upstream admin { # 适合于多台机器负载均衡自动摘除,ip_hash可使得相同ip映射到相同的服务器上
    server 192.168.1.1:8080;
    server 192.168.1.2:8080;
}

server
    {
        listen       80;
        server_name  www.abc.com; # 域名

        #ssl on;
        #ssl_certificate      /usr/local/nginx/conf/1_www.abc.com_bundle.crt;
        #ssl_certificate_key  /usr/local/nginx/conf/2_www.abc.com.key;

        charset utf-8;

        #root   /data/html;  # 这个位置的写法等同于下方的location /
        #index  index.html index.htm;

        location ^~ /admin/ {  # 以/admin/开头的链接,转给指定服务器处理
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_pass http://admin;
        }

        location / {
            root   /data/html; # 静态资源位置
            index  index.html index.htm;
        }

        access_log  /data/wwwlogs/access.log;
    }

设置上传文件的最大大小

nginx.conf文件中的http{}配置里面,增加或修改client_max_body_size 50m;的值,50m表示50兆字节大小。

特别注意!

对于子目录root到静态资源的路径问题,如果配置是这样的:

        location /static/ {
                root /var/www/app/static/;
        }

那么对应到的物理目录是/var/www/app/static/static,而不是/var/www/app/static

3. 匹配语法规则

location的语法规则

location匹配的是从url根目录开始的部分,不含域名,也不含问号?和问号后面的查询query_string。这个网站可以测试nginx正则匹配。

= 开头表示精确匹配

^~ 开头表示uri以某个常规字符串开头,理解为匹配 url路径即可。nginx不对url做编码,因此请求为/static/20%/aa,可以被规则^~ /static/ /aa匹配到(注意是空格)。

~ 开头表示区分大小写的正则匹配

~* 开头表示不区分大小写的正则匹配

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

/ 通用匹配,任何请求都会匹配到。

首先匹配 =,其次匹配^~, 其次是按文件中顺序的正则匹配,最后是交给 / 通用匹配。当有匹配成功时候,停止匹配,按当前匹配规则处理请求。

调试location匹配小技巧

可以在location {}return 603;return 604; 这样一个返回码,前端如果可以看到返回码,说明匹配上了,剩下的就是location中配置的问题了。

4. 关于rewrite

参考博客。rewrite指令最多循环10次,超过报500错误。

rewrite指令的语法:

   rewrite 正则表达式(用户获取匹配到的group) 重写的url(可以使用前面正则匹配到的group值) [flag] 

flag标记默认为last,即rewrite;redirect是302跳转,permanent是永久跳转301,不建议使用permanent.

关于正则表达式,写成^的,应该是没有特别的含义,因为此时也没有任何的group。

5. 关于proxy_pass

关于proxy_pass值后面带不带/的说明:博客

6. 常用示例

将首页/ 302定向到/xxx/目录上

指定完整路径,将302跳转过去:

        location = / {
            rewrite ^ http://$host/xxx/;
        }

指定相对路径,将不会302跳转(应该加上redirect关键字就可以302跳转),而是服务器端转发,【但是这种方式对页面的相对url地址会有问题,需要保证对应的页面全部使用根目录/开头的url引用】:

        location = / {
            rewrite ^ /m/;
        }

将http://定向为https://

server {
    listen 80;
    server_name www.abc.com;
    rewrite ^(.*)$  https://$host$1 redirect; # 如果是永久定向,则用permanent,在确定永久使用https时,就可以用permanent
}

将带子目录的某个url定向到另外一个域名上

server {
     location /sub/ {
         rewrite ^(.*) http://m.abc.com$1;
     }
}

根据cookie的值来确定映射到哪台机器上

map $COOKIE_env $env {
    pre pre;
    default idc;
}

upstream idc {
server 127.0.0.1:18080;
}

upstream pre {
server 10.2.3.4:18080;
}

    server {
        listen       80;
        server_name  demo.abc.com;
        location / {
            proxy_pass http://$env;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_connect_timeout 600;
            proxy_send_timeout 600;
            proxy_read_timeout 600;
        }
    }

根据userAgent做不同的内容展示

这个需求时来自于现在前后端分离后,前端甚至是使用wepack编译,对于国外Google搜索引擎是可以识别的,但是国内的百度搜索引擎则识别不了。为了解决这个问题,可以利用nginx的转发功能,根据userAgent判断是否是百度爬虫,继而将百度爬虫的请求导向到纯xml数据文件中。

示例写法:

        location / {
            root   html;
            index  index.html index.htm;

            if ($http_user_agent ~* "Baiduspider") { # 百度爬虫,则跳转掉
                proxy_pass http://www.abc.com;
            } 
       }

7. 关于tomcat contextPath分发设计

tomcat部署的应用,一般都有一个contextPath,所有tomcat的访问链接都在这个contextPath下,虽然tomcat可以部署为ROOT.war到根目录下,但是这样的应用只能有一个,对于tomcat开发和复用都有些麻烦。如果tomcat可以部署在根目录下,那么nginx就可以透明地把整个url proxy_pass给tomcat应用,很方便。但是如果面向客户的url是没有带contextPath的,但是nginx proxy_pass背后的tomcat是有contextPath的,那么怎样映射呢?

假设tomcat的应用名称是myapp,tomcat使用spring mvc,里面的某个链接配置为/abc/hi,那么本地tomcat起来之后,访问地址为:

http://127.0.0.1:8080/myapp/abc/hi

假设nginx在本地起,监听在80端口,默认访问链接是http://127.0.0.1。接下来是各种nginx配置和映射关系:

  • nginx根目录全映射
        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass http://127.0.0.1:8080;
        }

此时,访问http://127.0.0.1/myapp/abc/hi即访问http://127.0.0.1:8080/myapp/abc/hi,路径是完全一致的,页面内容中URL相对路径和绝对路径都可以工作正常。

  • tomcat路径比nginx路径长
        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass http://127.0.0.1:8080/myapp/;
        }

此时,访问http://127.0.0.1/abc/hi即访问http://127.0.0.1:8080/myapp/abc/hi,tomcat页面中的绝对路径是不一致的,会有问题,相对路径则没有问题。

  • tomcat路径比nginx路径短
        location /abc/ {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass http://127.0.0.1:8080/;
        }

此时,访问http://127.0.0.1/abc/myapp/abc/hi即访问http://127.0.0.1:8080/myapp/abc/hi,tomcat页面中的绝对路径是不一致的,会有问题,相对路径则没有问题。

  • tomcat路径和nginx路径前缀不一致
        location /world/ {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass http://127.0.0.1:8080/myapp/;
        }

此时,访问http://127.0.0.1/world/abc/hi即访问http://127.0.0.1:8080/myapp/abc/hi,tomcat页面中的绝对路径是不一致的,会有问题,相对路径则没有问题。

注意到,上面的proxy_pass配置,有的是/结尾有的不是,这个要注意,多尝试。

8. 关于多层nginx获取用户真实ip的做法

上面的nginx分发方式都是 用户浏览器Broswer -> nginx -> tomcat:8080端口 的网络结构,在java servlet中可以这样获取用户真实ip:

    public static String getRemoteIp(HttpServletRequest request) {
        String remoteIp = request.getHeader("X-Forwarded-For");
        if(remoteIp != null && !remoteIp.trim().isEmpty()) {
            return remoteIp;
        }
        return request.getRemoteAddr();
    }

但是如果nginx分发多了一层,是这样的结构:用户浏览器Browser -> nginx1 -> nginx2 -> tomcat:8080端口,那么如果nginx1和nginx2都是这样配置,那是获取不到用户真实ip的:

        location /abc/ {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass http://192.168.1.某个ip:80/;
        }

要这样配置:第二个nginx,即nginx2,把proxy_set_header X-Forwarded-For $remote_addr;去掉,换成:proxy_set_header X-Real-IP $remote_addr;,完整配置如下:

nginx1:

        location /abc/ {
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass http://192.168.1.某个ip:80/;
        }

nginx2:

        location /abc/ {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://192.168.1.某个ip:80/;
        }

9. nginx模块

nginx很适合统一地以切面的方式为网站增加一些功能,例如由http变成https网站,后台的实现不需要入侵https,仅需要在nginx配置下即可。

查看nginx安装了哪些模块:

nginx -V # 看下那些--with-开头的就是已安装的模块

自定义替换掉html中的内容为制定的内容

使用的是http_sub_module,lnmp安装的nginx默认有编译这个。

例如,给网站加入百度统计javascript,百度统计javascript代码要放到<head></head>中,这个工作可以让nginx来做。

由于百度统计javascript代码太长,这里只示例而已,sub_filter可以放到http, server, location这3个位置中:

server {
    sub_filter '</head>'  '<!--something add here--></head>';
    sub_filter_once on;
}

特别注意 如果是proxy_pass另外一个web服务器,而对方可能开启gzip导致sub_filter失效,此时,请在proxy_pass同级位置加上:proxy_set_header Accept-Encoding "";

使用http basic auth给网站添加访问密码

对于没有访问密码的网站,例如静态网站或一些开源软件,nginx可以很方便地加上访问密码限制。这个功能来自ngx_http_auth_basic_module模块,nginx默认自带有。控制可以加在http,server,location,limit_except 4个位置上,配置如下:

location / {
    auth_basic           "login to website";
    auth_basic_user_file ./htpasswd;
}

注意,htpasswd是一个密码文件,把它放到nginx.conf配置的同级目录下。htpasswd的格式为:

username:password # 注意,这里的密码必须是htpasswd生成的密码串,而非明文的密码

这个网站可以在线生成密码串:http://www.htaccesstools.com/htpasswd-generator/

10. 禁止微信缓存html

js/css等文件的缓存可以通过hash的方式来控制,但是html的缓存却不好控制。由于微信的优化,常见的html无缓存配置都没效果,这里有一个主题在讨论微信中的html文件缓存知乎也有讨论简述上这篇文章也有解决方案

两个方式

  1. 在html标签上加上:

    <html manifest="IGNORE.manifest">
  2. 对于html文件,设置nginx expires -1,加完之后用浏览器看请求,返回头会有Expires,值为当前时间。

         location ~ .*\.(html|htm)$
         {
             expires      -1;
         }
文档更新时间: 2018-11-10 22:49   作者:nick