4.4 使用 Nginx
4.4.1 Nginx 的運作方式
當 Nginx 啟動時,它會先建立一個名為 master 的主程序,master 會讀取設定檔及綁定 port 並建立幾個名為 worker 的子程序,這些子程序會執行網路連線、讀寫檔案及和其他服務通訊的工作。worker 可以依需求在硬體容許的情況下增加開啟的數量,如此可以增加服務的連接數,但是不能超過硬體的限制。
4.4.2 瞭解設定檔
Nginx 的設定檔目錄在 /etc/nginx
,其中 nginx.conf
是主要設定檔,你可以把虛擬主機 (網站) 的設定也寫在這裡,但是最佳作法是各個網站獨立為一個檔案放在 conf.d
目錄裡,然後在 nginx.conf
載入這些設定檔。
Nginx 的設定檔由數個指令 (directives) 組成,指令分為簡單指令及區塊指令兩種。
簡單指令由指令名稱及參數組成,中間以空白隔開,並且以分號 ;
結尾,例如:
listen 80;
區塊指令由指令名稱接著一對大括號 {}
包圍成一個區塊,裡面可以包含其他區塊指令及簡單指令,例如:
server {
listen 80;
}
有 4 個區塊指令最重要:events, http, server 及 location。最外層的區塊指令,也就是整個設定檔文件視為 main
區塊,events
及 http
放在 main
區塊中,server
在 http
中,而 location
在 server
中。
基本架構如下:
events {
}
http {
server {
location {
}
}
}
設定檔中如果出現 #
符號,在 #
符號之後的內容稱為註解。
我們先開啟 /etc/nginx/nginx.conf
設定檔邊看邊說明,其中有些設定是為了說明特別加入的,你可以視需要決定是否加入。
main 區塊
註:main 區塊指文件本身,並不會有 main
這個指令出現。
user nginx;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
error_log /var/log/nginx/error.log error;
- user:執行 worker 子程序的使用者名稱,它的後面可以接群組名稱,如果省略,則群組名稱和使用者名稱相同,預設值為
nobody
,因為 Nginx 的使用者名稱為 nginx ,所以這裡使用 nginx。必須放在 main 區塊。 - worker_processes:指定 worker 子程序的數量,預設為 1 個,如果不確定的話可以指定為
auto
。這個值不能亂設定,最佳的值要考慮幾個因素,例如 CPU 的核心數及硬碟的資料存取模式。通常會設定為和 CPU 的核心數相同,例如一顆 CPU 有 4 核心,就可設為 4 ;當你指定為auto
時 Nginx 就會嘗試以此方式來配置 worker 的啟用數量。必須放在 main 區塊。 - pid:定義一個儲存 Nginx 主程序 ID 的檔案。必須放在 main 區塊。
- include: 告訴 Nginx 要載入位於
modules-enabled
目錄的其他模組的設定檔。 - error_log:設定記錄檔,可以放在
main
,http
,mail
,stream
,server
,location
等區塊中。第一個參數是指定要儲存 log 的檔案位置,第二個參數可以指定要記錄的等級:debug
,info
,notice
,warn
,error
,crit
,alert
,emerg
,它會依這個順序記錄比它嚴重的事件,例如指定為error
,則crit
,alert
,emerg
都會被記錄,另一邊的warn
等等則不會被記錄。
events 區塊
events {
# 允許同一時間連線總數量
worker_connections 768;
}
- events 區塊:必須放在 main 區塊中。用來指定連線 (connection) 如何處理。
- worker_connections:一個 worker 子程序可以同時連線的最大值。這個連線數是指包含代理伺服器的連線數和其他連線數的總合,而不單單只有 Client 端連過來的數量。此外,這個值不能超過目前可以開啟的檔案數量的最大值,這個值被指定在 worker_rlimit_nofile 這個指令中。
http 區塊
http {
access_log off;
access_log /var/log/nginx/access.log main;
# log 內容格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# 防 DDOS,限制同一 ip 每秒一個 Request
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
gzip on;
include /etc/nginx/mime.types;
default_type application/octet-stream;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
root /usr/share/nginx/html;
index index.html index.htm;
# 載入網站設定檔,可以將不同網站的設定檔各自放在一個 .conf 檔案中
include /etc/nginx/conf.d/*.conf;
}
- access_log:所有的請求 (request) 都可以被記錄下來,格式透過
log_format
設定。記錄所有請求有個小缺點,這個動作會拖慢 Nginx 的回應速度,如果你很要求效能,並且對於存取記錄不是很在意的話,建議可以把它停用,保留error_log
即可。 - sendfile:如果你需要傳送大型檔案,可以將此指令開啟,預設是 off。除非你的Nginx 中所有網站都會傳送檔案,否則一般會把這個指令放在某個
server
或location
中。 - tcp_nopush:只有在 sendfile 啟用時才啟用這個指令。
- tcp_nodelay:在 Keep-alive 或 SSL 連線時,啟用這個指令。
- keepalive_timeout:指定 Keep-alive 的連線時間,到指定時間時如果 Client 都沒有任何動作才斷開連線。設定時間為 0 可用來停止 keep-alive。預設值是 75 秒。可使用區塊:
http
,server
,location
。 - limit_req_zone:不一定要使用。如果要避免被 DDOS 功擊,可以限制連線的請求頻率,依自己的需求設定。
- gzip:現在的瀏覽器都支援 gzip 壓縮資料的功能,啟用它可以降低資料的傳輸量。
- error_page:可以讓你指定某一連線狀態要顯示的頁面,例如
404
是找不到檔案。第一個參數是狀態碼,可用空白分隔指定多個狀態碼,最後一個參數是要顯示的網頁的名稱。 - root:文件根目錄。在
http
區塊中,表示為 Nginx 的文件根目錄。在server
中則是該網站的根目錄。 - index:定義索引檔 (或稱為首頁) 的檔案名稱,可用空白分隔指定多個。
server 區塊
你可以將 server
區塊直接寫在 http
區塊裡,但比較好的做法是一個網站 (即虛擬主機) 單獨寫成一個設定檔,放在 /etc/nginx/conf.d/
目錄裡,然後 include 進來。在 nginx.conf
中已經預設有一個 server
區塊,也就是我們看到的預設首頁,暫時保留它,然後來建立一個新的網站:
cd /etc/nginx/conf.d
vim www.myweb.test.conf
# 檔名可以自訂,但是使用網域名稱較容易辨認
# 記得要以 .conf 結尾
# 因為在 http 區塊中有一行 include /etc/nginx/conf.d/*.conf;
# 會載入所有以 .conf 結尾的檔案內容
www.myweb.test.conf
內容如下:
server {
listen 80;
server_name www.myweb.test;
root /usr/share/nginx/html/www.myweb.test;
index index.html index.htm;
}
記得重新載入設定檔:
# 可以先檢查設定檔是否正確
sudo nginx -t
# 重載設定
sudo nginx -s relaod
# 或重新啟動
sudo systemctl restart nginx
這樣就完成一個網站的設定。但是它還沒有網頁,現在來文件根目錄中建立一個首頁:
cd /usr/share/nginx/html/
# 建立網站目錄
mkdir www.myweb.test
cd www.myweb.test/
# 建立首頁
vim index.html
寫一個簡單的 HTML 內容:
<h1>Hello, Nginx :)</h1>
存檔離開。假如你在 2.1 節有照著設定 ip,現在可以在機機打開瀏覽器,輸入網址
http://192.168.8.8/www.myweb.test/
應該可以看到剛剛建立的首頁,這裡的 www.myweb.test
是指目錄名稱,因為 192.168.8.8
的文件根目錄是 /usr/share/nginx/html/
,www.myweb.test
被當成其下的子目錄。
由於我們沒有 DNS 去設定對應的網域名稱,這樣的網址看起來很奇怪,不過我們可以使用本機的 hosts
檔案去做到跟 DNS 一樣的事,電腦上的連線會優先尋找 hosts
,然後才去找 DNS。Mac 請開啟 /etc/hosts
,Windows 請開啟 C:\WINDOWS\system32\drivers\etc\hosts
,在檔案最後面加上
192.168.8.8 www.myweb.test
這樣當你連到 http://www.myweb.test
時,Nginx 會找到 server
區塊中符合 server_name
的虛擬主機,並從它的 root
指令所指的文件根目錄去找網頁。
同一個 ip 可以指定多個域名,只要該 Nginx 的虛擬主機中能找到相對應的域名即可。
現在,你可以在瀏覽器輸入網址
http://www.myweb.test/
應該會看到一樣的內容。
location 區塊
設定請求的 uri,可以巢狀組成,uri 名稱可以使用正規表達式,~*
表示不區分大小寫,~
則區分大小寫。Nginx 會回傳第一個配對到的 uri,直到找不到為止。
你可以使用 location 區塊來建立子路徑:
server {
listen 80;
server_name www.myweb.test;
root /usr/share/nginx/html/www.myweb.test;
index index.html index.htm;
location = / {
# 完全匹配,當瀏覽器輸入 http://www.myweb.test 時觸發
}
location / {
# 瀏覽器輸入 "http://www.myweb.test/index.html" 時觸發
# 和前一個設定動作相同,但前一個的解析速度較快
}
location /docs/ {
# 瀏覽器輸入 "http://www.myweb.test/docs/abc.html" 時觸發
}
location ^~ /images/ {
# 瀏覽器輸入 "http://www.myweb.test/images/nginx-logo.png" 時觸發
# 這會匹配以 /images/ 啟始的路徑,假設在 /docs/images/ 也有一個相同的目錄名稱(沒有額外指定 location),這個 location 並不會匹配,而是 /docs/ 那個設定值會匹配,除非你另外明確指定一個 /docs/images/ 的 location 設定值
}
location ~* \.(gif|jpg|png)$ {
# ~* 不區分大小寫, ~ 則區分大小寫
# 正規表達式,倒斜線用來跳脫 . 點,括號加上 | 分隔表示「或」,$ 表示結尾,也就是後面不再有任何字元
# 所以會匹配任何以 .gif .jpg .png 結尾的 URI,且不分大小寫
}
}
4.4.3 PHP 的設定
Nginx 本身並不能處理 PHP 檔案,但是它能以反向代理的方式,將任何 PHP 檔案丟給 PHP-FPM 伺服器處理,PHP-FPM 會執行解析 PHP 及執行運算,然後將結果回傳給 Nginx ,Nginx 在這裡就是個代理人負責傳遞而已。
我們將任何以 .php
結尾的檔案以 fastcgi 的方式傳給 PHP-FPM:
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
# 如果你的 php-fpm 使用 socket 請使用下面這行
# fastcgi_pass unix:/var/run/php-fpm.sock;
# 如果使用 http 則使用下面這行,2 選 1
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
# 加入 fastcgi_param 檔案的設定
include fastcgi_params;
# 假如 fastcgi_params 中沒有以下內容,請額外加入
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location = /404.html {
}
error_page 404 /404.html;
error_page 指令
第一個參數是狀態碼,第二個參數是 uri。當發生指定的錯誤狀態碼時,導向指定的 uri。我們同時用 location 設定了 /404.html
這個 uri ,它會回傳網站下的 404.html
這個檔案,請自行建立這個網頁。
我們用 if 判斷,當任何不符合 .php
結尾的檔案傳入時,丟出 404 這個狀態碼。
fastcgi_pass 指令
指定要轉送的 FastCGI 伺服器的位址,這裡指 PHP-FPM。你可以使用 TCP 或 Socket 兩種傳輸方式。
fastcgi_index 指令
當使用者瀏覽的網址結尾為 /
時,自動顯示 index.php
頁面。
如果同時存在 index.html 及 index.php 會顯示哪一個?
請看 server
區塊底下的 index
設定:
# 如果你設定成這樣,會優先顯示 index.html 或 index.htm,如果找不到 才依 fastcgi_index 去找 index.php
index index.html index.htm;
# 但是如果你設定成這樣,則會優先找 index.php,找不到才去找 index.html
index index.php index.html index.htm;
fastcgi_param 指令
指定要傳遞給 FastCGI 伺服器的參數。SCRIPT_FILENAME
是指 PHP 檔案名稱會被當成參數。$document_root
變數是指網站的文件根目錄。$fastcgi_script_name
變數是指請求的 uri ,如果 uri 以 /
結尾,則依 fastcgi_index
所指定的檔案來顯示。
root 指令
這個指令可以用來指定 uri 對應的實際目錄,在你未使用 root
指令時,location
所指定的 uri 會自動對應相對目錄,當你使用 root
指令時,路徑結果會變成 root + location
,說明如下:
# 假設網站文件目錄為 /usr/share/nginx/html/www.myweb.test
root /usr/share/nginx/html/www.myweb.test;
# 網址為 http://www.myweb.test/
# 範例 1
location /docs/ {
# 文件路徑 /usr/share/nginx/html/www.myweb.test/docs
# 網址 http://www.myweb.test/docs/
}
# 範例 2
location /docs/ {
root /usr/share/nginx/html/www.myweb.test;
# 文件路徑為 root + location
# 所以是 /usr/share/nginx/html/www.myweb.test/docs
# 實際上結果和「範例 1」是一樣的設定
# 因為此 location 中的 root 和 server 中的 root 一樣
}
# 範例 3
location /docs/ {
root /usr/share/nginx/html/www.myweb.test/data/guide;
# 文件路徑為 root + location
# 所以是 /usr/share/nginx/html/www.myweb.test/data/guide/docs
# 網址 http://www.myweb.test/docs/
# 網址沒變,但已經指向不同目錄了
}
要實際體驗的話,請這麼做
cd /usr/share/nginx/html/www.myweb.test
mkdir docs
vim docs/index.html
# 輸入 <h1>docs</h1> 存檔離開 :wq
mkdir -p data/guide/docs
vim data/guide/docs/index.html
# 輸入 <h1>data/guide/docs</h1> 存檔離開 :wq
然後將前面範例 1, 2, 3 的內容分別註解掉來看看結果,去掉說明文字如下:
location /docs/ {
# root /usr/share/nginx/html/www.myweb.test;
# root /usr/share/nginx/html/www.myweb.test/data/guide;
}
先把 2 個 root 都註解掉,存檔離開,nginx -s reload
重載設定後,瀏覽器看 http://www.myweb.test/docs/
觀察結果;接著把第一行 root 註解拿掉,存檔離開,重載設定,看結果,應該是一樣的;最後,把第一行 root 註解,第二行 root 註解拿掉,存檔離開,重載設定,看結果,應該顯示「data/guide/docs」。
4.4.4 HTTPS 的設定
SSL (Secure Sockets Layer) 是一種安全協定,目的是為了提供網路通訊時的資料安全。HTTPS 是以 SSL 進行加密,但是最後發佈的 3.0 版被發現不安全後,目前已被棄用,而以更新的 TSL (Transport Layer Security) 的 SSL 版本來取代,但我們仍會以 SSL 來稱呼這個協定。
自從 Google 大力推行 HTTPS,及 Let's Encrypt 的免費 SSL 憑證,現在已經有越來越多的網址開始使用 HTTPS。Google 的瀏覽器 Chrome 將從 68 版開始對 HTTP 網站發出「不安全」的警告,現在使用 HTTPS 已經是必要之事了。
要顯示加密網頁 HTTPS,只要將原本的 server
區塊複製一份,然後稍做修改即可。在使用 HTTPS 必須先有 SSL 憑證,但是我們不用真的去申請一個憑證,可以自行產生一個測試用的憑證。
mkdir -p /etc/pki/nginx/private
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/pki/nginx/private/server.key -out /etc/pki/nginx/server.crt
# 它會要你填一些資料,由於是我們自行產生的憑證根本沒人承認,隨便填即可:
Country Name (2 letter code) [XX]:TW
State or Province Name (full name) []:Taiwan
Locality Name (eg, city) [Default City]:Taichung
Organization Name (eg, company) [Default Company Ltd]:Tonycube.com
Organizational Unit Name (eg, section) []:dev
Common Name (eg, your name or your server's hostname) []:tony
Email Address []:
完成後就會看到我們的憑證 server.crt
及金鑰 private/server.key
。
接著就是把這個憑證拿來用:
# HTTP
server {
listen 80;
server_name www.myweb.test;
root /usr/share/nginx/html/www.myweb.test;
index index.php index.html index.htm;
location / {
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
error_page 404 /404.html;
location = /404.html {
}
}
# HTTPS
server {
listen 443 ssl http2; # <--- 這裡不一樣
server_name www.myweb.test;
root /usr/share/nginx/html/www.myweb.test;
index index.php index.html index.htm;
# 多了 ssl 的設定
ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/private/server.key";
ssl on;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
# 禁用 SSLv3
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# 以下和 HTTP 的內容相同
location / {
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
error_page 404 /404.html;
location = /404.html {
}
}
存檔後記得讓 Nginx 重載設定,就能連線到
https://www.myweb.test/docs/
如果瀏覽器跟你說憑證不安全,那是因為我們的憑證是自行產生的,沒有經過安全機構認證的原因。
在實際運作的網站可以購買有公信力的機購所發的憑證,或是使用免費的 Let's Encrypt 所提供的憑證。
4.4.5 Nginx 設定檔產生器
如果你覺得手動設定 Nginx 的設定檔很麻煩,可以使用 Nginx 設定檔產生器 https://nginxconfig.io/ ,勾一勾就能產生設定檔了。
4.4.6 針對 Laravel 的設定
Laravel 是 PHP 的網頁框架,它有提供一份 Nginx 伺服器的參考設定,我把它依前面的範例稍微修改如下:
server {
listen 80;
server_name www.myweb.test;
root /usr/share/nginx/html/www.myweb.test/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# 它這裡使用 socket,請調整成你自己的 php-fpm 所在目錄
fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
index.php
是 Laravel 框架運行的進入點,所以將 404 錯誤送給它,由它來處理。
4.4.7 403 Forbidden 的處理
在使用 Nginx 時,可能會發生在瀏覽器上出現 403 Forbidden
(被禁止)的情況,可以檢查幾個項目:
- 確認 xxx.conf 虛擬主機設定檔中有指定
index
- 確認文件根目錄之下的所有檔案的擁有者為 nginx
cd /usr/share/nginx
sudo chown -R nginx:nginx *
- 確認目錄權限為 755 ,檔案為 644
sudo chmod 755 [directory name]
sudo chmod 644 *
- 確認是否被 SElinux 擋掉
# 檢查是否已啟用
getenforce
# Enforcing <-- 表示啟用
# 暫時取消強迫性
sudo setenforce 0
# 再查看一次
getenforce
# Permissive <-- 仍是啟用,但不強迫執行
# 這時再重整網頁看看是否不會顯示 Forbidden,如果是,表示受 SELinux 影響
# 將它重新啟用
sudo setenforce 1
getenforce # 查看是否已啟用
# Enforcing <-- 確定啟用強迫執行
#########################################
# 現在確認是受 SELinux 影響,接下來調整檔案屬性
# 先來看看目前的檔案被標示成什麼類型
ls -Z /usr/share/nginx # 之下有一個 html 目錄
# 結果可能會像這樣
drwxr-xr-x nginx nginx user_u:object_r:default_t html
# 這個預設的 default_t 標籤,就是讓瀏覽器無法存取 Forbidden 的原因
# 執行以下指令
# 這樣其目錄下的所有檔案就會加上 httpd_sys_content_t 標籤
sudo chcon -Rv --type=httpd_sys_content_t /usr/share/nginx
# 看看是不是加上該標籤了
ls -Z /usr/share/nginx
# 再試試看瀏覽器能不能顯示網頁
# 或是執行以下指令
sudo restorecon -R -v /usr/share/nginx/html
# 這樣會讓這網站根目錄下的檔案全都恢復成 SELinux 的安全屬性
# 這通常發生在,從別的地方移動目錄到網站的文件目錄之下,它的 SELinux 屬性是前一個目錄時的設定
#########################################
# 針對 Laravel 把 storage 目錄設定可讀寫
sudo chcon -R -t httpd_sys_rw_content_t storage
# Laravel 執行以下指令來清暫快取
php artisan clear-compiled
php artisan cache:clear
php artisan view:clear
# 針對 sqlite 資料庫檔案設定可讀寫
sudo chcon -R -t httpd_sys_rw_content_t database/mydb.db
# 重要!不建議將 SELinux 永久性關閉
SELinux 資料可以參考 https://wiki.centos.org/zh-tw/HowTos/SELinux
繼續閱讀:動手學 VPS 架站:使用 CentOS 7 + Nginx + PHP-FPM + MariaDB (9) - 資料庫
由 Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀
我要留言
留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。