Nginx是一個免費開源且穩定高效的Web伺服器程式,擁有反向代理以及負載平衡的功能,經常作為最前端的伺服器。
安裝Nginx伺服器
要在Ubuntu Server上安裝Nginx伺服器,可以直接在終端機中執行以下指令:
Nginx預設會啟用HTTP,TCP連接埠為HTTP預設的80,可以使用以下指令來查看Nginx是否有確實安裝成功。
ss
指令可以顯示出Socket相關的資訊。-l
參數可以只顯示正在監聽中的連線。若使用ss
指令時都沒給任何參數的話,會忽略掉監聽中的連線。-t
參數可以只顯示TCP連線。-n
參數可以讓連接埠數字直接被輸出,而不是用一個名稱代替。-p
參數可以顯示佔用連線的行程。
如上圖,如果有看到TCP有在監聽連接埠80,就表示Nginx安裝成功了!
注意,Nginx之所以會去監聽任意網路介面上的80連接埠,是因為預設的設定檔(/etc/nginx/sites-available/default
)中,有設一個會去監聽任意網路介面,且連接埠為80的「虛擬主機」(Virtual Host)。有關於「虛擬主機」,會在本篇文章之後做介紹。
設定Nginx
Nginx的主設定檔為/etc/nginx/nginx.conf
。它預設的模樣長成這樣:
Nginx的設定有分層級(區塊),在最頂層比較重要的設定項目(命令)有:
worker_processes
:設定Nginx要有幾個「工人」(Worker)。一個工人就是一個行程,會透過非同步I/O和執行緒池(Thread Pool)處理多個連線。工人的數量通常會設定為處理器(邏輯處理器)的數量,可以小於但不建議超過。預設的auto
即表示要使用處理器(邏輯處理器)的數量作為工人的數量。user
:設定執行Nginx工人行程的擁有者和群組。不過如果只有指定使用者,則表示要使用與使用者相同名稱的群組。預設的擁有者和群組為www-data
。worker_rlimit_nofile
:設定每個Nginx工人行程最多同時可以開啟幾個檔案描述符(File Descriptor,簡稱FD)。關於這個設定的用途很模糊,我們可用ulimit -Sn
指令和ulimit -Hn
指令來查看每個行程的FD數量的最大限制,前者為超過會有警告的軟限制,後者為超過就不給用的硬限制,設定worker_rlimit_nofile
應該是要讓工人行程可以正常地突破軟限制。worker_rlimit_nofile
常被設為每個工人行程的最大連線數(即worker_connections
,等等會介紹)再乘上2
,或者直接設為65535
。include
:在這個命令的位置插入指定路徑(可用星號*
表示任意檔名)的Nginx設定檔。include
命令可以被用在任意區塊中。load_module
:動態載入模組。/etc/nginx/modules-available
目錄中存放著能被載入的模組的設定檔,如果要載入模組,可以會將其設定檔的符號連結(Symbolic Link)放置在/etc/nginx/modules-enabled
目錄中。至於靜態模組,可以使用nginx -V 2>&1 | tr -- - '\n' | grep module
指令來查看。沒有用到的模組就別動態載入了,會多吃記憶體。error_log
:設定錯誤日誌的存放位置,若第一個參數傳入檔案路徑,則可以存到檔案系統中。第二個參數可以控制錯誤日誌的訊息最低等級(重要度),預設是error
,可用的等級依照重要度排序有info
、notice
、warn
、error
、crit
、alert
、emerg
。若設定為error
,表示要存放error
、crit
、alert
和emerg
等級的訊息。如果沒有在Nginx的設定頂層中設定error_log
,Ubuntu Server預設的Nginx錯誤日誌會存放在/var/log/nginx/error.log
。error_log
命令還可以(不限於)被用在http
、server
、location
區塊中。events
:events
區塊用來設定處理連線的方式。http
:http
區塊用來設定HTTP,包括虛擬主機。http
區塊最多只能有一個。
如果用了error_log
命令把日誌檔案的路徑設在Linux發行版的預設日誌目錄下的nginx
目錄之外或者檔名不是以.log
結尾,要記得去調整logrotate
的設定檔(一般會在/etc/logrotate.d/nginx
,如果沒有就自行建立吧),讓它可以正確地去對Nginx日誌檔案做「輪替(rotate)」的動作,避免日誌檔案把伺服器的硬碟空間塞滿了!
events
區塊比較重要的設定項目(命令)有:
worker_connections
:設定每個工人行程的最大連線數,包含對客戶端和對被反向代理的伺服器等所建立的連線。這個值如果設定太大,雖然可以讓伺服器同時處理多筆連線,但會佔用很多的記憶體,而且每筆連線的處理速度也會變得很慢,甚至會導致開啟檔案描述符的額度不足;如果設得太小,不用說,會讓很多連線無法被建立。預設的最大連線數為768
,如果不設定會是512
。如果您的伺服器在上線之後觀察CPU和記憶體的使用率,覺得還有餘力可以再處理更多連線的話,可以逐步調高worker_connections
。multi_accept
:是否讓Nginx工人行程一次接受所有連線。不設定的話是off
,Nginx會一個一個選擇它認為比較好(比較閒?)的工人行程去接受連線;設成on
的話,Nginx會直接選一個工人行程去接受所有連線。至於哪種比較快似乎不一定,請自行嘗試吧!
http
區塊中已有預設許多幾乎必定得啟用的功能(如讓最後一個封包不管大小就直接送出的tcp_nodelay
、讓封包可以在資料集滿之後再送出的tcp_nopush
、不進行中介直接發送檔案的sendfile
、壓縮回應加快傳輸速度的gzip
、HTTP加密連線用的ssl_protocols
和ssl_prefer_server_ciphers
),其它比較重要的設定項目(命令)有:
log_format
:建立日誌的格式。第一個參數設定要建立的格式名稱,第二個參數和第三個參數用來設定格式(有點複雜就不在這裡提了)。access_log
:設定存取日誌的存放位置,若第一個參數傳入檔案路徑,則可以存到檔案系統中;若傳入off
則該區塊下不去記錄存取動作。第二個參數傳入要套用的日誌格式名稱(用上面提到的log_format
命令來建立),預設是Nginx內建的combined
格式。access_log
支援Gzip,但我們已有logrotate
,所以不需要去管它。access_log
命令還可以被用在server
、location
區塊中。server_tokens
:設定是否要將Nginx的版本資訊代入回應的HTTP標頭中(於Server
欄位),以及是否要在顯示Nginx預設的錯誤頁面時也顯示Nginx的版本資訊。不設定的話是on
。建議設成off
。keepalive_timeout
:設定Keep-alive連線的逾時時間(秒)。預設值為65
;不設定的話是75
。還可以被用在server
、location
區塊中。types_hash_max_size
:設定一個Hash Table的最大空間(位元組)。預設是2048
,不設定的話是1024
。還可以被用在server
、location
區塊中。types_hash_bucket_size
:設定一組Hash資料的最大空間(位元組)。不設定的話是64
。還可以被用在server
、location
區塊中。server_names_hash_max_size
:設定用來存放伺服器名稱的Hash Table的最大空間(位元組)。不設定的話是512
。server_names_hash_bucket_size
:設定一個伺服器名稱的最大空間(位元組)。不設定的話會是CPU的快取塊(Cache Line)大小,通常是32
或是64
。這個設定值直接影響著能使用的伺服器名稱的最大長度。default_type
:設定預設的Mime類型。預設是application/octet-stream
,不設定的話是text/plain
。還可以被用在server
、location
區塊中。client_header_buffer_size
、large_client_header_buffers
:用來設定客戶端請求(request)中標頭的緩衝空間(位元組),如果小的不夠就建大的,大的不夠的話就會回傳HTTP 400狀態(Bad Request)。不設定的話是1024
和8192
。它們還可以被用在server
區塊中。client_body_buffer_size
:設定客戶端請求中主體的緩衝空間(位元組),如果不夠大的話就會暫存成檔案。不設定的話,32位元的系統是8192
;64位元的系統是16384
。還可以被用在server
、location
區塊中。如果記憶體夠大,這個值可以設高一點。client_max_body_size
:設定客戶端請求中主體的最大大小(位元組),不設定的話是1m
。還可以被用在server
、location
區塊中。若是需要讓客戶端上傳檔案或是傳送可能會有比較多內容的訊息(例如長篇部落格文章),就會很需要調整這個設定。types
:設定檔案副檔名對應的Mime類型。/etc/nginx/mime.types
檔案是預設的對應表。types
命令還可以被用在server
、location
區塊中。open_file_cache
:是否快取檔案的FD、大小、修改時間、有無存在等。不設定的話是off
(不快取)。第一個參數要設定max
,也就是最大快取數量,並非愈大愈好,愈大會需要愈多的記憶體和搜尋時間,可以先從max=1000
開始嘗試;第二個參數要設定inactive
,也就是快取的持續時間(秒),不設定的話是inactive=60
。open_file_cache
命令還可以被用在server
、location
區塊中。open_file_cache_min_uses
:設定快取的檔案在inactive
的時間內,最少要被存取幾次才不會被踢出快取。不設定的話是1
。還可以被用在server
、location
區塊中。open_file_cache_errors
:是否快取讀取失敗的檔案。不設定的話是off
(不快取)。一般情況下這個功能不需要被開啟。還可以被用在server
、location
區塊中。open_file_cache_valid
:間隔多久(秒)後去驗證快取的資料之正確性。不設定的話是60
。還可以被用在server
、location
區塊中。charset
:設定純文字格式的編碼方式。參考這篇文章。server
:定義虛擬主機。可以使用多個server
區塊來定義多個虛擬主機。upstream
:定義上游伺服器的群組,用來做負載平衡。可以使用多個upstream
區塊來定義多個上游伺服器群組。
位元組若有k
、m
單位表示要再乘上210、220;秒若有m
、h
、d
單位表示要再乘上60、3600、86400。
關於types
,還可以再加上以下內容到對應表中(加入之前先檢查是否已存在):
application/font-woff2 woff2; application/wasm wasm;
關於Gzip還有以下幾個比較重要的設定項目(命令):
gzip_comp_level
:設定Gzip的壓縮程度,數值範圍是1~9,數值愈高壓縮程度愈高(需要更多的CPU運算和時間)。不設定的話是1
。設定1
就有明顯的效益,但如果想要減少更多的網路流量,可以再設高一點(建議6
以下)。gzip_comp_level
命令還可以被用在server
、location
區塊中。gzip_min_length
:設定需要被Gzip壓縮的最小資料量(位元組)。不設定的話是20
。建議可以設高一點(例如100
),增加Nginx對小資料的反應速度,並減少CPU使用率。gzip_min_length
命令還可以被用在server
、location
區塊中。gzip_proxied
:設定從被反向代理的伺服器拿到的資料要如何進行Gzip壓縮。不設定的話是off
(不壓縮)。其它的設定值根據判斷HTTP回應的標頭來決定要不要壓縮,有expired
、no-cache
、no-store
、private
、no_last_modified
、no_etag
、auth
,可用空格隔開多個。如果都要壓縮,就設成any
。gzip_proxied
命令還可以被用在server
、location
區塊中。gzip_vary
:設定是否在有使用Gzip壓縮的回應中添加Vary: Accept-Encoding
標頭。不設定的話是off
(不添加)。如果有使用CDN的話,這個項目一定要改成on
,為了要讓CDN根據客戶端請求中的Accept-Encoding
來決定要回傳的回應是要有用Gzip壓縮過的版本的還是沒有壓縮過的版本。gzip_vary
命令還可以被用在server
、location
區塊中。gzip_types
:設定可以被Gzip壓縮的MIME類型,用空格隔開多個,可以只用一個星號*
來表示的任意的MIME類型。不設定的話是text/html
。gzip_types
命令還可以被用在server
、location
區塊中。gzip_disable
:利用正規表示式排除不使用Gzip的User Agent
,可用空格隔開多個。填寫msie6
可用來排除不能正常支援Gzip壓縮的IE(IE4、IE5和部份IE6)。不建議使用這個設定項目來排除IE,因為IE很少人用了,沒有必要為了它在每次處理HTTP請求時都去耗費CPU資源來判斷User-Agent
標頭。gzip_disable
命令還可以被用在server
、location
區塊中。
關於gzip_types
,可以參考以下設定:
gzip_types text/plain text/css text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml application/json application/msgpack application/javascript application/wasm image/svg+xml image/bmp image/x-ms-bmp image/x-icon font/opentype font/ttf
由於虛擬主機的設定比較繁瑣,通常寫在其它的設定檔中,再於主設定檔中使用include
命令來引入。冗長但可以由各個虛擬主機共同使用的設定(例如用map
命令建立的Hash Table),可以被放在/etc/nginx/conf.d
目錄中;而由虛擬主機各別的設定,可以被放在/etc/nginx/sites-available
目錄中,而要上線的虛擬主機,可以將其設定檔的符號連結放置在/etc/nginx/sites-enabled
目錄中。一般來說,被放在/etc/nginx/sites-enabled
和/etc/nginx/sites-available
目錄中的設定檔,都是直接由server {
開頭(但不限於只使用一個server
區塊),而設定檔的檔名,則會和該虛擬主機直接相關,方便辨認。
舉例來說,若我們想要讓我們的網站1架設在1.magiclen.org
,網站2架設在2.magiclen.org
網域上,那麼在/etc/nginx/sites-available
目錄中,就可以建立出兩個設定檔,檔名分別為1.magiclen.org.conf
和2.magiclen.org.conf
。若要讓這兩個網站上線,就用sudo ln -s /etc/nginx/sites-available/*.magiclen.org.conf /etc/nginx/sites-enabled
指令來建立這兩個設定檔的符號連結到/etc/nginx/sites-enabled
目錄中。
設定虛擬主機
在一台實體主機上安裝Nginx伺服器,可以藉由使用多個server
區塊,將不同的網站掛上不同的網域、IP或是連接埠。簡單來說,一台實體主機可以建立出多個網站,就像是弄出了多台虛擬主機一樣,當然這邊完全沒有任何像KVM、Docker等虛擬化技術(Virtualization)存在,應該是因為外行人通常會認為一台主機只能使用一組網域和IP來架設一個網站,才如此命名(似乎是從Apache伺服器開始的)。
也就是說,server
區塊的根本,是Nginx要依照從客戶端來的HTTP請求,來區分要使用哪個server
區塊,進而提供不同的網站內容。區分的方式有兩步驟,第一步是看IP(網路介面的IP)和連接埠,第二步是看HTTP標頭中的Host
欄位。
用listen
命令設定要監聽的網路介面和連接埠
listen
命令直接影響著Nginx要去監聽哪幾個網路介面以及哪幾個連接埠。不設定的話是*:80
,也就是監聽所有網路介面(IPv4)的80連接埠。
由於listen
命令的設定方式挺多樣的,以下就直接以常用的例子來說明。
server {
listen *:8000;
...
}
以上設定,表示要監聽所有網路介面(IPv4)的8000連接埠。
server {
listen 8000;
...
}
以上設定,也是表示要監聽所有網路介面(IPv4)的8000連接埠。
server {
listen 127.0.0.1:8000;
listen 10.0.2.15:80;
...
}
以上設定,表示要監聽127.0.0.1
(loopback)網路介面的8000連接埠和10.0.2.15
網路介面的80連接埠。
server {
listen [::]:8000;
...
}
以上設定,表示要監聽所有網路介面(IPv6)的8000連接埠。
server {
listen *:443 ssl http2;
...
}
以上設定,表示要監聽所有網路介面(IPv4)的443連接埠,並且使用SSL模式(HTTPS)及啟用HTTP/2協定(可向下相容HTTP/1.x)。
SSL模式還需要搭配ssl_certificate
、ssl_certificate_key
和ssl_dhparam
命令,用於http
區塊或是server
區塊。可以參考這篇文章來申請免費的SSL證書。
用server_name
命令設定HTTP請求中的Host
標頭欄位需要擁有的值
當客戶端透過網域名稱和HTTP/HTTPS協定來發送請求時,客戶端軟體會先查找網域名稱對應的IP位址,接著將網域名稱代進請求中的Host
標頭欄位中,再將請求發送到該IP位址對應的主機。Nginx的server_name
命令就是要用來判斷客戶端請求中的Host
標頭欄位是否能夠與之匹配,可用空格隔開多個。server_name
的匹配是不分大小寫的(case-insensitive)。
如果不設定server_name
,就會是空字串""
,表示不去匹配Host
標頭欄位。server_name
命令可以使用星號*
來進行wildcard的匹配,也可以以~
字元為開頭,使用正規表示式進行匹配,例如~^www\d+\.magiclen\.org$
,就可以去匹配www1.magiclen.org
和www10.magiclen.org
。
在IP與連接埠都相同的情況下,server
區塊的選擇是根據server_name
的設定方式來決定的,順序如下:
Host
標頭欄位值完全與server_name
一致。- 最長且星號
*
在開頭的wildcard。(例如*.magiclen.org
和*.org
,都與blog.magiclen.org
匹配,就會選擇*.magiclen.org
) - 最長且星號
*
在結尾的wildcard。(例如blog.magiclen.*
和blog.*
,都與blog.magiclen.org
匹配,就會選擇blog.magiclen.*
) - 第一個匹配到的正規表示式。
若server_name
均無法完成匹配,就會選擇該IP與連接埠的第一個server
區塊。如果想要使用其它的server
區塊作為server_name
均無匹配到時的預設server
區塊,那就要在listen
命令加上default_server
參數,如下:
server {
listen 80;
server_name 1.magiclen.org;
...
}
server {
listen 80 default_server;
server_name 2.magiclen.org;
...
}
以上設定,請求中的Host
標頭欄位只能是1.magiclen.org
,才可以選擇到第一個server
區塊。
其它比較重要的設定項目
root
:設定網站根目錄。不設定的話,Ubuntu Server預設會使用/var/www/html
。root
命令還可以被用在http
、location
區塊中。index
:當請求目標是目錄時,要對應到該目錄底下的哪個檔案。可用空格隔開多個檔案,檔案尋找順序是從左到右。若以/
開頭,表示要從網站根目錄尋找檔案,通常會用在最後一個,例如index.html /no-index.html
。不設定的話是index.html
。要注意的是,這個命令是透過內部轉址來動作(會被location
區塊處理),並不是直接去存取檔案。index
命令還可以被用在http
、location
區塊中。error_page
:設定當要回傳300以上的HTTP狀態碼時,要如何進行轉址(例如轉到特定的錯誤網頁)。前面幾個參數是要設定的狀態碼,最後一個參數是轉址的路徑(以/
開頭的話是內部轉址,以其它協定為開頭的話就是外部轉址)。倒數第二個參數也可以加上=狀態碼
,來改變回應的狀態碼。error_page
命令還可以被用在http
、location
區塊中。location
:location
區塊可以針對不同的請求的路徑做設定,所以一個server
區塊中可以有多個location
區塊。
location
區塊
location
命令的設定方式挺多樣的,以下就直接以常用的例子來說明。
location = / {
...
}
以上的location
區塊可以處理/
請求。=
就是讓location
命令的第三個參數要與請求的路徑「完全匹配」的意思。
location / {
...
}
以上的location
區塊可以處理以/
為開頭的請求。
location /foo {
...
}
以上的location
區塊可以處理以/foo
為開頭的請求,區分大小寫(case-sensitive),包括/foobar
和/foo/bar
。不過若是在macOS或是Windwos等路徑不區分大小寫的作業系統中架設Nginx伺服器,這個方式就不分大小寫。
location ^~ /foo {
...
}
以上的location
區塊可以處理以/foo
為開頭的請求,區分大小寫(case-sensitive),包括/foobar
和/foo/bar
。不過若是在macOS或是Windwos等路徑不區分大小寫的作業系統中架設Nginx伺服器,這個方式就不分大小寫。這功能看起來和不寫^~
時一樣,但優先順序有差(稍候會提到)。
location ~ /foo {
...
}
以上的location
區塊可以處理請求路徑中含有/foo
的請求,區分大小寫,包括/foo
、/foobar
、/foo/bar
和/bar/foo
。
location ~ \.(jpg|gif)$ {
...
}
以上的location
區塊可以處理以.jpg
或是.gif
為結尾的請求,區分大小寫,包括/foo.jpg
和/bar.gif
。~
就是讓location
命令的第三個參數變成區分大小寫的正規表示式。
location ~* \.(jpg|gif)$ {
...
}
以上的location
區塊可以處理以.jpg
或是.gif
為結尾的請求,不區分大小寫,包括/foo.JPG
和/bar.GIF
。~*
就是讓location
命令的第三個參數變成不區分大小寫的正規表示式。
請求路徑在匹配時,最先會去找有用=
修飾的location
區塊,如果有找到能匹配的location
區塊,就會直接去用它。接下來會從有^~
修飾和完全無修飾的location
區塊來進行路徑前綴的匹配,並找出能夠匹配到最長前綴的那個location
區塊,看它是否有用^~
修飾,有的話就會去用它,沒有的話就暫時當這個location
區塊不存在,繼續從有用~
修飾的location
區塊進行正規表示式的匹配。若都找不到能匹配的用~
修飾的location
區塊,就繼續再從有用~*
修飾的location
區塊進行正規表示式的匹配。而使用正規表示式來匹配時,最先被匹配成功的location
區塊會被使用。
如果正規表示式都匹配失敗,就會使用先前找出的那個能夠匹配到最長前綴但完全無修飾的location
區塊。
所以在撰寫location
命令的時候,路徑的匹配順序是很重要的,它會一定程度地影響網站的反應速度。
再來要介紹幾個在location
區塊中比較重要的命令:
auth_basic
:設定HTTP基本認證(帳密認證)。參考這篇文章。deny
:禁止指定的客戶端IP或客戶端IP所在CIDR進行訪問。設定成all
表示要全部禁止,通常會於deny all;
前再搭配allow
來用。deny
命令還可以被用在http
、server
區塊中。allow
:允與指定的客戶端IP或客戶端IP所在CIDR進行訪問,以免其因之後的deny
命令而被禁止存取。還可以被用在http
、server
區塊中。expires
:設定HTTP回應標頭中,Cache-Control
欄位的max-age
參數,即資源的快取時間(秒),可以讓客戶端不會為了還在快取中的資源發送請求。或者可以設定為負數,表示要在HTTP回應標頭中明確加上Cache-Control: no-cache
,讓客戶端至少在使用快取的資源時會發送請求來驗證。不設定的話是off
(不使用Cache-Control
標頭欄位)。expires
命令還可以被用在server
區塊中。try_files
:try_files
命令的前幾個參數要傳入檔案或目錄路徑,最後一個參數是轉址的路徑。Nginx會從前幾個參數由左到右去判斷檔案或目錄存不存在,如果存在就回應該檔案或目錄;如果都不存在,就會使用最後一個參數來轉址。最後一個參數也可以是=狀態碼
,表示要用指定的狀態碼來回應,而不去轉址。try_files
命令還可以被用在server
區塊中。
if
、return
等流程控制的命令和變數(variable)也常被用於location
區塊甚至是server
區塊中。if
命令用來進行條件判斷,來決定要伺服器在處理請求時走哪個流程;return
命令的用法可以參考這篇文章。
最簡單的if
判斷方式如下:
if ($var) {
...
}
以上設定,若$var
不為空字串""
或是"0"
,if
區塊內的命令就會被執行。
if
命令也可以進行如location
命令的完全匹配和正規表示式匹配的動作,如下:
if ($var = 123) {
...
}
if ($var ~ ^(1|2)3$) {
...
}
if ($var ~* abc) {
...
}
加上!
表示要將判斷結果反向。如下:
if ($var != 123) {
...
}
if ($var !~ ^(1|2)3$) {
...
}
if ($var !~* abc) {
...
}
if
命令還支援如Linux系統的test
命令那樣的判斷,能判斷檔案是否存在(-f
)、目錄是否存在(-d
)等。如下:
if (-f $var) {
...
}
if (!-d $var) {
...
}
至於Nginx的變數,常用的有以下幾個:
$uri
:請求的路徑,已被正規化,僅包含路徑。$request_uri
:原始的請求路徑,包含查詢(query)。$scheme
:請求使用的通訊協定,http
或是https
。$remote_addr
:客戶端的IP位址。$server_addr
:請求是從哪個IP位址的網路介面進來的。$request_method
:請求方法。例如GET
、POST
。$http_xxx
:請求標頭中的xxx
欄位。例如User-Agent
標頭欄位就是$http_user_agent
。$cookie_xxx
:請求所帶的Cookie中的xxx
欄位。$server_name
:server_name
命令的第一個參數值。$host
:若$http_host
存在,則$host
為$http_host
的去除連接埠外加轉小寫的結果。若$http_host
不存在,則$host
的值為$server_name
。
location
區塊中也可以再加入location
區塊。我們甚至還可以直接對location
區塊「取名字」。例如:
location @home {
expires 1d;
rewrite ^ /index.html;
}
如此一來,在填寫轉址的路徑時,可以直接使用這個名稱,例如:
server {
error_page 404 @home;
location @home {
rewrite ^ /index.html;
}
}
正規表示式的利用
當server_name
命令是使用正規表示式時,可以將正規表示式的群組內容保留給之後的命令來使用,例如:
server {
server_name ~^(www\.)?(?<domain>.+)$;
location / {
root /sites/$domain;
}
}
location
命令也是一樣,例如:
location ~ ^/api/v(?<number>\d+) {
root /api/$number;
}
反向代理(Reverse Proxy)
在location
區塊中,利用proxy_pass
命令,可以讓Nginx把請求透過URI,交給其它的伺服器來處理。用法如下:
server {
error_page 404 = @fallback;
location @fallback {
proxy_pass http://127.0.0.1:3000;
}
}
以上設定,當Nginx伺服器遇到無對應檔案的請求時,就會把請求交給127.0.0.1:3000
這個另外的伺服器來處理。這邊error_page
命令之所以會多用一個=
,是為了要讓另外的伺服器可改變原先的HTTP狀態。
另外,為了讓被代理的伺服器工作正常,我們也會需要再傳遞請求的時候,去修改請求的標頭欄位,以保留客戶端的IP位址以及Host
欄位等。可以直接引用/etc/nginx/proxy_params
設定檔來完成這件事。如下:
server {
error_page 404 = @fallback;
location @fallback {
proxy_pass http://127.0.0.1:3000;
include proxy_params;
}
}
為了避免被代理的伺服器也在進行HTTP回應時去做Gzip等壓縮的動作,可以考慮在/etc/nginx/proxy_params
設定檔中加上:
proxy_set_header Accept-Encoding "";
一般來說,被代理的伺服器和Nginx伺服器的距離並不會太遠(或許根本就在同一台電腦上),所以Nginx在與被代理的伺服器連線時不會啟動HTTP的Keep-Alive機制,甚至Nginx還會自動在請求的標頭中再添加Connection: close
,來明確關閉Keep-Alive,避免Keep-Alive連線長時間存在而佔用記憶體。如果還是想啟動Keep-Alive機制的話,可以參考下一小節的作法。
而若被代理的伺服器和Nginx伺服器是在同一個Ubuntu Server作業系統(或是同一個Unix-like作業系統)中,我們可以優先考慮使用Unix Domain Socket(簡稱UDS,或稱IPC Socket)來實現不同伺服器行程間的通訊,比起用TCP Socket透過loopback網路介面來連線,有著更低的延遲(latency)並和更高的吞吐量(throughput)。若UDS的檔案路徑是/path/to/socket
,則Nginx的設定方式如下:
server {
error_page 404 = @fallback;
location @fallback {
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
使用UDS時要注意Nginx必須要對UDS的檔案擁有讀取和寫入的權限。
值得注意的是,當location
區塊會將請求「pass」給其它服務來處理時(即不限於使用proxy_pass
命令),且它是以=
進行完全匹配,或是指定路徑前綴來匹配的話,如果匹配樣本最後是以/
來結尾,則等同自動再加上一個完全匹配路徑無/
結尾的location
區塊,用以進行301轉址,將無/
結尾的路徑轉成有/
結尾的路徑。什麼意思呢?例如以下這個設定:
server {
location /foo/ {
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
等同於:
server {
location = /foo {
return 301 /foo/;
}
location /foo/ {
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
不過如果「pass」命令是寫在if
區塊內,就不會自動新增301轉址的location
區塊。
另外,使用了proxy_pass
命令把請求給其它伺服器來處理的location
區塊,若沒有「取名」或是沒有使用正規表示式來匹配路徑的話,則在使用proxy_pass
命令時,可以在URI加上路徑,以改變請求的路徑。例如:
server {
location /foo/ {
proxy_pass http://unix:/path/to/socket:/bar/;
include proxy_params;
}
}
以上設定,可以把路徑為/foo
,或是以/foo/
為開頭的請求,在交給其它伺服器來處理時,將請求路徑的/foo/
部份以/bar/
來取代。注意由於這邊是使用UDS來通訊,在UDS檔案路徑與請求路徑間,需要使用冒號:
隔開。
另外有些特定的伺服器程式可能會需要大量的執行時間,如果要讓該程式也能夠正常把結果回傳給Nginx,就要在Nginx設定檔中使用proxy_read_timeout
和proxy_send_timeout
命令來提高伺服器程式讀取和回應資料的間隔之逾時的時間。proxy_read_timeout
和proxy_send_timeout
命令可以被用在http
、server
和location
區塊中,不設定的話是60s
(60秒)。
反向代理的快取
Nginx在接收被代理的伺服器回應的資料時,可以將其快取成檔案,這樣下次如果又有一樣的請求,Nginx就可以直接從檔案系統中撈出來回應,不必再轉送給被代理的伺服器處理。
在http
區塊中使用proxy_cache_path
命令,可以建立檔案快取存放的目錄,以及用以儲存鍵值(Key)的「Zone」。例如:
http {
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
...
}
以上設定,可以建立出一個名為my_cache
的「Zone」,擁有10MiB的大小的共享記憶體空間,用以儲存被快取的資源的鍵值雜湊值以及請求次數。而被快取的資源則會儲存在檔案系統中的/path/to/cache
目錄底下,另外擁有兩層子目錄。/path/to/cache
目錄不需要事先被建立出來,因為它會在Nginx載入設定檔時被建立,但/path/to
目錄要事先被建立。
levels
最多可以擁有三層,值的格式為x[:y[:z]]
,x
表示第一層子目錄的檔名長度,y
表示第二層子目錄的檔名長度,z
表示第三層子目錄的檔名長度。檔名是從快取的鍵值經過MD5雜湊值的十六進制(HEX)字串中,於尾端開始取固定長度的子字串來使用。舉例來說,若levels
設為1:2
,快取的鍵值經MD5雜湊後的十六進制字串為b7f54b2df7773722d382f4809d65029c
,則這個快取檔案的存放路徑為c/29/b7f54b2df7773722d382f4809d65029c
。
max_size=10g
用來限制/path/to/cache
目錄底下的快取總大小最多只能有10GiB,超過的話就會從最久沒被使用的快取開始刪除。
inactive=60m
用來設定快取在60分鐘內如果都沒被使用,就將其刪除。use_temp_path=off
用來設定要快取的檔案不要事先存到proxy_temp_path
再搬進快取(在無開啟反向代理快取時,被代理的伺服器所回應的資料會先被暫存至預設的或是指定的proxy_temp_path
,在有啟用快取的情況下,實在沒必要再做這個動作)。
在想要啟用反向代理快取的location
區塊,使用proxy_cache
命令,就可以讓Nginx進行反向代理快取了。proxy_cache
命令的參數要傳入一個「Zone」的名稱,例如:
http {
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
error_page 404 = @fallback;
location @fallback {
proxy_cache_revalidate on;
proxy_cache my_cache;
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
}
以上設定另外還啟用了proxy_cache_revalidate
,可以讓Nginx在處理過期快取的時候去嘗試向被代理的伺服器確認快取的有效性,而不是直接讓被代理的伺服器重新處理請求。
利用proxy_cache_use_stale
命令,當被反向代理的伺服器無法正常處理請求(有錯誤或是逾時),或是回應了如500 (Internal Server Error)的錯誤狀態碼時,Nginx還可以把上一次快取到但是過期的結果回應給客戶端。例如:
http {
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
error_page 404 = @fallback;
location @fallback {
proxy_cache_use_stale error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_cache_revalidate on;
proxy_cache my_cache;
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
}
proxy_cache_use_stale
命令也可以在參數加上updating
,讓快取正在被更新時,也依然去使用過期的快取。而若加了updating
,則應該要再啟用proxy_cache_background_update
,讓Nginx可以在背景更新過期的快取。例如:
http {
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
error_page 404 = @fallback;
location @fallback {
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache my_cache;
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
}
另外還有proxy_cache_min_uses
命令可以設定資源至少要被請求幾次才會真的快取到檔案系統中。不設定的話是1
。增加這個值表示要讓更頻繁被存取的資源更能留在快取中。例如:
http {
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
error_page 404 = @fallback;
location @fallback {
proxy_cache_min_uses 3;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache my_cache;
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
}
對於沒有在HTTP標頭內設定快取的回應,我們也可以利用proxy_cache_valid
命令來快取它,不過不是很建議這樣做。proxy_cache_valid
命令的前幾個參數是HTTP狀態碼,最後一個參數是要快取的時間(秒),即表示要對HTTP狀態碼為前幾個參數的回應做快取。如果只有傳入一個時間給proxy_cache_valid
命令,則會套用給200、301和302狀態碼;如果狀態碼是傳入any
,即代表所有狀態碼。例如:
http {
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
error_page 404 = @fallback;
location @fallback {
proxy_cache_valid 200 60m;
proxy_cache_min_uses 3;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache my_cache;
proxy_pass http://unix:/path/to/socket;
include proxy_params;
}
}
}
若想控制啟用快取的時機以及重新整理(refresh)快取的時機,可以利用proxy_cache_bypass
和proxy_no_cache
命令。這兩個命令都可以傳入多個字串,以空格隔開。若傳入proxy_cache_bypass
命令的字串有至少一個不是空字串""
或是"0"
,則Nginx就不會使用快取中的資料來回應,但還是會把新拿到的回應存入快取中;若傳入proxy_no_cache
命令的字串有至少一個不是空字串""
或是"0"
,Nginx還是可以使用快取中的資料來回應,但如果需要去向被代理的伺服器拿新的回應,該回應就不會被存入快取中。
如果要刪除快取,可以直接刪除檔案系統中,存放快取資料的目錄下的檔案以及目錄。舉例來說,如果想刪除所有快取,可以執行以下指令(請小心使用,有誤刪到其它檔案的危險性):
快取用的鍵值可以透過proxy_cache_key
命令來設定,預設類似是:
proxy_cache_key $scheme$proxy_host$request_uri;
如果是用HTTP標頭欄位的訊息來辨識不同的客戶端,就可能要改用如下的設定(以Authentication
標頭來舉例):
proxy_cache_key $scheme$request_uri$http_authentication;
反向代理的負載平衡(Load Balancing)
上面介紹的反向代理,是直接透過URI去連結某個被代理的伺服器。其實我們可以在http
區塊下,透過upstream
命令建立出「被代理的伺服器的群組」,讓Nginx能夠「選擇」要把請求交給這群組中的哪個伺服器處理。Nginx要做的事其實就是負責接收客戶訂單,交給「被代理的伺服器的群組」中的「上游廠商」進行生產,再把成品拿回來出貨給客戶,這也就是要稱這些伺服器為upstream
(上游)的原因了。
Nginx的負載平衡方法(非Nginx Plus)主要有以下幾種:
- 循環(Round-robin):輪詢那些在上游群組中的伺服器,並依照權重來決定一個伺服器要處理幾個請求後才會換下一個。
- 最小連接(Least-connected):選擇目前連接數最少的伺服器來用。如果有多種選擇,就使用上面的循環(Round-robin)方式來從中再挑。
- IP雜湊(IP-hash):根據客戶端的IP來決定要用哪個伺服器,可以保證同一個IP選到同一個伺服器。
如果選到的上游伺服器請求處理失敗,就繼續找可用的下一個伺服器。如果都處理失敗,客戶端就會取得最後一次處理的結果。
以下是一個簡單的負載平衡範例:
http {
upstream myapp1 {
server srv1.example.com;
server srv2.example.com;
server unix:/path/to/socket;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
include proxy_params;
}
}
}
由於以上設定並未在名為myapp1
的upstream
區塊內定義Nginx要用哪種負載平衡方法,因此myapp1
會使用預設的循環(Round-robin)方法。另外,若是原本在proxy_pass
命令有傳入URI路徑的話,改成用upstream
區塊時,依然可以在proxy_pass
命令傳入路徑。
還可以進行如下更進階的設定:
http {
upstream myapp1 {
server srv1.example.com weight=5;
server srv2.example.com max_fails=3 fail_timeout=30;
server unix:/path/to/socket backup;
server srv3.example.com down;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
include proxy_params;
}
}
}
weight
用來設定權重,也就是設定這個伺服器要處理幾次的請求才換下一個伺服器。不設定的話是weight=1
。
max_fails
和fail_timeout
用來設定伺服器在某段時間(秒)內處理請求超過多少次後,就將它暫時禁用某段時間。不設定的話是max_fails=1 fail_timeout=10
。
backup
用來指定作為備援的伺服器,備援伺服器只有在其它非備援的伺服器都被禁用時,才會被使用。down
用來禁用指定的伺服器。
若在upstream
區塊使用了least_conn
命令,則會使用最小連接(Least-connected)作為負載平衡方法。如下:
http {
upstream myapp1 {
least_conn;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
include proxy_params;
}
}
}
若在upstream
區塊使用了ip_hash
命令,則會使用IP雜湊(IP-hash)作為負載平衡方法。如下:
http {
upstream myapp1 {
ip_hash;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
include proxy_params;
}
}
}
在upstream
區塊使用keepalive
命令,可以設定對每個上游伺服器的最大Keep-Alive的連接數。另外還需要使用proxy_http_version
命令將HTTP版本設成1.1
(不設定的話是1.0
),並用proxy_set_header
命令將Connection: close
標頭欄位移除。如下:
http {
upstream myapp1 {
keepalive 32;
server srv1.example.com;
server srv2.example.com;
server srv3.example.com;
}
server {
listen 80;
location / {
proxy_pass http://myapp1;
include proxy_params;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
套用Nginx的新設定
改寫Nginx設定檔並存檔後,新設定並不會立即套用給正在運行中的Nginx伺服器。
要讓Nginx伺服器重新讀取設定檔,最好先用以下指令檢查設定檔是否無誤,避免Nginx伺服器因為設定檔錯誤而中斷服務。
接著再執行以下指令來重新載入Nginx的設定檔:
網站根目錄的權限處理
Ubuntu Server的Nginx伺服器預設會使用/var/www/html
作為網站根目錄,而這個/var/www/html
目錄預設的擁有者和群組都是root
,權限為0755
(drwxr-xr-x
)。
其實我們可以將網站根目錄的擁有者和群組設為Nginx工人行程用的擁有者和群組,甚至替該目錄加上SGID,讓這個目錄中底下被建立出來的檔案和目錄的群組,都與網站根目錄的群組一樣。若Nginx工人行程的擁有者和群組都是www-data
,則可以使用如下的指令來修改網站根目錄的權限。
sudo chown www-data:www-data /var/www/html
sudo chmod 2775 /var/www/html
然後把我們常用的Linux一般使用者也加進Nginx工人行程用的群組中,指令如下:
sudo usermod -a -G www-data $USER
如此一來要維護網站根目錄下的檔案就會變得很方便了!