# NGINX 陷阱和常見錯誤
翻譯自:[https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/)
> ### 警告:
> **請閱讀下面所有的內容!是所有的!**
不管是新手還是老用戶,都可能會掉到一個陷阱中去。下面我們會列出一些我們經常看到,和經常需要解釋如何解決的問題。在 Freenode 的# NGINX IRC頻道中,我們頻繁的看到這些問題出現。
### 本指南說
最經常看到的是,有人從一些其他的指南中,嘗試拷貝、粘貼一個配置片段。并不是說其他所有的指南都是錯的,但是里面錯誤的比例很可怕。即使是在 Linode 庫中也有質量較差的信息,一些 NGINX 社區成員曾經徒勞的試圖去糾正。
本指南的文檔,是社區成員所創建和審查,他們直接和所有類型的 NGINX 用戶在一起工作。這個特定的文檔之所以存在,是因為社區成員看到有大量普遍和重復出現的問題。
### 我的問題沒有被列出來
在這里你沒有看到和你具體問題相關的東西。也許我們并沒有解決你經歷的具體問題。不要只是大概瀏覽下這個網頁,也不要假設你是無意才找到這里的。你找到這里,是因為這里列出了你做錯的一些東西。
在許多問題上,當涉及到支持很多用戶,社區成員不希望去支持破碎的配置。所以在提問求助前,先修復你的配置。通讀這個文檔來修復你的配置,不要只是走馬觀花。
### chmod 777
**永遠不要** 使用777。這可能是一個漂亮的數字,有時候可以懶惰的解決權限問題,但是它同樣也表示你沒有線索去解決權限問題,你只是在碰運氣。你應該檢查整個路徑的權限,并思考發生了什么事情。
要輕松的顯示一個路徑的所有權限,你可以使用
~~~
namei -om /path/to/check
~~~
### 把root放在location區塊內
糟糕的配置:
~~~
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 。如果沒有添加,就會沒有 root 。讓我們看下正確的配置。
推薦的配置:
~~~
server {
server_name www.example.com;
root /var/www/nginx -default/;
location / {
# [...]
}
location /foo {
# [...]
}
location /bar {
# [...]
}
}
~~~
### 重復的index指令
糟糕的配置:
~~~
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 {}區塊里面,下面的就會繼承這個配置。
推薦的配置:
~~~
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 指令的陷阱。更多陷阱你應該點擊看看邪惡的 if 指令。我們看下 if 指令的幾個邪惡的用法。
> **注意看這里**:
> [邪惡的 if 指令](#)
#### 用if判斷Server Name
糟糕的配置:
~~~
server {
server_name example.com *.example.com;
if ($host ~* ^www\.(.+)) {
set $raw_domain $1;
rewrite ^/(.*)$ $raw_domain/$1 permanent;
}
# [...]
}
}
~~~
這個配置有三個問題。首先是if的使用, 為啥它這么糟糕呢? 你有閱讀邪惡的if指令嗎?當 NGINX 收到無論來自哪個子域名的何種請求,不管域名是www.example.com還是example.com,這個fi指令**總是**會被執行。 因此 NGINX 會檢查**每個請求**的Host header,這是十分低效的。 你應該避免這種情況,而是使用下面配置里面的兩個server指令。
推薦的配置:
~~~
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 ,你應該看看rty_files,這會讓你的生活變得更輕松。
糟糕的配置:
~~~
server {
root /var/www/example.com;
location / {
if (!-f $request_filename) {
break;
}
}
}
~~~
推薦的配置:
~~~
server {
root /var/www/example.com;
location / {
try_files $uri $uri/ /index.html;
}
}
~~~
我們不再嘗試使用 if 來判斷$uri是否存在,用 try_files 意味著你可以測試一個序列。如果 $uri 不存在,就會嘗試 $uri/ ,還不存在的話,在嘗試一個回調 location 。
在上面配置的例子里面,如果 $uri 這個文件存在,就正常服務;如果不存在就檢測 $uri/ 這個目錄是否存在;如果不存在就按照 index.html 來處理,你需要保證 index.html 是存在的。try_files的加載是如此簡單。這是另外一個你可以完全的消除 if 指令的實例。
### 前端控制器模式的web應用
“前端控制器模式”是流行的設計,被用在很多非常流行的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;
# [...]
}
~~~
### 腳本文件名里面的FastCGI路徑
很多外部指南喜歡依賴絕對路徑來獲取你的信息。這在 PHP 的配置塊里面很常見。當你從倉庫安裝 NGINX ,通常都是以在配置里面折騰好“include fastcgi_params;”來收尾。這個配置文件位于你的 NGINX 根目錄下,通常在/etc/nginx/里面。
推薦的配置:
~~~
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
~~~
糟糕的配置:
~~~
fastcgi_param SCRIPT_FILENAME /var/www/yoursite.com/$fastcgi_script_name;
~~~
$document_root$ 在哪里設置呢?它是 server 塊里面的 root 指令來設置的。你的 root 指令不在 server 塊內?請看前面關于 root 指令的陷阱。
### 費力的rewrites
不要知難而退, rewrite 很容易和正則表達式混為一談。實際上, rewrite 是很容易的,我們應該努力去保持它們的整潔。很簡單,不添加冗余代碼就行了。
糟糕的配置:
~~~
rewrite ^/(.*)$ http://example.com/$1 permanent;
~~~
好點兒的配置:
~~~
rewrite ^ http://example.com$request_uri? permanent;
~~~
更好的配置:
~~~
return 301 http://example.com$request_uri;
~~~
反復對比下這幾個配置。第一個 rewrite 捕獲不包含第一個斜杠的完整 URI 。使用內置的變量 $request_uri ,我們可以有效的完全避免任何捕獲和匹配。
### 忽略 http:// 的rewrite
這個非常簡單, rewrites 是用相對路徑的,除非你告訴 NGINX 不是相對路徑。生成絕對路徑的 rewrite 也很簡單,加上 scheme 就行了。
糟糕的配置:
~~~
rewrite ^ example.com permanent;
~~~
推薦的配置:
~~~
rewrite ^ http://example.com permanent;
~~~
你可以看到我們做的只是在 rewrite 里面增加了 *http://*。這個很簡單而且有效。
### 代理所有東西
糟糕的配置:
~~~
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 可能要這樣子做,但在 NGINX 里你不必這樣。換個思路,try_files 有一個神奇之處,它是按照特定順序去嘗試文件的。這意味著 NGINX 可以先嘗試下靜態文件,如果沒有才繼續往后走。這樣PHP就不用參與到這個處理中,會快很多。特別是如果你提供一個1MB圖片數千次請求的服務,通過PHP處理還是直接返回靜態文件呢?讓我們看下怎么做到吧。
推薦的配置:
~~~
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;
}
}
~~~
另外一個推薦的配置:
~~~
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 處理;只有在 NGINX 不能直接處理請求的URI的時候,才會進入 proxy 這個 location 來處理。
現在,考慮下你的請求中有多少靜態內容,比如圖片、css、javascript等。這可能會幫你節省很多開銷。
### 配置的修改沒有起效
瀏覽器緩存。你的配置可能是對的,但怎么嘗試結果總是不對,百思不得其解。罪魁禍首是你的瀏覽器緩存。當你下載東西的時候,瀏覽器做了緩存。
怎么修復:
-
在 Firefox 里面 Ctrl+Shift+Delete ,檢查緩存,點擊立即清理。可以用你喜歡的搜索引擎找到其他瀏覽器清理緩存的方法。每次更改配置后,都需要清理下緩存(除非你知道這個不必要),這會省很多事兒。
-
使用 curl 。
### VirtualBox
如果你在 VirtualBox 的虛擬機中運行 NGINX ,而它不工作,可能是因為 sendfile() 引起的麻煩。只用簡單的注釋掉 sendfile 指令,或者設置為 off。 該指令大都會寫在 NGINX .conf 文件中:
~~~
sendfile off;
~~~
### 丟失(消失)的 HTTP 頭
如果你沒有明確的設置 underscores_in_headers on; , NGINX 將會自動丟棄帶有下劃線的 HTTP 頭(根據 HTTP 標準,這樣做是完全正當的). 這樣做是為了防止頭信息映射到 CGI 變量時產生歧義,因為破折號和下劃線都會被映射為下劃線。
### 沒有使用標準的 Document Root Location
在所有的文件系統中,一些目錄永遠也不應該被用做數據的托管。這些目錄包括 / 和 /root 。你永遠不應該使用這些目錄作為你的 document root。
使用這些目錄的話,等于打開了潘多拉魔盒,請求會超出你的預期獲取到隱私的數據。
**永遠也不要這樣做!!!** ( 對,我們還是要看下飛蛾撲火的配置長什么樣子)
~~~
server {
root /;
location / {
try_files /web/$uri $uri @php;
}
location @php {
[...]
}
}
~~~
當一個對 /foo 的請求,會傳遞給 PHP 處理,因為文件沒有找到。這可能沒有問題,直到遇到 /etc/passwd 這個請求。沒錯,你剛才給了我們這臺服務器的所有用戶列表。在某些情況下, NGINX 的 workers 甚至是 root 用戶運行的。那么,我們現在有你的用戶列表,以及密碼哈希值,我們也知道哈希的方法。這臺服務器已經變成我們的肉雞了。
Filesystem Hierarchy Standard (FHS) 定義了數據應該如何存在。你一定要去閱讀下。 簡單點兒說,你應該把 web 的內容**放在 /var/www/ , /srv 或者 /usr/share/www 里面**。
### 使用默認的 Document Root
在 Ubuntu、 Debian 等操作系統中, NGINX 會被封裝成一個易于安裝的包, 里面通常會提供一個 『默認』的配置文件作為范例,也通常包含一個 document root 來保存基礎的 HTML 文件。
大部分這些打包系統,并沒有檢查默認的 document root 里面的文件是否修改或者存在。 在包升級的時候,可能會導致代碼失效。有經驗的系統管理員都知道,不要假設默認的 document root 里面的數據在升級的時候會原封不動。
你不應該使用默認的 document root 做網站的任何關鍵文件的目錄。 并沒有默認的 document root 目錄會保持不變這樣的約定,你網站的關鍵數據, 很可能在更新和升級系統提供的 NGINX 包時丟失。
### 使用主機名來解析地址
糟糕的配置:
~~~
upstream {
server http://someserver;
}
server {
listen myhostname:80;
# [...]
}
~~~
你不應該在 listen 指令里面使用使用主機名。雖然這樣可能是有效的,但它會帶來層出不窮的問題。其中一個問題是,這個主機名在啟動時或者服務重啟中不能解析。這會導致 NGINX 不能綁定所需的 TCP socket 而啟動失敗。
一個更安全的做法是使用主機名對應 IP 地址,而不是主機名。這可以防止 NGINX 去查找 IP 地址,也去掉了去內部、外部解析程序的依賴。
例子中的 upstream location 也有同樣的問題,雖然有時候在 upstream 里面不可避免要使用到主機名,但這是一個不好的實踐,需要仔細考慮以防出現問題。
推薦的配置:
~~~
upstream {
server http://10.48.41.12;
}
server {
listen 127.0.0.16:80;
# [...]
}
~~~
### 在 HTTPS 中使用 SSLv3
由于 SSLv3 的 [POODLE 漏洞](https://www.openssl.org/~bodo/ssl-poodle.pdf),建議不要在開啟 SSL 的網站使用 SSLv3。你可以簡單粗暴的直接禁止 SSLv3, 用 TLS 來替代:
~~~
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
~~~
- 序
- Lua 入門
- Lua簡介
- Lua環境搭建
- 基礎數據類型
- 表達式
- 控制結構
- if/else
- while
- repeat
- for
- break,return
- Lua函數
- 函數的定義
- 函數的參數
- 函數的返回值
- 函數回調
- 模塊
- String庫
- Table庫
- 日期時間函數
- 數學庫函數
- 文件操作
- 元表
- 面向對象編程
- FFI
- 下標從1開始
- 局部變量
- 判斷數組大小
- 非空判斷
- 正則表達式
- 不用標準庫
- 虛變量
- 函數在調用代碼前定義
- 抵制使用module()函數來定義Lua模塊
- 點號與冒號操作符的區別
- Nginx
- Nginx 新手起步
- location 匹配規則
- if 是邪惡的
- 靜態文件服務
- 日志服務
- 反向代理
- 負載均衡
- 陷阱和常見錯誤
- 環境搭建
- Windows平臺
- CentOS平臺
- Ubuntu平臺
- Mac OS X平臺
- Hello World
- 簡單API Server框架
- 獲取Nginx內置綁定變量
- LuaRestyRedisLibrary
- select+set_keepalive組合操作引起的數據讀寫錯誤
- redis接口的二次封裝(簡化建連、拆連等細節)
- redis接口的二次封裝(發布訂閱)
- pipeline壓縮請求數量
- script壓縮復雜請求
- LuaCjsonLibrary
- json解析的異常捕獲
- 稀疏數組
- 空table編碼為array還是object
- 跨平臺的庫選擇
- PostgresNginxModule
- 調用方式簡介
- 不支持事務
- 超時
- 健康監測
- SQL注入
- LuaNginxModule
- 執行階段概念
- 正確的記錄日志
- 熱裝載代碼
- 阻塞操作
- 緩存
- sleep
- 定時任務
- 禁止某些終端訪問
- 請求返回后繼續執行
- 調試
- 調用其他C函數動態庫
- 我的lua代碼需要調優么
- 變量的共享范圍
- 動態限速
- shared.dict 非隊列性質
- 如何添加自己的lua api
- 正確使用長鏈接
- 如何引用第三方resty庫
- 典型應用場景
- LuaRestyDNSLibrary
- 使用動態DNS來完成HTTP請求
- 緩存失效風暴
- 測試
- 單元測試
- API測試
- 性能測試
- 持續集成
- 灰度發布
- Web 服務
- API的設計
- 數據合法性檢測
- 協議無痛升級
- 代碼規范
- 連接池
- C10K編程
- TIME_WAIT問題
- 與Docker使用的網絡瓶頸
- 火焰圖
- 什么時候使用
- 顯示的是什么
- 如何安裝火焰圖生成工具
- 如何定位問題
- 開源文化對360企業安全的影響