nginx 配置檔案的匹配規則
引出
之前在對php-fpm
進行nginx
代理時, 為了對後臺限定 IP 訪問, 添加了如下配置:
location ^~ /admin {
allow 127.0.0.1;
deny all;
}
結果呢? 所有admin
路徑下的php
檔案, 全都沒有解析, 變成檔案下載了. 當時我不知道是什麼問題, 不過將這段配置去掉之後, 問題就消失了. 所以, 我可以肯定的是, 一定是這段路徑匹配的問題, 導致沒有走php-fpm
的解析.
探究
為了探究原因, 我查詢資料並做了嘗試. 如果想直接看結果, 可以跳過這一 part.
在上方出現問題的場景中, nginx
的配置檔案大體如下:
server { listen 80; server_name localhost; root /var/www/html; index index.php; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { try_files $uri =404; //...此處省略 fpm 配置 } location ^~ /admin { allow 127.0.0.1; deny all; } }
經過思考, 當我訪問localhost/admin/test.php
的時候, nginx
沒有執行第二個匹配規則, 沒有將檔案交由php-fpm
解析器執行, 進而導致其作為靜態檔案直接下載.
接下來, 就是驗證這個想法了. 最簡單的驗證方法, 就是在nginx
匹配規則中, 直接返回 HTTP 響應嗎. 這樣用curl
看一下響應碼, 就知道執行了哪個規則了.
說幹就幹, 修改配置檔案如下:
server { listen 80; server_name localhost; location / { return 300; } location ~ \.php$ { return 200; } location ^~ /admin { return 100; } }
和猜想的一樣, 即使匹配規則在前面, 但是仍然先匹配到了規則^~
. 也就是說規則 ^~
比規則 ~
的匹配優先順序更高.
不過還有一點無法確定, 即使先匹配到了後面的規則, 那也不能說明前面的規則就不走了啊. nginx
也有可能是按照順序依次進行匹配的.
為了驗證, 我們將第三個配置規則中的return 100
刪掉. 此時, 如果能夠匹配到php
的規則, 那麼就會返回響應碼200, 如果不能, 應該提示找不到檔案. 測試一下.
至此說明匹配到 ^~
規則的時候, 就會直接執行而不進行後續的匹配了. 那問了, 有可能是因為兩個匹配規則的優先順序不同, 故而忽略了優先順序低的匹配規則.
為了驗證nginx
server {
listen 80;
server_name localhost;
location ~ hp$ {
return 400;
}
location ~ php$ {
return 500;
}
}
配置檔案中兩個正則匹配, 我的想法是這樣的, 此時訪問, 會返回響應嗎 400, 說明匹配了第一個規則, 然後我將第一個規則中的return 400
刪除, 如果返回了 500, 就說明nginx
在匹配了第一個規則之後, 繼續執行了下一個匹配. 很嚴謹. 先訪問一下:
很好, 符合預期, 然後將第一個規則中的return
刪除, 再次訪問:
這次返回了 404, 這說明, nginx
在執行到第一個匹配的時候, 就停止匹配, 不再進行後續匹配了.
至此, nginx
的匹配規則基本上已經復現出來了.
- 按照優先順序從高到低的順序進行匹配
- 相同優先順序的, 按照配置檔案中的順序進行匹配
- 當匹配到一條規則之後, 停止後續匹配.
匹配規則
接下來整理一下nginx
路徑的匹配規則, 以下優先順序按照從高到底排序:
location = /xxx
: 路徑精確匹配location ^~ /xxx
: 路徑字首匹配location ~ xxx
: 路徑正則匹配location ~* xxx
: 路徑正則匹配, 不區分大小寫, 與正則匹配的優先順序相同location /xxx
: 路徑字首匹配location /
: 通用匹配, 當其他都沒有匹配的時候, 會走到這裡.
nginx
會按照優先順序從高到低依次進行匹配, 在第一個匹配成功的時候執行操作並停止匹配.
回顧
匹配規則看上去很簡潔. 現在可以回頭看一下我們最初遇到的問題了.
我們想讓某後臺地址限定 IP 訪問, 故而添加了這樣的配置:
location ~ \.php${
//...
}
location ^~ /admin {
allow 127.0.0.1;
deny all;
}
現在應該很清楚了吧, 所有admin
下的路徑, 因為規則^~
的優先順序更高, 故而解析到了後面的規則, 而沒有執行php
的解析操作. 又因為沒有解析操作, 故而 php 檔案都當做資原始檔返回了.
那麼問題來了, 如果我想對admin
路徑下的路徑執行訪問限制, 改怎麼辦呢?
-
將規則
^~
改成~
? 不行, 因為優先順序相同, 先匹配到前面的 php 正則匹配, 後面的限制沒有效果 -
將規則
^~
改成~
並提到前面? 不行, 因為優先順序相同, 先匹配到限制, 如果通過不會進行後面的 php 解析.
這不陷入死迴圈了麼? 我又想對某個路徑執行限制, 如果限制通過的話, 又需要能夠正常解析. 怎麼破? 這裡我探索出來的思路是, 他不是不認識php
檔案麼, 我讓他認識認識不就完了麼. 直接將匹配的解析過程巢狀寫入, 配置檔案大體如下:
location ^~ /admin{
deny all;
location ~ \.php$ {
//...
}
}
這樣的話, 就可以達到在執行 IP 限制的前提下, 又能夠正常解析php-fpm
.
那麼一個新的問題來了, 這不就相當於將 php 的解析複製了一遍麼? 也太不優雅了. 我想到的方案是, 通過nginx
的include
命令. 通過將php
檔案的解析配置單獨放到一個配置檔案php-fpm.conf.common
檔案中, 內容如下:
location ~ \.php${
// ...
}
這樣, 原本的配置檔案就可以改寫成如下形式了:
location ^~ /admin {
allow 127.0.0.1;
deny all;
# 這裡因為相對路徑使用的是 nginx.conf 的路徑, 所以需要再走一層
include ./conf.d/php-fpm.conf.comon
}
include ./conf.d/php-fpm.conf.common
此時, 就能夠實現之前的目的了, admin
路徑下的php
檔案僅對指定 ip 開放, 且通過時能夠正常進行解析.
有可能有更優雅的解決方案, 我看網上有些實現是通過rewrite
的方式來實現的, 但是我試了很多次都沒有成功. 如果你有更好的方式, 還望不吝賜教.
經過幾天的實驗, 終於把nginx
的執行順序搞懂了, 感謝我的中學老師會了我控制變數法.