NGINX 配置陷阱和常见错误
关于本指南
这是一篇来自 NGINX 官方 WIKI 的指南,由 Github 的 renyidong 翻译。
无论新老用户,都有可能掉进陷阱。
下面我们列出了常见的问题,并解释如何解决他们。
在 Freenode IRC 的 #nginx 频道,我们经常看到这些问题。
最常见的问题是有人从其他指南拷贝配置片段。
不能说所有的指南都是错的,但绝大部分是有问题的。
即使是 Linode library,也有部分极其劣质的信息,NGINX 社区成员正在努力去尝试更正。
本站文档由了解各种 NGINX 用户的社区成员创建并审核。
本文的纂写仅仅是鉴于社区成员所遇到的、大量的、常见的、反复出现的问题。
未列出我的问题
在这里,也许你找不到任何和你的问题直接相关的内容。
这是因为,我们并不是因为你遇到的具体问题而让你来这里。
不要仅仅瞥了一眼就断言我们无缘无故带你到此。
我们让你来这里,是因为你犯了某个这里提到的错误。
对于大量的用户遇到的大量的问题,社区成员不想去支持错误的配置。
请先改正你的配置,再请求帮助。
你要认真阅读本文才能改正你的配置。不要随便扫一遍了事。
Chmod 777
绝不使用 777。它可能是个用着相当顺手的数字。但就算你只是在测试,它意味着你根本不知道你在干什么。
你应该仔细检查整个路径的每个部分的权限,仔细思考它们会产生什么效果,要简单的检查路径的权限,可以使用:
namei -om /path/to/check
Location 段里的 Root 选项
BAD
server {
server_name www.example.com;
location / {
root /var/www/nginx-default/;
# [...]
}
location /foo {
root /var/www/nginx-default/;
# [...]
}
location /bar {
root /var/www/nginx-default/;
# [...]
}
}
这确实能用。把root放在location区块可以生效而且合乎语法。
但是当需要增加location区块,你要逐个location区块添加root指令,没有匹配任何location的请求将没有root。
下面看看正确的配置。
GOOD
server {
server_name www.example.com;
root /var/www/nginx-default/;
location / {
# [...]
}
location /foo {
# [...]
}
location /bar {
# [...]
}
}
多重 Index 指令
BAD
http {
index index.php index.htm index.html;
server {
server_name www.example.com;
location / {
index index.php index.htm index.html;
# [...]
}
}
server {
server_name example.com;
location / {
index index.php index.htm index.html;
# [...]
}
location /foo {
index index.php;
# [...]
}
}
}
为什么在不需要时重复那么多行。简单的使用一次 “index” 指令。仅仅需要添加到*http{}*区块,将会被继承。
GOOD
http {
index index.php index.htm index.html;
server {
server_name www.example.com;
location / {
# [...]
}
}
server {
server_name example.com;
location / {
# [...]
}
location /foo {
# [...]
}
}
}
使用 If
有几个页面介绍使用 if 语句。它叫做IfIsEvil,你真的应该看看。让我们看看使用 if 的坏处。
See also
Server Name (if)
BAD
server {
server_name example.com *.example.com;
if ($host ~* ^www\.(.+)) {
set $raw_domain $1;
rewrite ^/(.*)$ $raw_domain/$1 permanent;
}
# [...]
}
}
这里有3个问题。第一个是if,我们关心的。当 NGINX 接受一个请求不管是子域名 www.example.com
还是 example.com,if指令总是会判断。因为你要求 NGINX 检查每个请求的 Host 头。
这样效率低下,你应该避免。像下面一样使用2个server指令。
GOOD
server {
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
server {
server_name example.com;
# [...]
}
除了配置文件更易于阅读,这种方法减少 NGINX 处理需求。我们摆脱了虚假的if,同时使用
$scheme
,没有硬编码 URI 的结构,可以是 http 或者 https。
检查文件 (if) 存在
使用 if 去检查文件是否存在是糟糕的。如果你使用最新版本的 NGINX ,应该看看try_files
,更
易使用。
BAD
server {
root /var/www/example.com;
location / {
if (!-f $request_filename) {
break;
}
}
}
GOOD
server {
root /var/www/example.com;
location / {
try_files $uri $uri/ /index.html;
}
}
这里我们判断$uri
是否存在不再需要if
。使用try_files
意味着可以依序测试。
如果$uri
不存在,尝试$uri/
,如果还不存在,尝试备用的location。
在这个案例中,会先查看$uri
文件是否存在,存在就返回。如果不存在,就检查该目录是否存在。
如果不存在,将返回index.html
,你必须确定存在该文件。
Front Controller 模式 WEB 应用
“Front Controller 模式”设计是受欢迎的,且用在许多最流行的 PHP 软件包中。
很多实例比应有的更复杂。让 Drupal, Joomla 等运行,使用:
try_files $uri $uri/ /index.php?q=$uri&$args;
注解 - 参数名称根据你使用的包是不同的。例如
- “q” 是 Drupal、Joomla、WordPress 使用的参数
- “page” 是 CMS Made Simple 使用的参数
一些软件甚至不用查询字符串,而且可以从REQUEST_URI
读取(WordPress支持,例如):
try_files $uri $uri/ /index.php;
当然你的情况可能不同,你可能根据需求添加复杂的规则,但一个基本的网站,这些将完美的工作。
你可以从简单的开始。
如果你不在乎目录是否存在,你也可以决定跳过目录检查,从中删除$uri/
。
把不受控制的请求给 PHP
很多对 PHP 的 NGINX 配置样例把每个以.php
结尾的 URI 给 PHP 解释器。请注意在大多数 PHP
设置中,这将是个严重的安全问题,因为它允许任意第三方代码执行。
出问题的区域通常如下:
location ~* \.php$ {
fastcgi_pass backend;
# [...]
}
这里每一个以.php
结尾文件的请求将会给FastCGI后端。
这里的问题是如果全路径并不指向文件系统上实际的文件,默认的 PHP 配置会试图猜测你需要执行哪个文件。
例如,如果请求不存在的文件/forum/avatar/1232.jpg/file.php
,但/forum/avatar/1232.jpg
存在,
PHP 解释器将处理/forum/avatar/1232.jpg
。如果它包含 PHP 代码,这段代码将相应的执行。
下面的选项可以避免上述情况:
- 在php.ini中设置
cgi.fix_pathinfo=0
。使得 PHP 解释器仅尝试给出的路径,如果文件没有找到就停止处理。 - 保证 NGINX 仅传给后端指定的 PHP 文件请求:
location ~* (file_a|file_b|file_c)\.php$ {
fastcgi_pass backend;
# [...]
}
- 特别地在任何包含用户上传的目录中禁用 PHP 文件的执行
location /uploaddir {
location ~ \.php$ {return 403;}
# [...]
}
- 使用try_files指令过滤
location ~* \.php$ {
try_files $uri =404;
fastcgi_pass backend;
# [...]
}
- 使用嵌套的 location 过滤
location ~* \.php$ {
location ~ \..*/.*\.php$ {return 404;}
fastcgi_pass backend;
# [...]
}
Script Filename 中的 FastCGI Path
一些指南倾向使用绝对路径得到信息。这在 PHP 区块很常见。当你从源安装 NGINX ,通常你能够把
include fastcgi_params;
加入到配置中。该文件通常在 NGINX 配置目录/etc/nginx/
。
GOOD
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
BAD
fastcgi_param SCRIPT_FILENAME /var/www/yoursite.com/$fastcgi_script_name;
$document_root
在哪里设置?是通过 root 指令在 server 区块设置。root 指令没有配置?看看
第一个陷阱。
繁杂的 Rewrites
不要感觉糟糕,正则表达式容易让人困惑。实际上,我们应该努力保持整洁、简单,不增加繁琐的东西,这容易做到。
BAD
rewrite ^/(.*)$ http://example.com/$1 permanent;
GOOD
rewrite ^ http://example.com$request_uri? permanent;
BETTER
return 301 http://example.com$request_uri;
第一个 rewrite 捕获在第一个*/*之后的 URI。通过使用内建变量$request_uri
,我们可以有效的避免做任何捕获或者匹配。
Rewrite 丢失 http://
rewrite 是简单,记得添加scheme
。
BAD
rewrite ^ example.com permanent;
GOOD
rewrite ^ http://example.com permanent;
在上面可以看到,我们在 rewrite 添加了http://
。简单有效。
代理一切
BAD
server {
server_name _;
root /var/www/site;
location / {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/tmp/phpcgi.socket;
}
}
令人讨厌的,在这种情况下把所有的请求都给 PHP 处理。为什么?Apache 也许是这样做,但你不必。让我这么说吧...
try_files
指令存在的原因,它按特定的顺序尝试文件。意味着 NGINX 可以先尝试提供静态内容。如果不能,继续尝试。
这样 PHP 根本不参与,快的多。特别是如果你通过 PHP 提供 1MB 图片的时间是直接提供的1000倍,让我们看看该怎么做。
GOOD
server {
server_name _;
root /var/www/site;
location / {
try_files $uri $uri/ @proxy;
}
location @proxy {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/tmp/phpcgi.socket;
}
}
Also GOOD
server {
server_name _;
root /var/www/site;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/tmp/phpcgi.socket;
}
}
很简单,是不是?如果请求的 URI 存在,NGINX 直接提供。如果不存在,检查目录是否存在。如果不存在,传递给你的代理。
只有当 NGINX 不能直接服务请求的 URI,你的代理将参与开销。
现在想想你的多少请求是静态资源,比如 images、css、javascript 等。你可能节省很多开销。
配置更改没有生效
浏览器缓存。你的配置也许是很棒的,但你会坐在那,撞墙一个月。问题出在你的浏览器缓存。
当你下载了东西,你的浏览器会保存下来。同时也保存了文件是如何提供的。如果你配置有 types 区块,你会遇到这情况。
处理方法:
- 在 Firefox 浏览器中按 Ctrl+Shift+Delete,检查缓存,点击 Clear Now。其他浏览器,可搜索下方法。
每次更改之后都清除下,会为你节省不少麻烦。 - 使用 curl。
VirtualBox
如果这不起作用,而且你在 VirtualBox 中的虚拟机中运行 NGINX,也许是sendfile()
引起的问题。
简单的注释掉sendfile
指令,或者设置为“off”。
sendfile off;
缺失的 HTTP 头
如果你没有明确的设置underscores_in_headers on
,NGINX 将会默默地忽略掉带有下划线的头信息(根据 HTTP 标准是完全有效的)。
这样做为了防止头信息映射到 CGI 变量过程中破折号和下划线都映射成下划线引起歧义。
没有使用标准的 Document Root Locations
有些目录在任何文件系统中不应该用于托管数据。其中包括/
和/root
。你不应该使用这些作为网站根目录。
这样作让你的隐私数据毫无预期的返回给请求。
永远不要这样做!!!
server {
root /;
location / {
try_files /web/$uri $uri @php;
}
location @php {
# [...]
}
}
当一个请求/foo
,因为文件找不到将代理到 php 。这看起来正常,直到请求/etc/passwd
。是的,你给了服务器上所有用户列表。
在某些情况下,Nginx 服务器进程被设置以 root 用户运行。是的,我们现在有你的用户列表以及密码 hash 密码,以及如何 hash 的。
文件系统层级标准定义了数据应该存在哪里。你应该看看。
简而言之,你的 web 内容可以存放在/var/www/
、/srv
、/usr/share/www
。
使用默认的 Document Root
Ubuntu、Debian 等其他操作系统中NGINX包,作为易于安装的包会提供默认的配置文件用作一个例子,而且会包含网站根目录存储基本的
HTML 文件。
多数系统包不检查默认的Document Root中文件是否存在或者修改。这样会在软件包升级时导致代码丢失。经验丰富的系统管理员没有期望在
升级期间默认的document root内容保持不变。
你不应该对将网站的关键文件使用默认的document root。在你的系统升级或者更新 NGINX 包时有很大可能性会导致默认的document root
内容会改变。
使用 Hostname 去解析地址
BAD
upstream {
server http://someserver;
}
server {
listen myhostname:80;
# [...]
}
你不应该在 listen 指令中使用 hostname。也许这样有效,也将会有大量的问题。
其中一个问题是在服务启动或者重启时 hostname 不能解析。这会引起 NGINX 无法绑定到特定的 TCP 端口,最终 NGINX 启动失败。
更安全的做法是知道需要绑定的 IP 地址,并使用 IP 而不是 hostname。 阻止 NGINX 需要查找 IP ,不依赖内部或者外部的解析。
upstream 区块也有同样的问题。并不是总要在 upstream 区块避免使用 hostname,这不是好的做法,使用时要考虑清楚。
upstream {
server http://10.48.41.12;
}
server {
listen 127.0.0.16:80;
# [...]
}
HTTPS 中使用 SSLv3
由于 SSLv3 的POODLE漏洞,建议在 SSL 的网站不启用 SSLv3。你可以很简单的禁用 SSLv3,只提供 TLS 协议。
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;