Nginx配置杂记
co678170
8年前
<p>Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP代理服务器,相较于Apache,具有占有内存少、稳定性高等优势。Nginx安装非常简单、配置文件简洁,但是配置的类目却不少,本文主要记录Nginx的安装以及相关的配置(以下操作在CentOS6.7 64bit环境下)。</p> <h3><strong>Nginx安装</strong></h3> <p>作为一个喜欢折腾的人,Nginx首选当然是采用源码包安装,不过也可以选择yum、rpm来安装。Mac下,用brew install nginx来快速安装。</p> <p>为了能从源码包编译Nginx,除了编译器之外,还需要提供OpenSSL(启用SSL)以及Perl(使用rewrite)、zlib压缩库等等。由于Nginx的模块化特性,还可以安装第三方模块为Nginx提供额外的功能,如ngx_lua、pageSpeed。</p> <p>第三方模块可以在 <a href="/misc/goto?guid=4958184186834948584" rel="nofollow,noindex">github</a> 或者 <a href="/misc/goto?guid=4959717267182592727" rel="nofollow,noindex">https://www.nginx.com/resources/wiki/modules/</a> 查找。</p> <p>接下来,通过安装Nginx相关依赖库、Nginx以及ngx_lua来介绍如何安装Nginx以及第三方模块。</p> <pre> <code class="language-groovy">SOURCE_DIR=/source/ PCRE_DIR=pcre-8.37 PCRE_TAR=pcre-8.37.tar.gz PCRE_URL=http://sourceforge.net/projects/pcre/files/pcre/8.37/pcre-8.37.tar.gz ZLIB_DIR=zlib-1.2.8 ZLIB_TAR=zlib-1.2.8.tar.gz ZLIB_URL=http://sourceforge.net/projects/libpng/files/zlib/1.2.8/zlib-1.2.8.tar.gz OPENSSL_DIR=openssl-1.0.2h OPENSSL_TAR=openssl-1.0.2h.tar.gz OPENSSL_URL=https://www.openssl.org/source/openssl-1.0.2h.tar.gz OPENSSL_INSTALL=/usr/local/openssl LUAJIT_DIR=LuaJIT-2.0.4 LUAJIT_TAR=LuaJIT-2.0.4.tar.gz LUAJIT_URL=http://luajit.org/download/LuaJIT-2.0.4.tar.gz NGINX_DIR=nginx-1.11.1 NGINX_TAR=nginx-1.11.1.tar.gz NGINX_URL=http://nginx.org/download/nginx-1.11.1.tar.gz NGINX_INSTALL_DIR=/usr/local/nginx/ NGX_DEVEL_KIT=https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz NGX_DEVEL_KIT_TAR=v0.3.0.tar.gz NGX_DEVEL_KIT_DIR=ngx_devel_kit-0.3.0 NGX_LUA=https://github.com/openresty/lua-nginx-module/archive/v0.10.5.tar.gz NGX_LUA_TAR=v0.10.5.tar.gz NGX_LUA_DIR=lua-nginx-module-0.10.5 export LUAJIT_LIB=/usr/local/lib export LUAJIT_INC=/usr/local/include/luajit-2.0 mkdir -p $SOURCE_DIR # 更新yum源以及安装gcc、gcc-c++ yum update -y && yum install -y gcc gcc-c++ # 安装pcre cd $SOURCE_DIR wget $PCRE_URL tar -zxvf $PCRE_TAR cd $SOURCE_DIR$PCRE_DIR ./configure && make && make install # 安装zlib cd $SOURCE_DIR wget $ZLIB_URL tar -zxvf $ZLIB_TAR cd $SOURCE_DIR$ZLIB_DIR ./configure make && make install # 安装perl yum install -y perl # 安装openssl cd $SOURCE_DIR wget $OPENSSL_URL tar -zxvf $OPENSSL_TAR cd $SOURCE_DIR$OPENSSL_DIR ./config --prefix=$OPENSSL_INSTALL make && make install # 安装luajit cd $SOURCE_DIR wget $LUAJIT_URL tar -zxvf $LUAJIT_TAR cd $SOURCE_DIR$LUAJIT_DIR make make install cd $SOURCE_DIR wget $NGX_DEVEL_KIT tar -zxvf $NGX_DEVEL_KIT_TAR wget $NGX_LUA tar -zxvf $NGX_LUA_TAR # 安装nginx cd $SOURCE_DIR wget $NGINX_URL tar -zxvf $NGINX_TAR cd $NGINX_DIR groupadd www useradd www -g www ./configure --user=www --group=www --prefix=$NGINX_INSTALL_DIR --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-openssl=$SOURCE_DIR$OPENSSL_DIR --add-module=$SOURCE_DIR$NGX_DEVEL_KIT_DIR --add-module=$SOURCE_DIR$NGX_LUA_DIR make && make install # 解决nginx启动问题 ln -s /lib64/libpcre.so.0.0.1 /lib64/libpcre.so.1 ln -s /usr/local/lib/libluajit-5.1.so.2 -s /lib64/libluajit-5.1.so.2</code></pre> <h3><strong>Nginx配置起步</strong></h3> <p>nginx.conf是主配置文件,由若干个部分组成,每个大括号({})表示一个部分。每一行指令都由分号结束(;),标志着一行的结束。</p> <p>以下是一份简单的配置:</p> <pre> <code class="language-groovy">user nobody nobody; worker_processes 2; events { use epoll; worker_connections 1024; } http { include mime.types; default_type application/octet-stream; access_log /dev/null; error_log /dev/null; sendfile on; keepalive_timeout 65; gzip on; gzip_min_length 1024; gzip_buffers 4 8k; gzip_http_version 1.0; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript; server { listen 80; server_name localhost; location / { root html; index index.html index.htm; } } }</code></pre> <p>从配置可以看出,Nginx监听了80端口、域名为localhost、根路径为html文件夹(上面安装路径为 /usr/local/nginx,所以绝对路径为/usr/local/nginx/html)、默认index文件为index.html、index.htm。</p> <p>Nginx测试配置文件(包含include文件),可通过以下命令来完成,但是只检查语法错误。</p> <pre> <code class="language-groovy">nginx -t -c /path/to/nginx.conf</code></pre> <p><strong>include指令</strong></p> <p>在Nginx的配置文件中,include可以出现在任何地方,以便增强配置文件的可读性,使得部分配置文件可以重新使用。</p> <p>使用include包含的文件,必须确保包含的文件自身有正确的Nginx语法,即配置指令和块,然后指定这些文件的路径(没有给全路径的情况下,Nginx会基于它的主配置文件路径进行搜索)。如:</p> <pre> <code class="language-groovy">include mime.types; # 即:/usr/local/nginx/conf/mime.types</code></pre> <p>如果路径中出现通配符,表示可配置多个文件。这种多用于包含多个虚拟主机的情况,只需要在nginx.conf中http块结束前添加如下一行配置,就可以将多个虚拟主机的配置分离出来,比较好维护。</p> <pre> <code class="language-groovy">include server/*.conf;</code></pre> <p>nginx将会配置server目录下符合.conf结束的配置文件。</p> <p><strong>server部分</strong></p> <p>由关键字server开始的部分被称作虚拟服务器部分,包含在http部分中,用于响应Http请求。一个虚拟服务器由listen和server_name指令组合定义。</p> <p>listen指令定义了一个IP地址/端口组合或者是UNIX套接字路径。listen还有其他的一些可选参数比如default_server、ssl、http2等等。</p> <pre> <code class="language-groovy">listen address[:port]; listen port; listen unix:path;</code></pre> <p>server_name指令默认值为”",意味着没有server_name指令时,对于没有设置Host头的请求将会匹配该server。比如说,对于IP地址访问的请求,可以直接丢弃,如下:</p> <pre> <code class="language-groovy">server { listen 80; return 444; # Nginx对于Http非标准代码会立即关闭一个链接 }</code></pre> <p><strong>location指令</strong></p> <p>location指令可以用在server部分,提供来自客户端的URI或者内部重定向访问,也可以被嵌套使用。</p> <p>location定义:</p> <pre> <code class="language-groovy">location [modifier] uri {...}</code></pre> <table> <thead> <tr> <th>修饰符</th> <th>含义</th> </tr> </thead> <tbody> <tr> <td>=</td> <td>使用精确匹配并且终止搜索</td> </tr> <tr> <td>^~</td> <td>表示uri以某个常规字符串开头,理解为匹配url路径即可。它并非正则表达式匹配,目的是优于正则表达式匹配。这里匹配的是解码uri,例如uri中的“%20”将会匹配location的“ ”(空格)。</td> </tr> <tr> <td>~</td> <td>区分大小写的正则表达式匹配</td> </tr> <tr> <td>~*</td> <td>不区分大小写的正则表达式匹配</td> </tr> </tbody> </table> <p>多个 location 配置的情况下匹配顺序为(当有匹配成功时候,停止匹配,按当前匹配规则处理请求):</p> <ol> <li>匹配=</li> <li>匹配^~</li> <li>按文件中正则出现的顺序匹配</li> </ol> <p>除了上面的location定义之外,还可以命名location:</p> <pre> <code class="language-groovy">location @name {...}</code></pre> <p>命名location仅对内部访问重定向,在进入一个location之前它会保留被请求的URI部分。命名重定向只能够在server级别定义。</p> <p>location指令除了以下两点之外可以被嵌套:</p> <ol> <li>具有“=”前缀</li> <li>命名location</li> </ol> <p>如下:</p> <pre> <code class="language-groovy">location / { location ^~ /css { location ~* /css/.*\.css$ { } } }</code></pre> <p><strong>valid_referers指令</strong></p> <p>valid_referers指令是用来校验Http请求头Referer是否有效。</p> <pre> <code class="language-groovy">valid_referers none | blocked | server_names | string ...;</code></pre> <p>nginx会通过查看Referer字段和valid_referers后面的Referer列表进行匹配,如果匹配到了就将invalid_referer字段值设为0,否则设为1。</p> <ul> <li>none:表示没有Referer;</li> <li>blocked:表示有Referer但是被防火墙或者是代理给去除了;</li> <li>server_names:表示Referer头为server_names列表中的一个;</li> <li>string|regular expression:用于匹配Referer。</li> </ul> <p>通过我们通过校验Referer头来处理图片防盗链,如下:</p> <pre> <code class="language-groovy">location ~* \.(gif|jpg|png)$ { valid_referers none blocked server_names; if ($invalid_referer) { return 403; } }</code></pre> <p><strong>try_files指令</strong></p> <p>try_files指令可以用在server部分,不过最常见的还是用在location部分,它会按照给定的参数顺序进行尝试,第一个被匹配到的将会被使用。</p> <p>它的语法如下:</p> <pre> <code class="language-groovy">try_files file ... uri; try_files file ... =code;</code></pre> <p>try_files指令可能会通过添加‘/’来检查目录的存在,如“$uri/”。如果没有找到文件,会进行一个内部重定向到最后一个参数,最后一个参数可以是命名location或者Http状态码。</p> <p>它经常被用于从一个变量去匹配一个可能的文件,然后将处理传递到一个命名location,如下:</p> <pre> <code class="language-groovy">location / { try_files $uri $uri/ @mongrel; } location @mongrel { proxy_pass http://server; }</code></pre> <p><strong>if是邪恶的</strong></p> <p>当在location块中使用if指令,在某些情况下它并不按照预期运行,一般来说避免使用if指令。</p> <p>if指令的语法如下:</p> <pre> <code class="language-groovy">if (condition) { ... }</code></pre> <p>在location块中if指令能够保证安全的指令应该只有以下两个:</p> <pre> <code class="language-groovy">return ...; rewrite ... break;</code></pre> <p>在某些情况下,需要测试一个变量,那么还是需要用到if指令,如下:</p> <pre> <code class="language-groovy">if ($http_user_agent ~ MSIE) { rewrite ^(.*)$ /msie/$1 break; } if ($invalid_referer) { return 403; }</code></pre> <p>以下用一些例子说明if可能产生一些意料不到的结果:</p> <pre> <code class="language-groovy"># 第二个header才会出现在响应头中 location /only-one-if { set $true 1; if ($true) { add_header X-First 1; } if ($true) { add_header X-Second 2; } return 204; } # try_files失效 location /if-try-files { try_files /file @fallback; set $true 1; if ($true) { # nothing } }</code></pre> <p><strong>nginx内置预定义变量</strong></p> <p>为了使得更加容易配置Nginx,Nginx提供了许多预定义的变量,当然也可以通过使用set来设置变量。你可以在if中使用预定义变量,也可以将它们传递给代理服务器。</p> <table> <thead> <tr> <th>变量名称</th> <th>值</th> </tr> </thead> <tbody> <tr> <td>$args_name</td> <td>在请求中的name参数</td> </tr> <tr> <td>$args</td> <td>所有请求参数</td> </tr> <tr> <td>$query_string</td> <td>$args的别名</td> </tr> <tr> <td>$content_length</td> <td>请求头Content-Length的值</td> </tr> <tr> <td>$content_type</td> <td>请求头Content-Type的值</td> </tr> <tr> <td>$host</td> <td>如果当前有Host,则为请求头Host的值;如果没有这个头,那么该值等于匹配该请求的server_name的值</td> </tr> <tr> <td>$remote_addr</td> <td>客户端的IP地址</td> </tr> <tr> <td>$request</td> <td>完整的请求,从客户端收到,包括Http请求方法、URI、Http协议、头、请求体</td> </tr> <tr> <td>$request_uri</td> <td>完整请求的URI,从客户端来的请求,包括参数</td> </tr> <tr> <td>$scheme</td> <td>当前请求的协议</td> </tr> <tr> <td>$uri</td> <td>当前请求的标准化URI</td> </tr> </tbody> </table> <h3><strong>反向代理</strong></h3> <p>反向代理是一个Web服务器,它接受客户端的连接请求,然后将请求转发给上游服务器,并将从服务器得到的结果返回给连接的客户端。</p> <p>比如说,我们用Node写了一个服务,挂载在服务器的3000端口上,用户并不能直接该服务,可以采用Nginx来转发,访问http://xxx.com,Nginx反向代理,从http://localhost:3000获取内容。配置如下:</p> <pre> <code class="language-groovy">server { listen 80; server_name xxx.com; location / { proxy_pass http://localhost:3000; 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; } }</code></pre> <p>代理到上游服务器的配置中,最重要的是proxy_pass指令。</p> <p>以下是代理模块中的一些常用指令:</p> <table> <thead> <tr> <th>指令</th> <th>说明</th> </tr> </thead> <tbody> <tr> <td>proxy_connect_timeout</td> <td>Nginx从接受请求至连接到上游服务器的最长等待时间</td> </tr> <tr> <td>proxy_cookie_domain</td> <td>替代从上游服务器来的Set-Cookie头的domain属性</td> </tr> <tr> <td>proxy_cookie_path</td> <td>替代从上游服务器来的Set-Cookie头的path属性</td> </tr> <tr> <td>proxy_set_header</td> <td>重写发送到上游服务器头的内容,也可以通过将某个头部的值设置为空字符串,而不发送某个头部的方法实现</td> </tr> </tbody> </table> <p><strong>upstream模块</strong></p> <p>upstream指令启用一个新的配置区段,在该区段定义一组上游服务器。这些服务器可能被设置不同的权重,也可能出于对服务器进行维护,标记为down。</p> <p>以下是一个简单的upstream示例:</p> <pre> <code class="language-groovy">upstream nodejs { ip_hash; server 127.0.0.1:3000; server 127.0.0.1:3001 down; keepalive 32; }</code></pre> <p>server指令可选参数 :</p> <ul> <li>weight:设置一个服务器的访问权重,数值越高,收到的请求也越多;</li> <li>fail_timeout:在这个指定的时间内服务器必须提供响应,如果在这个时间内没有收到响应,那么服务器将会被标记为down状态;</li> <li>max_fails:设置在fail_timeout时间之内尝试对一个服务器连接的最大次数,如果超过这个次数,那么服务器将会被标记为down;</li> <li>down:标记一个服务器不再接受任何请求;</li> <li>backup:一旦其他服务器宕机,那么有该标记的机器将会接收请求。</li> </ul> <p>keepalive指令 :</p> <p>Nginx服务器将会为每一个worker进行保持同上游服务器的连接。</p> <p>负载均衡 :</p> <p>upstream模块能够使用3种负载均衡算法:轮询、IP哈希、最少连接数。</p> <ul> <li>轮询:默认情况下使用轮询算法,不需要配置指令来激活它,它是基于在队列中谁是下一个的原理确保访问均匀地分布到每个上游服务器;</li> <li>IP哈希:通过ip_hash指令来激活,Nginx通过IPv4地址的前3个字节或者整个IPv6地址作为哈希键来实现,同一个IP地址总是能被映射到同一个上游服务器;</li> <li>最少连接数:通过least_conn指令来激活,该算法通过选择一个活跃数最少的上游服务器进行连接。如果上游服务器处理能力不同,可以通过给server配置weight权重来说明,该算法将考虑到不同服务器的加权最少连接数。</li> </ul> <p><strong>常见使用场景</strong></p> <p>使用SSL对流量进行加密</p> <p>Nginx经常被用于终结SSL连接,比如说多说采用第三方登录的图片并不支持https,可以通过改造图片URL(指向https某个目录下),然后Nginx将https反向代理到http上游服务器获取图片来解决。</p> <p>以下以解决多说头像不支持http问题为例,将https://wenjs.me/proxy/xxx.png转发到http://xxx.png</p> <p>Server配置如下:</p> <pre> <code class="language-groovy">server { listen 443 ssl http2 default; server_name wenjs.me; ssl on; ssl_certificate xxx.crt; ssl_certificate_key xxx.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache shared:SSL:10m; ssl_session_timeout 60m; ssl_session_tickets on; ssl_stapling on; ssl_stapling_verify on; ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; ssl_prefer_server_ciphers on; # other location resolver 8.8.8.8; # 转发到http源地址 location ~ ^/proxy/(\w+\.)(bdimg\.com|cdncache\.org|douban\.com|gravatar\.com|qlogo\.cn|sinaimg\.cn|xnimg\.cn)(\/.*)$ { valid_referers blocked server_names; if ($invalid_referer) { return 403; } proxy_connect_timeout 10s; proxy_read_timeout 10s; proxy_pass http://$1$2$3; expires max; } # 返回默认图片 location ~ ^/proxy/(.*)$ { valid_referers blocked server_names; if ($invalid_referer) { return 403; } rewrite https://static.duoshuo.com/images/noavatar_default.png permanent; } }</code></pre> <p>下载多说的embed.js,修改头像的URL,页面引用该新的embed.js即可。</p> <pre> <code class="language-groovy">avatarUrl: function(e) { if (e.avatar_url) { e.avatar_url = e.avatar_url.replace(/^http\:\/\//, "//wenjs.me/proxy/"); } return e.avatar_url || nt.data.default_avatar_url }</code></pre> <p>跨域问题</p> <p>在工作中,有时候会遇到一些接口不支持跨域,这时候可以简单的添加add_headers来支持cors跨域。配置如下:</p> <pre> <code class="language-groovy">server { listen 80; server_name api.xxx.com; add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET,POST,HEAD'; location / { proxy_pass http://127.0.0.1:3000; 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; } }</code></pre> <p><strong>正向代理</strong></p> <p>正向代理是一个位于客户端和原始服务器之间的代理服务器,为了从原始服务器取得内容,客户端向代理服务器发送一个请求并指定目标(原始服务器),然后代理服务器向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。</p> <p>比如说,一个用户访问不了某网站A,但是他能访问一个代理服务器,这个代理服务器能访问A,于是用户可以先连上代理服务器,告诉它需要访问的内容,代理服务器去取回来返回给用户。</p> <p><strong>透明代理</strong></p> <p>透明代理的意思是客户端根本不需要知道有代理服务器的存在,它改编你的请求头(报文),并会传送真实IP。以前在学校的时候,校园网就采用了透明代理的方式。</p> <h3>总结</h3> <p>以上内容主要介绍了Nginx的安装、配置指令以及Proxy模块相关的知识,希望能对Nginx有个初步的认识,在前端开发中,能够利用Nginx来快速搭建一些调试环境。</p> <p> </p> <p>来自:http://www.androidchina.net/5134.html</p> <p> </p>