nginx配置陷阱

Nginx 配置陷阱

Posted on 2013/07/30 by 虚伪的灵魂

注意, 本篇文章中的配置方式, 可能在各版本中有所差异, 配置投入生产环境前, 务必确认你已经理解了这些配置, 且该配置在当前生产环境下是正常可用的.

location区块之下的root指令

点评: root 指令置于 location 之下的配置是完全可用的, 但是错就错在你只要一新增 location 区块, 你就需要为这个 location 添加一条 root.这样的情况下我们不如参考一下好评配置.

重复的 index 指令

http {
  index index.php index.htm index.html;
  server {
    server_name www.domain.com;
    location / {
      index index.php index.htm index.html;
      [...]
    }
  }
  server {
    server_name domain.com;
    location / {
      index index.php index.htm index.html;
      [...]
    }
    location /foo {
      index index.php;
      [...]
    }
  }
}

点评: 不需要重复这么多次index指令, 只要把他置于 http 区块之下, 下级区块就可以继承这一指令的配置了.

http {
  index index.php index.htm index.html;
  server {
    server_name www.domain.com;
    location / {
      [...]
    }
  }
  server {
    server_name domain.com;
    location / {
      [...]
    }
    location /foo {
      [...]
    }
  }
}

使用if指令

由于篇幅有限, if指令的陷阱只介绍一小部分, 更多内容请点击Nginx配置陷阱之万恶的if指令.

服务器域名

server {
  server_name domain.com *.domain.com;
  if ($host ~* ^www\.(.+)) {
    set $raw_domain $1;
    rewrite ^/(.*)$ $raw_domain/$1 permanent;
    [...]
  }
}

点评: 这个配置一共有三处问题.首先是 if 的使用, 为啥他差的掉渣? 你有没有阅读Nginx配置陷阱之万恶的if指令?当nginx收到无论来自哪个子域名的何种请求, 域名是否以 www 开头的判断总是会执行. 自然而然的该配置会导致Nginx检查每次请求的host header,这显然是十分低效的.因此,不如使用两个server区块来实现上述配置需求.

server {
  server_name www.domain.com;
  return 301 $scheme://domain.com$request_uri;
}
server {
  server_name domain.com;
  [...]
}

点评: 上述指令中的 $scheme 可以传递当前host的请求协议.

文件是否存在

使用if来确认文件是否存在是相当可怕的. 如果你使用了较新版本的nginx, 你应该看看 try_files, 这会使你的生活变得更轻松.o

server {
  root /var/www/domain.com;
  location / {
    try_files $uri $uri/ /index.html;
  }
}

点评: 现在改变的是我们尝试不使用if来断定\(uri是否存在, 使用 try_files 意味着你可以测试一个序列. 如果 \)uri 不存在, 尝试使用 $uri/, 如果那个也不存在则尝试使用一个回调路径.

在这样的情况下, 我们就可以看到, 如果 $uri 存在, 则正常服务. 如果不存在, 就试试目录是否存在, 如果还是不存在他就会使用 index.html 这个你确定一定会存在的地址提供服务.他的加载如此简单, 这是你可以消除if的另一个实例.

基于包的前端控制器模式

基于包的前端控制器模式(Front Controller Pattern based packages)

前端控制器模式是一种很受欢迎的设计, 它被用在很多最流行的PHP软件包(packages)里.很多人给出的软件包在Nginx下运行的配置过于复杂.其实要想让 Drupal, Joomla 等工作起来, 只要使用如下的配置就可以了:

try_files $uri $uri/ /index.php?q=$uri&$args;

你实际使用的软件包的在参数上的差异需要注意, 例如:

“q” 参数是 Drupal, Joomla, WordPress 需要的
“page” 则是 CMS Made Simple 需要的 

而另一些软件甚至都不需要 query string, 他可以从 REQUEST_URI 中读取对应信息, 例如 WordPress 就支持这样配置:

try_files $uri $uri/ /index.php;

而且如果你根本不在意, 如果\(uri不存在时, \)uri是否存在的话, 你完全可以 “$uri/” 这条配置将不可控请求传递给PHP。

许多网络上倡导的PHP配置的例子都是都是将传递给以.php结尾的URI传递给PHP解释器.请注意, 目前大多数类似的PHP设置都有严重的安全问题, 因为它可能允许第三方执行任意代码.

这个有问题的配置通常如下:

location ~* \.php$ {
  fastcgi_pass backend;
  ...
}

在这里任意以.php结尾的请求都会传递给 FastCGI backend, 这样做的会出现的问题是, 当完整的路径未能指向文件系统里一个确切的文件时, PHP的默认配置会试图猜测你想要执行的文件是什么.

举个实例, 如果一个如下的请求/forum/avatar/1232.jpg/file.php中的文件并不存在,而 /forum/avatar/1232.jpg 存在, 那么PHP解释器就会取而代之的使用 /forum/avatar/1232.jpg 来解释. 如果这里面包含了嵌入式的PHP代码, 这段代码就会被执行.

下面给出避免出现这一情况的给中解决方案:

脚本文件里的FastCGI路径

不解释

繁重的重写

不要一看到正则表达式就发慌. 事实上它很简单,我们应该努力让他们干净整洁. 相当简单,毫不冗余.

# 屌丝配置:
rewrite ^/(.*)$ http://domain.com/$1 permanent;

# 依然是屌丝配置:
rewrite ^ http://domain.com$request_uri? permanent;

# 高富帅写法:
return 301 http://domain.com$request_uri;

看看那些屌丝写法,再看会来,再看看那些屌丝,再看回来.好了.第一个配置捕获了去掉了第一个斜线的完整路径.而通过使用内置变量$request_uri, 我们可以有效地避免做任何捕获或匹配, 使用 return 指令, 我们可以完全避免的正则表达式的匹配过程.

忽略http://的重写

这点比较简单, 重写(的路径)是相对的除非你告诉nginx他们不是.

# 差评配置:
rewrite ^/blog(/.*)$ blog.domain.com$1 permanent;

# 好评配置:
rewrite ^/blog(/.*)$ http://blog.domain.com$1 permanent;

代理所有东西

这个例子中, 所有的东西都交给PHP处理了. Apache可能需要这么整(黑的漂亮), 但是Nginx中则不必. try_files 指令按特定的顺序尝试访问文件.这就意味着 nginx 可以先试试看静态文件, 如果没找到再转向到用户定义的回调上.这种方式下PHP就不用进去搞基了,除非确实有基友被请求了, 服务器也节约了资源.然后我们看看怎么撸才是正确的.

配置没有变化

可能是你的浏览器缓存导致的, 解决方式:

VirtualBox

如果你在VirtualBox的虚拟机里运行的Nginx跑不起来, 那么很有可能是 sendfile() 导致了这一问题的出现, 修复如下:

sendfile off;

丢失(消失)的HTTP标头

如果你没有明确的设置 underscores_in_headers on;, nginx将丢弃的HTTP头信息用下划线表示(这是完全有效的HTTP标准). 这样做是为了防止头信息映射到CGI变量是产生歧义.

文章参考以及大部分翻译来源: http://wiki.nginx.org/Pitfalls