NGINX 完全手冊

因爲傳統 Web 服務器無法處理超過 1 萬個併發請求,俄羅斯一位名叫 Igor Sysoev 的年輕開發人員很沮喪。這就是著名的 C10k 問題 。沮喪之餘,他於 2002 年開始研發新的 Web 服務器。

NGINX 於 2004 年基於 2-clause BSD 許可條款首次向公衆發佈。根據 2021 年 3 月 Web 服務器調查,NGINX 擁有 35.3% 的市場份額,大約有 4.196 億個站點在使用它。

由於有了 NGINXConfig 這樣的的工具以及 DigitalOcean 等互聯網上大量線程的配置文件,人們傾向於做大量的複製粘貼,卻並不理解這些配置的含義。

相信我,搞懂它並不難......

我並不是說複製代碼不好,但在千萬不要在不理解的情況下複製代碼。

此外,NGINX 是一種軟件,應該根據要提供服務的應用程序的要求和主機上的可用資源進行精確配置。

這就是爲什麼你應該理解並修改你正在複製的內容,而不是盲目地複製——這就是本手冊存在的意義。

通讀整本書後,你應該能夠:

先決條件

目錄

項目代碼

你可以在這個倉庫中找到示例項目的代碼。留一個 ⭐ 讓我保持動力。

master 分支包含本書中使用的所有代碼。

NGINX 簡介

NGINX 是一種高性能網絡服務器,旨在滿足現代網絡日益增長的需求。它專注於高性能、高併發和低資源使用。儘管它通常被稱爲 Web 服務器,但 NGINX 的核心是一個反向代理服務器。

不過,NGINX 並不是市場上唯一的 Web 服務器。它最大的競爭對手之一是 Apache HTTP Server (httpd),它於 1995 年首次發佈。儘管 Apache HTTP Server 更靈活,但服務器管理員通常更喜歡 NGINX,有兩個主要原因:

我不會深入討論整個 Apache 與 NGINX 的爭論。但是,如果你想詳細瞭解它們之間的差異,請參閱 Justin Ellingwood 的這篇出色的文章。

事實上,爲了解釋 NGINX 的請求處理技術,我想在這裏引用 Justin 的文章中的兩段:

Nginx 在 Apache 之後出現,更針對的解決大規模站點將面臨的併發問題。利用這些知識,Nginx 從頭開始設計爲使用異步、非阻塞、事件驅動的連接處理算法。

Nginx 產生工作進程,每個進程可以處理數千個連接。工作進程通過實現一個快速輪詢機制來實現這一點,該機制不斷地檢查和處理事件,將實際工作與連接解耦,這允許每個工作進程僅在觸發新事件時才關注連接。

如果這看起來有點難以理解,請不要擔心。現在對內部工作原理有一個基本的瞭解就足夠了。

NGINX 在靜態內容交付方面更快,同時資源相對較少,因爲它沒有嵌入動態編程語言處理器。當對靜態內容的請求到來時,NGINX 只響應文件而不運行任何額外的進程。

這並不意味着 NGINX 不能處理需要動態編程語言處理器的請求。接收到需要動態處理的請求時,NGINX 只是將任務委託給單獨的進程,例如 PHP-FPM、[Node.js](https:/ /nodejs.org/) 或 Python。一旦該進程完成其工作,NGINX 會將響應反向代理回客戶端。

NGINX 配置文件語法參考了腳本語言語法,因此很容易配置,可以生成緊湊、易於維護的配置文件。

如何安裝 NGINX

在基於 Linux 的系統上安裝 NGINX 非常簡單。可以使用運行 Ubuntu 的虛擬專用服務器作爲你的訓練場,也可以使用 Vagrant 在本地系統上配置虛擬機。

大多數情況下,配置本地虛擬機就足夠了,這也是我將在本文中使用的方式。

如何配置本地虛擬機

Vagrant 是 Hashicorp 的一個開源工具,它可以使用簡單的配置文件配置虛擬機。

要使用這種方式,需要 VirtualBox 和 Vagrant,所以提前先安裝它們。如果你需要對該主題進行一些瞭解,此教程可能會有所幫助。

在系統中的某處創建一個具有適當名稱的工作目錄。我的是 ~/vagrant/nginx-handbook 目錄。

在工作目錄中創建一個名爲 Vagrantfile 的文件並輸入以下內容:

Vagrant.configure("2") do |config|

    config.vm.hostname = "nginx-handbook-box"
  
    config.vm.box = "ubuntu/focal64"
  
    config.vm.define "nginx-handbook-box"
  
    config.vm.network "private_network", ip: "192.168.20.20"
  
    config.vm.provider "virtualbox" do |vb|
      vb.cpus = 1
      vb.memory = "1024"
      vb.name = "nginx-handbook"
    end
  
  end

這個 Vagrantfile 就是我之前講的配置文件。它包含虛擬機名稱、CPU 數量、RAM 大小、IP 地址等信息。

要使用此配置啓動虛擬機,請在工作目錄中打開終端並執行以下命令:

vagrant up

# Bringing machine 'nginx-handbook-box' up with 'virtualbox' provider...
# ==> nginx-handbook-box: Importing base box 'ubuntu/focal64'...
# ==> nginx-handbook-box: Matching MAC address for NAT networking...
# ==> nginx-handbook-box: Checking if box 'ubuntu/focal64' version '20210415.0.0' is up to date...
# ==> nginx-handbook-box: Setting the name of the VM: nginx-handbook
# ==> nginx-handbook-box: Clearing any previously set network interfaces...
# ==> nginx-handbook-box: Preparing network interfaces based on configuration...
#     nginx-handbook-box: Adapter 1: nat
#     nginx-handbook-box: Adapter 2: hostonly
# ==> nginx-handbook-box: Forwarding ports...
#     nginx-handbook-box: 22 (guest) => 2222 (host) (adapter 1)
# ==> nginx-handbook-box: Running 'pre-boot' VM customizations...
# ==> nginx-handbook-box: Booting VM...
# ==> nginx-handbook-box: Waiting for machine to boot. This may take a few minutes...
#     nginx-handbook-box: SSH address: 127.0.0.1:2222
#     nginx-handbook-box: SSH username: vagrant
#     nginx-handbook-box: SSH auth method: private key
#     nginx-handbook-box: Warning: Remote connection disconnect. Retrying...
#     nginx-handbook-box: Warning: Connection reset. Retrying...
#     nginx-handbook-box: 
#     nginx-handbook-box: Vagrant insecure key detected. Vagrant will automatically replace
#     nginx-handbook-box: this with a newly generated keypair for better security.
#     nginx-handbook-box: 
#     nginx-handbook-box: Inserting generated public key within guest...
#     nginx-handbook-box: Removing insecure key from the guest if it's present...
#     nginx-handbook-box: Key inserted! Disconnecting and reconnecting using new SSH key...
# ==> nginx-handbook-box: Machine booted and ready!
# ==> nginx-handbook-box: Checking for guest additions in VM...
# ==> nginx-handbook-box: Setting hostname...
# ==> nginx-handbook-box: Configuring and enabling network interfaces...
# ==> nginx-handbook-box: Mounting shared folders...
#     nginx-handbook-box: /vagrant => /home/fhsinchy/vagrant/nginx-handbook

vagrant status

# Current machine states:

# nginx-handbook-box           running (virtualbox)

vagrant up 命令的輸出在你的系統上可能會有所不同,但只要 vagrant status 表示機器正在運行,就可以開始了。

鑑於虛擬機現在正在運行,應該能夠通過 SSH 進入它。爲此,請執行以下命令:

vagrant ssh nginx-handbook-box

# Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-72-generic x86_64)
# vagrant@nginx-handbook-box:~$

如果一切正常,應該登錄到虛擬機上,這可以通過終端上的 vagrant@nginx-handbook-box 行看出。

此虛擬機可在本地計算機上的 http://192.168.20.20 上訪問。你甚至可以通過在你的 hosts 文件中添加一個條目來爲虛擬機分配一個像 http://nginx-handbook.test 這樣的自定義域:

# on mac and linux terminal
sudo nano /etc/hosts

# on windows command prompt as administrator
notepad c:\windows\system32\drivers\etc\hosts

現在在文件末尾追加以下行:

nginx-handbook.test    192.168.20.20

現在你應該可以在瀏覽器中通過 http://nginx-handbook.test URI 訪問虛擬機。

可以通過在工作目錄中執行以下命令來停止或銷燬虛擬機:

# to stop the virtual machine
vagrant halt

# to destroy the virtual machine
vagrant destroy

如果你想了解更多 Vagrant 命令,這個備忘單可能會派上用場。

現在系統上有一個正常運行的 Ubuntu 虛擬機,接下來要做的就是安裝 NGINX。

如何配置虛擬專用服務器

對於本演示,我將使用 Vultr 作爲我的供應商,但你可以使用 DigitalOcean 或你喜歡的任何供應商。

假設你已經擁有提供商的帳戶,請登錄該帳戶並部署新服務器:

在 DigitalOcean 上,它通常被稱爲 droplet。在下一個屏幕上,選擇靠近你的節點。我住在孟加拉國,所以我選擇新加坡:

在下一步中,必須選擇操作系統和服務器配置。選擇 Ubuntu 20.04 和儘可能小的服務器配置:

儘管生產服務器往往比這更大更強大,但對於本文來說,一臺小型服務器就足夠了。

最後,在最後一步,將類似 nginx-handbook-demo-server 之類的東西作爲服務器主機和標籤。如果必要,甚至可以將它們留空。

一旦對自己的選擇感到滿意,請繼續並按下 Deploy Now 按鈕。

部署過程可能需要一些時間才能完成,一旦完成,將在儀表板上看到新創建的服務器:

還要注意 Status – 它應該是 Running 而不是 PreparingStopped。要連接到服務器,還需要用戶名和密碼。

進入服務器的概覽頁面,應該會看到服務器的 IP 地址、用戶名和密碼:

使用 SSH 登錄服務器的命令如下:

ssh <username>@<ip address>

所以就我的服務器而言,它將是:

ssh root@45.77.251.108

# Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
# Warning: Permanently added '45.77.251.108' (ECDSA) to the list of known hosts.

# root@45.77.251.108's password: 
# Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-65-generic x86_64)

# root@localhost:~#

系統會詢問是否要繼續連接到此服務器。輸入 “yes”,然後系統會要求輸入密碼。從服務器概覽頁面複製密碼並將其粘貼到終端中。

如果做的一切順利,會成功登錄到你的服務器——終端上會看到 root@localhost 行。這裏的 “localhost” 是服務器主機名,你的顯示可能會有所不同。

可以通過其 IP 地址直接訪問該服務器。或者,如果擁有任何自定義域名,也可以使用它。

在整篇文章中,將看到我將測試域名添加到我的操作系統的 hosts 文件中。如果是真實服務器,則必須使用 DNS 提供商配置這些服務器。

請記住,只要此服務器正在使用,就會被收費。雖然收費應該很少,但我還是要警告你。可以通過點擊服務器概覽頁面上的垃圾桶圖標隨時銷燬服務器:

如果擁有自定義域名,則可以爲此服務器分配一個子域名。現在已進入服務器,剩下的就是安裝 NGINX。

如何在配置的服務器或虛擬機上安裝 NGINX

假設已登錄到服務器或虛擬機,第一件應該做的事就是執行更新。執行以下命令來執行此操作:

sudo apt update && sudo apt upgrade -y

更新後,通過執行以下命令安裝 NGINX:

sudo apt install nginx -y

安裝完成後,NGINX 應自動註冊爲 systemd 服務並運行。要檢查,請執行以下命令:

sudo systemctl status nginx

# ● nginx.service - A high performance web server and a reverse proxy server
#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
#      Active: active (running)

如果狀態顯示 running,那麼就可以開始了。否則,可以通過執行以下命令來啓動服務:

sudo systemctl start nginx

最後,爲了直觀地驗證一切是否正常,請使用喜歡的瀏覽器訪問服務器 / 虛擬機,應該會看到 NGINX 的默認歡迎頁面:

NGINX 通常安裝在 /etc/nginx 目錄中,我們接下來的大部分工作都將在這裏完成。

恭喜!已經在服務器 / 虛擬機上啓動並運行了 NGINX。現在是時候先進入 NGINX 了。

NGINX 的配置文件介紹

作爲 Web 服務器,NGINX 的工作是爲客戶端提供靜態或動態內容。但是如何提供這些內容通常由配置文件控制。

NGINX 的配置文件以.conf 擴展名結尾,通常位於/etc/nginx/ 目錄中。讓我們先通過 cd 進入這個目錄並獲取所有文件的列表:

cd /etc/nginx

ls -lh

# drwxr-xr-x 2 root root 4.0K Apr 21  2020 conf.d
# -rw-r--r-- 1 root root 1.1K Feb  4  2019 fastcgi.conf
# -rw-r--r-- 1 root root 1007 Feb  4  2019 fastcgi_params
# -rw-r--r-- 1 root root 2.8K Feb  4  2019 koi-utf
# -rw-r--r-- 1 root root 2.2K Feb  4  2019 koi-win
# -rw-r--r-- 1 root root 3.9K Feb  4  2019 mime.types
# drwxr-xr-x 2 root root 4.0K Apr 21  2020 modules-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 modules-enabled
# -rw-r--r-- 1 root root 1.5K Feb  4  2019 nginx.conf
# -rw-r--r-- 1 root root  180 Feb  4  2019 proxy_params
# -rw-r--r-- 1 root root  636 Feb  4  2019 scgi_params
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-enabled
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 snippets
# -rw-r--r-- 1 root root  664 Feb  4  2019 uwsgi_params
# -rw-r--r-- 1 root root 3.0K Feb  4  2019 win-utf

在這些文件中,應該有一個名爲 nginx.conf 的文件。這是 NGINX 的主要配置文件。可以使用 cat 程序查看此文件的內容:

cat nginx.conf# user www-data;# worker_processes auto;# pid /run/nginx.pid;# include /etc/nginx/modules-enabled/*.conf;# events {#     worker_connections 768;#     # multi_accept on;# }# http {#     ###     # Basic Settings#     ###     sendfile on;#     tcp_nopush on;#     tcp_nodelay on;#     keepalive_timeout 65;#     types_hash_max_size 2048;#     # server_tokens off;#     # server_names_hash_bucket_size 64;#     # server_name_in_redirect off;#     include /etc/nginx/mime.types;#     default_type application/octet-stream;#     ###     # SSL Settings#     ###     ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE#     ssl_prefer_server_ciphers on;#     ###     # Logging Settings#     ###     access_log /var/log/nginx/access.log;#     error_log /var/log/nginx/error.log;#     ###     # Gzip Settings#     ###     gzip on;#     # gzip_vary on;#     # gzip_proxied any;#     # gzip_comp_level 6;#     # gzip_buffers 16 8k;#     # gzip_http_version 1.1;#     # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;#     ###     # Virtual Host Configs#     ###     include /etc/nginx/conf.d/*.conf;#     include /etc/nginx/sites-enabled/*;# }# #mail {# #    # See sample authentication script at:# #    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript# # # #    # auth_http localhost/auth.php;# #    # pop3_capabilities "TOP" "USER";# #    # imap_capabilities "IMAP4rev1" "UIDPLUS";# # # #    server {# #        listen     localhost:110;# #        protocol   pop3;# #        proxy      on;# #    }# # # #    server {# #        listen     localhost:143;# #        protocol   imap;# #        proxy      on;# #    }# #}

哇!東西很多。試圖在當前狀態下理解這個文件將是一場噩夢。因此,讓我們備份文件並創建一個新的空文件:

# renames the filesudo mv nginx.conf nginx.conf.backup# creates a new filesudo touch nginx.conf

強烈不鼓勵你編輯原始的 nginx.conf 文件,除非你完全知道你在做什麼。出於學習目的,可以重命名以備份它,但是稍後,我將向你展示在真實場景中應該如何配置服務器。

如何配置基本 Web 服務器

在本書的這一部分中,將最終通過從頭開始配置一個基本的靜態 Web 服務器來動手實踐。本節的目的是向你介紹 NGINX 配置文件的語法和基本概念。

如何編寫你的第一個配置文件

首先使用 nano 文本編輯器打開新創建的 nginx.conf 文件:

sudo nano /etc/nginx/nginx.conf

在整本書中,我將使用 nano 作爲我的文本編輯器。如果你願意,可以使用更流行的編輯器,但在現實生活場景中,你最有可能在服務器上使用 nano 或 [vim](https: //www.vim.org/) ,而不是其他任何東西。因此,請以本書爲契機,提高你的 Nano 技能。此外,官方備忘單隨時供你參考。

打開文件後,將其內容更新爲:

events {}http {    server {        listen 80;        server_name nginx-handbook.test;        return 200 "Bonjour, mon ami!\n";    }}

如果你有構建 REST API 的經驗,那麼你可能會從 return 200 "Bonjour, mon ami!\n"; 行已經看出來服務器已配置狀態代碼 200 的消息 “Bonjour, mon ami !”。

如果你目前一頭霧水,請不要擔心,我將逐行解釋這個文件,但首先讓我們看看這個配置的實際效果。

如何校驗並重新加載配置文件

編寫新配置文件或更新舊配置文件後,首先要做的是檢查文件是否存在語法錯誤。nginx 二進制文件包含一個選項 -t 來做到這一點。

sudo nginx -t# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok# nginx: configuration file /etc/nginx/nginx.conf test is successful

如果有任何語法錯誤,此命令將會展示錯誤信息以及出錯的行號。

雖然配置文件沒問題,但 NGINX 配置並不會立即生效。NGINX 的工作方式是它讀取一次配置文件並在此基礎上持續工作。

如果更新配置文件,則必須明確指示 NGINX 重新加載配置文件。有兩種方法可以做到這一點。

-s 選項用於向 NGINX 發送各種信號。可用的信號是 stopquitreloadreopen。在我剛剛提到的兩種方式中,我更喜歡第二種方式,因爲它打字較少。

一旦通過執行 nginx -s reload 命令重新加載了配置文件,就可以通過向服務器發送一個簡單的 get 請求來查看它的運行情況:

curl -i http://nginx-handbook.test# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 10:03:33 GMT# Content-Type: text/plain# Content-Length: 18# Connection: keep-alive# Bonjour, mon ami!

服務器響應狀態代碼 200 以及預期的消息。恭喜你走到這一步!現在是解釋的時候了。

如何理解 NGINX 中的指令和上下文

在這裏編寫的幾行代碼雖然看似簡單,但卻介紹了 NGINX 配置文件的兩個最重要的術語。它們是指令上下文

從技術上講,NGINX 配置文件中的所有內容都是 ** 指令 **。指令有兩種類型:

一個簡單的指令由指令名稱和空格分隔的參數組成,如 listenreturn 等。簡單指令以分號結束。

塊指令類似於簡單指令,不同之處在於它們不是以分號結尾,而是以一對花括號 {} 括起的附加指令。

能夠在其中包含其他指令的塊指令稱爲上下文,即 eventshttp 等。NGINX 中有四個核心上下文:

可以將 NGINX 中的上下文視爲其他編程語言中的作用域。它們之間也有一種繼承關係。可以在官方 NGINX 文檔中找到指令的字母索引。

我已經提到在一個配置文件中可以有多個 server 上下文。但是當請求到達服務器時,NGINX 如何知道哪一個上下文中應該處理請求?

listen 指令是在配置中識別正確的 server 上下文的方法之一。考慮以下場景:

http {    server {        listen 80;        server_name nginx-handbook.test;        return 200 "hello from port 80!\n";    }    server {        listen 8080;        server_name nginx-handbook.test;        return 200 "hello from port 8080!\n";    }}

現在,如果向 http://nginx-handbook.test:80 發送請求,那麼將收到 “hello from port 80!” 響應。如果向 http://nginx-handbook.test:8080 發送請求,將收到 “hello from port 8080!” 響應:

curl nginx-handbook.test:80# hello from port 80!curl nginx-handbook.test:8080# hello from port 8080!

這兩個服務器塊就像兩個人拿着電話聽筒,等待指定電話號碼呼入時響應。它們的電話號碼由 listen 指令指示。

除了listen 指令,還有 server_name 指令。考慮以下虛構圖書館管理應用程序的場景:

http {    server {        listen 80;        server_name library.test;        return 200 "your local library!\n";    }    server {        listen 80;        server_name librarian.library.test;        return 200 "welcome dear librarian!\n";    }}

這是虛擬主機思想的一個基本例子。正在同一臺服務器中以不同的服務器名稱運行兩個單獨的應用程序。

如果向 http://library.test 發送請求,那麼將獲得 “your local library!” 響應。如果向 http://librarian.library.test 發送請求,將收到 “welcome dear librarian!” 響應。

curl http://library.test

# your local library!

curl http://librarian.library.test

# welcome dear librarian!

爲了讓這個演示在你的系統上運行,你必須更新你的 hosts 文件使其包含這兩個域名:

192.168.20.20   library.test
192.168.20.20   librarian.library.test

最後,return 指令負責向用戶返回一個有效的響應。該指令包含兩個參數:狀態代碼和要返回的字符串消息。

如何使用 NGINX 提供靜態內容

現在已經很好地瞭解瞭如何爲 NGINX 編寫基本配置文件,讓我們升級配置以提供靜態文件而不是純文本響應。

爲了提供靜態內容,首先必須將它們存儲在服務器上的某個位置。如果你使用 ls 列出服務器根目錄下的文件和目錄,會在那裏找到一個名爲 /srv 的目錄:

ls -lh /

# lrwxrwxrwx   1 root    root       7 Apr 16 02:10 bin -> usr/bin
# drwxr-xr-x   3 root    root    4.0K Apr 16 02:13 boot
# drwxr-xr-x  16 root    root    3.8K Apr 21 09:23 dev
# drwxr-xr-x  92 root    root    4.0K Apr 21 09:24 etc
# drwxr-xr-x   4 root    root    4.0K Apr 21 08:04 home
# lrwxrwxrwx   1 root    root       7 Apr 16 02:10 lib -> usr/lib
# lrwxrwxrwx   1 root    root       9 Apr 16 02:10 lib32 -> usr/lib32
# lrwxrwxrwx   1 root    root       9 Apr 16 02:10 lib64 -> usr/lib64
# lrwxrwxrwx   1 root    root      10 Apr 16 02:10 libx32 -> usr/libx32
# drwx------   2 root    root     16K Apr 16 02:15 lost+found
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 media
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 mnt
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 opt
# dr-xr-xr-x 152 root    root       0 Apr 21 09:23 proc
# drwx------   5 root    root    4.0K Apr 21 09:59 root
# drwxr-xr-x  26 root    root     820 Apr 21 09:47 run
# lrwxrwxrwx   1 root    root       8 Apr 16 02:10 sbin -> usr/sbin
# drwxr-xr-x   6 root    root    4.0K Apr 16 02:14 snap
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 srv
# dr-xr-xr-x  13 root    root       0 Apr 21 09:23 sys
# drwxrwxrwt  11 root    root    4.0K Apr 21 09:24 tmp
# drwxr-xr-x  15 root    root    4.0K Apr 16 02:12 usr
# drwxr-xr-x   1 vagrant vagrant   38 Apr 21 09:23 vagrant
# drwxr-xr-x  14 root    root    4.0K Apr 21 08:34 var

這個/srv 目錄旨在包含由該系統提供的特定於站點的數據。現在 cd 進入這個目錄並克隆本書附帶的代碼庫:

cd /srvsudo git clone https://github.com/fhsinchy/nginx-handbook-projects.git

nginx-handbook-projects 目錄中應該有一個名爲 static-demo 的目錄,總共包含四個文件:

ls -lh /srv/nginx-handbook-projects/static-demo# -rw-r--r-- 1 root root 960 Apr 21 11:27 about.html# -rw-r--r-- 1 root root 960 Apr 21 11:27 index.html# -rw-r--r-- 1 root root 46K Apr 21 11:27 mini.min.css# -rw-r--r-- 1 root root 19K Apr 21 11:27 the-nginx-handbook.jpg

現在有了要提供的靜態內容,請按如下方式更新配置:

events {}http {    server {        listen 80;        server_name nginx-handbook.test;        root /srv/nginx-handbook-projects/static-demo;    }}

代碼幾乎相同,除了 return 指令現在已被替換爲 root 指令。該指令用於聲明站點的根目錄。

通過編寫 root /srv/nginx-handbook-projects/static-demo 告訴 NGINX 在有任何請求時在這個服務器的 /srv/nginx-handbook-projects/static-demo 目錄中查找要提供的文件。由於 NGINX 是一個 Web 服務器,它非常聰明,可以默認爲 index.html 文件提供服務。

讓我們看看這是否有效。測試並重新加載更新的配置文件並訪問服務器,應該會看到一個不完整的 HTML 站點:

儘管 NGINX 已正確提供 index.html 文件,但從三個導航鏈接的外觀來看,似乎 CSS 代碼不起作用。

你可能認爲 CSS 文件中有問題。但實際上,問題出在配置文件中。

NGINX 中的靜態文件類型處理

要調試現在面臨的問題,向服務器發送 CSS 文件的請求:

curl -I http://nginx-handbook/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 12:17:16 GMT
# Content-Type: text/plain
# Content-Length: 46887
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-b727"
# Accept-Ranges: bytes

注意 Content-Type 並查看展示 text/plain 而不是 text/css。這意味着 NGINX 將此文件作爲純文本而不是樣式表提供。

儘管 NGINX 足夠聰明,默認情況下可以找到 index.html 文件,但在解釋文件類型時它非常愚蠢。要解決此問題,再次更新配置:

events {

}

http {

    types {
        text/html html;
        text/css css;
    }

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;
    }
}

我們對代碼所做的唯一更改是嵌套在 http 塊中的新 types 上下文。可能已經從名稱中猜到,此上下文用於配置文件類型。

通過在此上下文中編寫 text/html html,你告訴 NGINX 將任何以 html 擴展名結尾的文件解析爲 text/html

可能認爲配置 CSS 文件類型就足夠了,因爲 HTML 已經被很好地解析——但並非如此。

如果在配置中引入 types 上下文,NGINX 會變得更加笨拙,只會解析你配置的文件。因此,如果只在此上下文中定義了 text/css css,那麼 NGINX 將開始將 HTML 文件解析爲純文本。

驗證並重新加載新更新的配置文件並再次訪問服務器。再次發送對 CSS 文件的請求,這次文件應該被解析爲 text/css 文件了:

curl -I http://nginx-handbook.test/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 12:29:35 GMT
# Content-Type: text/css
# Content-Length: 46887
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-b727"
# Accept-Ranges: bytes

訪問服務器進行視覺驗收,這次站點應該看起來更好:

如果已正確更新並重新加載配置文件,但仍看到舊站點,請執行強制刷新。

如何引入部分配置文件

types 上下文中映射文件類型可能適用於小型項目,但對於較大的項目,它可能很麻煩且容易出錯。

NGINX 爲這個問題提供瞭解決方案。如果你再次列出 /etc/nginx 目錄中的文件,會看到一個名爲 mime.types 的文件。

ls -lh /etc/nginx

# drwxr-xr-x 2 root root 4.0K Apr 21  2020 conf.d
# -rw-r--r-- 1 root root 1.1K Feb  4  2019 fastcgi.conf
# -rw-r--r-- 1 root root 1007 Feb  4  2019 fastcgi_params
# -rw-r--r-- 1 root root 2.8K Feb  4  2019 koi-utf
# -rw-r--r-- 1 root root 2.2K Feb  4  2019 koi-win
# -rw-r--r-- 1 root root 3.9K Feb  4  2019 mime.types
# drwxr-xr-x 2 root root 4.0K Apr 21  2020 modules-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 modules-enabled
# -rw-r--r-- 1 root root 1.5K Feb  4  2019 nginx.conf
# -rw-r--r-- 1 root root  180 Feb  4  2019 proxy_params
# -rw-r--r-- 1 root root  636 Feb  4  2019 scgi_params
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-enabled
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 snippets
# -rw-r--r-- 1 root root  664 Feb  4  2019 uwsgi_params
# -rw-r--r-- 1 root root 3.0K Feb  4  2019 win-utf

我們來看看這個文件的內容:

cat /etc/mime.types

# types {
#     text/html                             html htm shtml;
#     text/css                              css;
#     text/xml                              xml;
#     image/gif                             gif;
#     image/jpeg                            jpeg jpg;
#     application/javascript                js;
#     application/atom+xml                  atom;
#     application/rss+xml                   rss;

#     text/mathml                           mml;
#     text/plain                            txt;
#     text/vnd.sun.j2me.app-descriptor      jad;
#     text/vnd.wap.wml                      wml;
#     text/x-component                      htc;

#     image/png                             png;
#     image/tiff                            tif tiff;
#     image/vnd.wap.wbmp                    wbmp;
#     image/x-icon                          ico;
#     image/x-jng                           jng;
#     image/x-ms-bmp                        bmp;
#     image/svg+xml                         svg svgz;
#     image/webp                            webp;

#     application/font-woff                 woff;
#     application/java-archive              jar war ear;
#     application/json                      json;
#     application/mac-binhex40              hqx;
#     application/msword                    doc;
#     application/pdf                       pdf;
#     application/postscript                ps eps ai;
#     application/rtf                       rtf;
#     application/vnd.apple.mpegurl         m3u8;
#     application/vnd.ms-excel              xls;
#     application/vnd.ms-fontobject         eot;
#     application/vnd.ms-powerpoint         ppt;
#     application/vnd.wap.wmlc              wmlc;
#     application/vnd.google-earth.kml+xml  kml;
#     application/vnd.google-earth.kmz      kmz;
#     application/x-7z-compressed           7z;
#     application/x-cocoa                   cco;
#     application/x-java-archive-diff       jardiff;
#     application/x-java-jnlp-file          jnlp;
#     application/x-makeself                run;
#     application/x-perl                    pl pm;
#     application/x-pilot                   prc pdb;
#     application/x-rar-compressed          rar;
#     application/x-redhat-package-manager  rpm;
#     application/x-sea                     sea;
#     application/x-shockwave-flash         swf;
#     application/x-stuffit                 sit;
#     application/x-tcl                     tcl tk;
#     application/x-x509-ca-cert            der pem crt;
#     application/x-xpinstall               xpi;
#     application/xhtml+xml                 xhtml;
#     application/xspf+xml                  xspf;
#     application/zip                       zip;

#     application/octet-stream              bin exe dll;
#     application/octet-stream              deb;
#     application/octet-stream              dmg;
#     application/octet-stream              iso img;
#     application/octet-stream              msi msp msm;

#     application/vnd.openxmlformats-officedocument.wordprocessingml.document    docx;
#     application/vnd.openxmlformats-officedocument.spreadsheetml.sheet          xlsx;
#     application/vnd.openxmlformats-officedocument.presentationml.presentation  pptx;

#     audio/midi                            mid midi kar;
#     audio/mpeg                            mp3;
#     audio/ogg                             ogg;
#     audio/x-m4a                           m4a;
#     audio/x-realaudio                     ra;

#     video/3gpp                            3gpp 3gp;
#     video/mp2t                            ts;
#     video/mp4                             mp4;
#     video/mpeg                            mpeg mpg;
#     video/quicktime                       mov;
#     video/webm                            webm;
#     video/x-flv                           flv;
#     video/x-m4v                           m4v;
#     video/x-mng                           mng;
#     video/x-ms-asf                        asx asf;
#     video/x-ms-wmv                        wmv;
#     video/x-msvideo                       avi;
# }

該文件包含一長串文件類型及其擴展名。要在配置文件中使用此文件,請將配置更新爲如下所示:

events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;
    }

}

舊的 types 上下文現在已替換爲新的 include 指令。顧名思義,該指令允許包含來自其他配置文件的內容。

驗證並重新加載配置文件並再次發送對 mini.min.css 文件的請求:

curl -I http://nginx-handbook.test/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 12:29:35 GMT
# Content-Type: text/css
# Content-Length: 46887
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-b727"
# Accept-Ranges: bytes

在下面關於如何理解主配置文件的部分中,我將演示如何使用 include 來模塊化虛擬服務器配置。

NGINX 中的動態路由

在上一節中編寫的配置是一個非常簡單的靜態內容服務器配置。它所做的只是匹配來自與客戶端訪問的 URI 相對應的站點根目錄的文件並進行響應。

因此,如果客戶端請求根目錄中存在文件,例如 index.htmlabout.htmlmini.min.css,NGINX 將返回該文件。但是,如果訪問諸如 http://nginx-handbook.test/nothing 之類的路由,它將以默認的 404 頁面響應:

在本書的這一部分,將瞭解 location 上下文、變量、重定向、重寫和 try_files 指令。本節中不會有新項目,但在此處學習的概念將在接下來的部分中必不可少。

此外,本節中的配置更改非常頻繁,因此請不要忘記在每次更新後驗證並重新加載配置文件。

位置匹配

我們將在本節中討論的第一個概念是 location  上下文。更新配置如下:

events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location /agatha {
            return 200 "Miss Marple.\nHercule Poirot.\n";
        }
    }
}

我們已經用新的 location 上下文替換了 root 指令。這個上下文通常嵌套在 server 塊中。server 上下文中可以有多個 location 上下文。

如果向 http://nginx-handbook.test/agatha 發送請求,將獲得 200 響應代碼和 Agatha Christie 創建的字符列表。

curl -i http://nginx-handbook.test/agatha

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 15:59:07 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive

# Miss Marple.
# Hercule Poirot.

現在,如果向 http://nginx-handbook.test/agatha-christie 發送請求,將得到相同的響應:

curl -i http://nginx-handbook.test/agatha-christie# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 15:59:07 GMT# Content-Type: text/plain# Content-Length: 29# Connection: keep-alive# Miss Marple.# Hercule Poirot.

發生這種情況是因爲,通過編寫 location /agatha,告訴 NGINX 匹配任何以 “agatha” 開頭的 URI。這種匹配稱爲前綴匹配

要執行完全匹配,必須按如下方式更新代碼:

events {}http {    server {        listen 80;        server_name nginx-handbook.test;        location = /agatha {            return 200 "Miss Marple.\nHercule Poirot.\n";        }    }}

在位置 URI 之前添加一個 = 符號將指示 NGINX 只有在 URL 完全匹配時才響應。現在,如果向除 /agatha 之外的任何內容發送請求,都將收到 404 響應。

curl -I http://nginx-handbook.test/agatha-christie# HTTP/1.1 404 Not Found# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:14:29 GMT# Content-Type: text/html# Content-Length: 162# Connection: keep-alivecurl -I http://nginx-handbook.test/agatha# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:15:04 GMT# Content-Type: text/plain# Content-Length: 29# Connection: keep-alive

NGINX 中的另一種匹配是 regex 匹配。使用此匹配,可以根據複雜的正則表達式檢查 URL location。

events {}http {    server {        listen 80;        server_name nginx-handbook.test;        location ~ /agatha[0-9] {        	return 200 "Miss Marple.\nHercule Poirot.\n";        }    }}

通過將之前使用的 = 符號替換爲 ~ 符號,來告訴 NGINX 執行正則表達式匹配。將 location 設置爲 ~ /agatha[0-9] 意味着 NIGINX 只有在單詞 “agatha” 後面有數字時纔會響應:

curl -I http://nginx-handbook.test/agatha# HTTP/1.1 404 Not Found# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:14:29 GMT# Content-Type: text/html# Content-Length: 162# Connection: keep-alivecurl -I http://nginx-handbook.test/agatha8# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:15:04 GMT# Content-Type: text/plain# Content-Length: 29# Connection: keep-alive

默認情況下,正則表達式匹配區分大小寫,這意味着如果將任何字母大寫,則該 location 將不起作用:

curl -I http://nginx-handbook.test/Agatha8# HTTP/1.1 404 Not Found# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:14:29 GMT# Content-Type: text/html# Content-Length: 162# Connection: keep-alive

要將其轉換爲不區分大小寫,必須在 ~ 符號後添加一個 *

events {}http {    server {        listen 80;        server_name nginx-handbook.test;        location ~* /agatha[0-9] {        	return 200 "Miss Marple.\nHercule Poirot.\n";        }    }}

這將告訴 NGINX 大小寫不敏感匹配 location。

curl -I http://nginx-handbook.test/agatha8# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:15:04 GMT# Content-Type: text/plain# Content-Length: 29# Connection: keep-alivecurl -I http://nginx-handbook.test/Agatha8# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Wed, 21 Apr 2021 16:15:04 GMT# Content-Type: text/plain# Content-Length: 29# Connection: keep-alive

NGINX 爲這些匹配分配優先級值,正則匹配比前綴匹配具有更高的優先級。

events {}http {    server {        listen 80;        server_name nginx-handbook.test;		location /Agatha8 {        	return 200 "prefix matched.\n";        }                location ~* /agatha[0-9] {        	return 200 "regex matched.\n";        }    }}

現在,如果向 http://nginx-handbook.test/Agatha8 發送請求,將得到以下響應:

curl -i http://nginx-handbook.test/Agatha8# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Thu, 22 Apr 2021 08:08:18 GMT# Content-Type: text/plain# Content-Length: 15# Connection: keep-alive# regex matched.

但是這個優先級可以稍微改變。NGINX 中的最後一種匹配類型是優先前綴匹配。要將前綴匹配轉換爲優先匹配,需要在位置 URI 之前包含 ^~ 修飾符:

events {}http {    server {        listen 80;        server_name nginx-handbook.test;		location ^~ /Agatha8 {        	return 200 "prefix matched.\n";        }                location ~* /agatha[0-9] {        	return 200 "regex matched.\n";        }    }}

現在,如果向 http://nginx-handbook.test/Agatha8 發送請求,將得到以下響應:

curl -i http://nginx-handbook.test/Agatha8# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Thu, 22 Apr 2021 08:13:24 GMT# Content-Type: text/plain# Content-Length: 16# Connection: keep-alive# prefix matched.

這一次,前綴匹配優先。所以按優先級降序排列的所有匹配列表如下:

| Match | Modifier | | --- | --- | | Exact | = | | Preferential Prefix | ^~ | | REGEX | ~ or ~* | | Prefix | None |

NGINX 中的變量

NGINX 中的變量和其他編程語言中的變量類似。set 指令可用於在配置文件中的任何位置聲明新變量:

set $<variable_name> <variable_value>;# set name "Farhan"# set age 25# set is_working true

變量可以是三種類型

除了聲明的變量之外,NGINX 模塊中還有內置的變量。變量的字母索引可在官方文檔中找到。

要查看一些正在運行的變量,按如下方式更新配置:

events {}http {    server {        listen 80;        server_name nginx-handbook.test;        return 200 "Host - $host\nURI - $uri\nArgs - $args\n";    }}

現在向服務器發送請求後,會得到如下響應:

# curl http://nginx-handbook.test/user?name=Farhan# Host - nginx-handbook.test# URI - /user# Args - name=Farhan

如你所見,$host$uri 變量分別保存根地址和相對於根的請求 URI。如你所見,$args 變量包含所有查詢字符串。

可以使用 $arg 變量訪問各個值,而不是打印查詢字符串的文字字符串形式。

events {}http {    server {        listen 80;        server_name nginx-handbook.test;                set $name $arg_name; # $arg_<query string name>        return 200 "Name - $name\n";    }}

現在來自服務器的響應應該如下所示:

curl http://nginx-handbook.test?name=Farhan# Name - Farhan

我在這裏演示的變量嵌入在 ngx_http_core_module 中。要在配置中訪問變量,必須使用嵌入變量的模塊構建 NGINX。從源代碼構建 NGINX 並使用動態模塊稍微超出了本文的範圍,但我肯定會在我的博客中寫到這一點。

Redirects 和 Rewrites

NGINX 中的重定向與任何其他平臺中的重定向相同。要演示重定向的工作原理,請將配置更新爲如下所示:

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;        root /srv/nginx-handbook-projects/static-demo;        location = /index_page {                return 307 /index.html;        }        location = /about_page {                return 307 /about.html;        }    }}

現在,如果向 http://nginx-handbook.test/about_page 發送請求,將被重定向到 http://nginx-handbook.test/about.html:

curl -I http://nginx-handbook.test/about_page# HTTP/1.1 307 Temporary Redirect# Server: nginx/1.18.0 (Ubuntu)# Date: Thu, 22 Apr 2021 18:02:04 GMT# Content-Type: text/html# Content-Length: 180# Location: http://nginx-handbook.test/about.html# Connection: keep-alive

如你所見,服務器響應狀態碼爲 307,位置指示 http://nginx-handbook.test/about.html 。如果你從瀏覽器訪問 http://nginx-handbook.test/about_page , 會看到 URL 將自動更改爲 http://nginx-handbook.test/about.html 。

然而,rewrite 指令的工作方式略有不同。它在內部更改 URI,而不讓用戶知道。要查看它的實際效果,請按如下方式更新配置:

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;        root /srv/nginx-handbook-projects/static-demo;        rewrite /index_page /index.html;        rewrite /about_page /about.html;    }}

現在,如果向 http://nginx-handbook/about_page URI 發送請求,將響應 200 響應碼的 about.html 文件:

curl -i http://nginx-handbook.test/about_page# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Thu, 22 Apr 2021 18:09:31 GMT# Content-Type: text/html# Content-Length: 960# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT# Connection: keep-alive# ETag: "60800c0a-3c0"# Accept-Ranges: bytes# <!DOCTYPE html># <html lang="en"># <head>#     <meta charset="UTF-8">#     <meta http-equiv="X-UA-Compatible" content="IE=edge">#     <meta >#     <style>#         .container {#             max-width: 1024px;#             margin-left: auto;#             margin-right: auto;#         }# #         h1 {#             text-align: center;#         }#     </style># </head># <body class="container">#     <header>#         <a class="button" href="index.html">Index</a>#         <a class="button" href="about.html">About</a>#         <a class="button" href="nothing">Nothing</a>#     </header>#     <div class="card fluid">#         <img src="./the-nginx-handbook.jpg" alt="The NGINX Handbook Cover Image">#     </div>#     <div class="card fluid">#         <h1>this is the <strong>about.html</strong> file</h1>#     </div># </body># </html>

如果使用瀏覽器訪問 URI,將看到 about.html 頁面,而 URL 保持不變:

除了處理 URI 更改的方式之外,重定向和重寫之間還有另一個區別。當重寫發生時,NGINX 會重新評估 server 上下文。因此,重寫是比重定向更昂貴的操作。

如何嘗試多個文件

本節要展示的最後一個概念是 try_files 指令。try_files 指令不是響應單個文件,而是一次檢查存在的多個文件。

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;        root /srv/nginx-handbook-projects/static-demo;        try_files /the-nginx-handbook.jpg /not_found;        location /not_found {                return 404 "sadly, you've hit a brick wall buddy!\n";        }    }}

如你所見,添加了一個新的 try_files 指令。通過編寫try_files /the-nginx-handbook.jpg /not_found;,可以指示 NGINX 在收到請求時在根目錄中查找名爲 the-nginx-handbook.jpg 的文件。如果它不存在,則轉到 /not_found 位置。

所以現在如果你訪問服務器,你會看到圖像:

但是,如果更新配置以嘗試使用不存在的文件(例如 blackhole.jpg),將收到 404 響應並顯示消息 “sadly, you've hit a brick wall buddy!”。

現在用這種方式寫一個 try_files 指令的問題是,無論你訪問什麼 URL,只要服務器收到一個請求並且在磁盤上找到了 -nginx-handbook.jpg 文件,NGINX 就會返回它。

這就是爲什麼 try_files 經常與 $uri NGINX 變量一起使用的原因。

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;        root /srv/nginx-handbook-projects/static-demo;        try_files $uri /not_found;        location /not_found {                return 404 "sadly, you've hit a brick wall buddy!\n";        }    }}

通過編寫 try_files $uri /not_found;,在指示 NGINX 首先嚐試獲取客戶端請求的 URI。如果它沒有找到,則嘗試下一個。

所以現在如果你訪問 http://nginx-handbook.test/index.html 應該得到舊的 index.html 頁面。about.html 頁面也是如此:

但是如果你請求一個不存在的文件,會得到 /not_found location 的響應:

curl -i http://nginx-handbook.test/nothing# HTTP/1.1 404 Not Found# Server: nginx/1.18.0 (Ubuntu)# Date: Thu, 22 Apr 2021 20:01:57 GMT# Content-Type: text/plain# Content-Length: 38# Connection: keep-alive# sadly, you've hit a brick wall buddy!

可能已經注意到的一件事是,如果訪問服務器根目錄 http://nginx-handbook.test,會收到 404 響應。

這是因爲當訪問服務器根目錄時,$uri 變量不對應任何現有文件作爲 NGINX 備選 location。如果要解決此問題,請按如下方式更新配置:

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;        root /srv/nginx-handbook-projects/static-demo;        try_files $uri $uri/ /not_found;        location /not_found {                return 404 "sadly, you've hit a brick wall buddy!\n";        }    }}

通過寫 try_files $uri $uri//not_found;,指示 NGINX 首先嚐試請求 URI。如果不存在,則嘗試將請求的 URI 作爲目錄,並且每當 NGINX 最終進入目錄時,它會自動開始查找 index.html 文件。

現在如果訪問服務器,應該得到 index.html 文件:

try_files 是一種可用於多種變體的指令。在接下來的部分中,會遇到一些其他變體,但我建議在 Internet 上自行研究該指令的不同用法。

NGINX 日誌

默認情況下,NGINX 的日誌文件位於/var/log/nginx 中。如果列出這個目錄的內容,可能會看到如下內容:

ls -lh /var/log/nginx/# -rw-r----- 1 www-data adm     0 Apr 25 07:34 access.log# -rw-r----- 1 www-data adm     0 Apr 25 07:34 error.log

讓我們先清空這兩個文件。

# delete the old filessudo rm /var/log/nginx/access.log /var/log/nginx/error.log# create new filessudo touch /var/log/nginx/access.log /var/log/nginx/error.log# reopen the log filessudo nginx -s reopen

如果不向 NGINX 發送 reopen 信號,它將繼續將日誌寫入先前打開的流,而新文件將保持爲空。

現在要在訪問日誌中創建一個條目,向服務器發送一個請求。

curl -I http://nginx-handbook.test# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Sun, 25 Apr 2021 08:35:59 GMT# Content-Type: text/html# Content-Length: 960# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT# Connection: keep-alive# ETag: "608529d5-3c0"# Accept-Ranges: bytessudo cat /var/log/nginx/access.log # 192.168.20.20 - - [25/Apr/2021:08:35:59 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.68.0"

如你所見,access.log 文件中添加了一個新條目。默認情況下,對服務器的任何請求都將記錄到此文件中。我們可以使用 access_log 指令來改變這種行爲。

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;                location / {            return 200 "this will be logged to the default file.\n";        }                location = /admin {            access_log /var/logs/nginx/admin.log;                        return 200 "this will be logged in a separate file.\n";        }                location = /no_logging {            access_log off;                        return 200 "this will not be logged.\n";        }    }}

/admin location 塊中的第一個 access_log 指令指示 NGINX 將此 URI 的任何訪問日誌寫入 /var/logs/nginx/admin.log 文件。第二個 /no_logging location 中的完全關閉此 location 的訪問日誌。

驗證並重新加載配置。現在,如果向這些位置發送請求並檢查日誌文件,應該會看到如下內容:

curl http://nginx-handbook.test/no_logging# this will not be loggedsudo cat /var/log/nginx/access.log# emptycurl http://nginx-handbook.test/admin# this will be logged in a separate file.sudo cat /var/log/nginx/access.log# emptysudo cat /var/log/nginx/admin.log # 192.168.20.20 - - [25/Apr/2021:11:13:53 +0000] "GET /admin HTTP/1.1" 200 40 "-" "curl/7.68.0"curl  http://nginx-handbook.test/# this will be logged to the default file.sudo cat /var/log/nginx/access.log # 192.168.20.20 - - [25/Apr/2021:11:15:14 +0000] "GET / HTTP/1.1" 200 41 "-" "curl/7.68.0"

另一方面,error.log 文件保存失敗日誌。要進入 error.log,必須使 NGINX crash。爲此,請按如下方式更新配置:

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;        return 200 "..." "...";    }}

如你所知,return 指令只接受兩個參數——但我們在這裏給出了三個。現在嘗試重新加載配置,將看到一條錯誤消息:

sudo nginx -s reload# nginx: [emerg] invalid number of arguments in "return" directive in /etc/nginx/nginx.conf:14

檢查錯誤日誌的內容,消息也應該出現在那裏:

sudo cat /var/log/nginx/error.log # 2021/04/25 08:35:45 [notice] 4169#4169: signal process started# 2021/04/25 10:03:18 [emerg] 8434#8434: invalid number of arguments in "return" directive in /etc/nginx/nginx.conf:14

錯誤消息有級別。錯誤日誌中的 notice 條目是代表日誌記錄的信息是無關緊要的,但必須立即處理 emergcrit 條目。

有八個級別的錯誤消息:

默認情況下,NGINX 記錄所有級別的消息。可以使用 error_log 指令覆蓋此行爲。如果要將消息的最低級別設置爲warn,請按如下方式更新配置文件:

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx-handbook.test;	    	error_log /var/log/error.log warn;        return 200 "..." "...";    }}

驗證並重新加載配置,從現在開始,只會記錄級別爲 warn 或更高級別的消息。

cat /var/log/nginx/error.log# 2021/04/25 11:27:02 [emerg] 12769#12769: invalid number of arguments in "return" directive in /etc/nginx/nginx.conf:16

與之前的輸出不同,這裏沒有 “通知” 條目。emerg 是比 warn 更高級別的錯誤,這就是它被記錄的原因。

對於大多數項目,保留錯誤配置應該沒問題。我唯一的建議是將最小錯誤級別設置爲 warn。這樣就不必查看錯誤日誌中不必要的條目。

但是,如果想了解有關在 NGINX 中自定義日誌記錄的更多信息,此官方文檔的 鏈接可能會有所幫助。

如何使用 NGINX 作爲反向代理

當配置爲反向代理時,NGINX 位於客戶端和後端服務器之間。客戶端向 NGINX 發送請求,然後 NGINX 將請求傳遞給後端。

後端服務器處理完請求後,將其發送回 NGINX。反過來,NGINX 將響應返回給客戶端。

在整個過程中,客戶端不知道誰在實際處理請求。寫起來聽起來很複雜,但一旦你自己動手,你就會發現 NGINX 讓反向代理變的多麼容易。

讓我們看一個非常基本且不切實際的反向代理示例:

events {}http {    include /etc/nginx/mime.types;    server {        listen 80;        server_name nginx.test;        location / {                proxy_pass "https://nginx.org/";        }    }}

除了驗證和重新加載配置之外,還必須將此地址添加到的 hosts 文件中,以使此演示在你的系統上運行:

192.168.20.20   nginx.test

現在,如果訪問 http://nginx.test,將看到原始的 https://nginx.org 站點,而 URI 保持不變。

甚至能夠在一定程度上瀏覽網站。如果訪問 http://nginx.test/en/docs/ 應該得到 http://nginx.org/en/docs/ 頁面的響應。

如你所見,在基本層面上,proxy_pass 指令只是將客戶端的請求傳遞給第三方服務器,並將響應反向代理到客戶端。

使用 NGINX 的 Node.js

現在已經知道如何配置基本的反向代理服務器,可以爲 NGINX 反向代理的 Node.js 應用程序提供服務。我在本文附帶的代碼倉庫中添加了一個演示應用程序。

我假設你有使用 Node.js 的經驗並且知道如何使用 PM2 啓動 Node.js 應用程序。

如果你已經在 /srv/nginx-handbook-projects 中克隆了存儲庫,那麼 node-js-demo 項目應該在 /srv/nginx-handbook-projects/node-js-demo 目錄中。

爲了運行這個 demo,需要在你的服務器上安裝 Node.js。可以按照此處找到的說明執行此操作。

演示應用程序是一個簡單的 HTTP 服務器,它響應 200 狀態代碼和 JSON 有效負載。可以通過簡單地執行 node app.js 來啓動應用程序,但更好的方法是使用 PM2。

PM2 是一個守護進程管理器,廣泛用於 Node.js 應用程序的生產。如果想了解更多信息,此鏈接可能會有所幫助。

通過執行 sudo npm install -g pm2 全局安裝 PM2。安裝完成後,在/srv/nginx-handbook-projects/node-js-demo目錄下執行以下命令:

pm2 start app.js# [PM2] Process successfully started# ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐# │ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │# ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤# │ 0  │ app                │ fork     │ 0    │ online    │ 0%       │ 21.2mb   │# └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

或者,也可以從服務器上的任何位置執行 pm2 start /srv/nginx-handbook-projects/node-js-demo/app.js。可以通過執行 pm2 stop app 命令來停止應用程序。

應用程序現在應該正在運行,但不應從服務器外部訪問。要驗證應用程序是否正在運行,請從服務器內部向 http://localhost:3000 發送 get 請求:

curl -i localhost:3000# HTTP/1.1 200 OK# X-Powered-By: Express# Content-Type: application/json; charset=utf-8# Content-Length: 62# ETag: W/"3e-XRN25R5fWNH2Tc8FhtUcX+RZFFo"# Date: Sat, 24 Apr 2021 12:09:55 GMT# Connection: keep-alive# Keep-Alive: timeout=5# { "status": "success", "message": "You're reading The NGINX Handbook!" }

如果收到 200 響應,則服務器運行成功。現在要將 NGINX 配置爲反向代理,請打開配置文件並按如下方式更新其內容:

events {}  http {    listen 80;    server_name nginx-handbook.test    location / {        proxy_pass http://localhost:3000;    }}

這裏沒啥需要解釋的。只是將接收到的請求傳遞給在端口 3000 上運行的 Node.js 應用程序。現在,如果從外部向服務器發送請求,應該得到如下響應:

curl -i http://nginx-handbook.test# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Sat, 24 Apr 2021 14:58:01 GMT# Content-Type: application/json# Transfer-Encoding: chunked# Connection: keep-alive# { "status": "success", "message": "You're reading The NGINX Handbook!" }

儘管這適用於像這樣的基本服務器,但可能需要添加更多指令才能使其在實際場景中工作,具體取決於應用程序的要求。

例如,如果應用程序處理 Web socket 連接,那麼應該按如下方式更新配置:

events {}  http {    listen 80;    server_name nginx-handbook.test    location / {        proxy_pass http://localhost:3000;        proxy_http_version 1.1;        proxy_set_header Upgrade $http_upgrade;        proxy_set_header Connection 'upgrade';    }}

proxy_http_version 指令設置服務器的 HTTP 版本。默認情況下它是 1.0,但 web socket 要求它至少是 1.1。proxy_set_header 指令用於在後端服務器上設置標頭。該指令的通用語法如下:

proxy_set_header <header name> <header value>

因此,通過編寫proxy_set_header Upgrade $http_upgrade;,是在指示 NGINX 將$http_upgrade 變量的值作爲名爲Upgrade 的標頭傳遞——與Connection 標頭相同。

如果你想了解更多關於 web socket 代理的信息,這個指向官方 NGINX 文檔的鏈接可能會有所幫助。

根據的應用程序所需的標頭,可能需要設置更多標頭。但是上面提到的配置在配置 Node.js 應用程序時非常常見。

使用 NGINX 的 PHP

PHP 和 NGINX 就像麪包和黃油一樣。畢竟 LEMP 技術棧中的 E 和 P 就代表 NGINX 和 PHP。

這裏假設你有使用 PHP 的經驗並且知道如何運行 PHP 應用程序。

我已經在本文附帶的代碼倉庫中包含了一個演示 PHP 應用程序。如果你已經在/srv/nginx-handbook-projects 目錄中克隆了它,那麼應用程序應該在/srv/nginx-handbook-projects/php-demo 中。

爲了讓這個演示運行,你必須安裝一個名爲 PHP-FPM 的包。要安裝軟件包,可以執行以下命令:

sudo apt install php-fpm -y

要測試應用程序,請通過在 /srv/nginx-handbook-projects/php-demo` 目錄中執行以下命令來啓動 PHP 服務:

php -S localhost:8000# [Sat Apr 24 16:17:36 2021] PHP 7.4.3 Development Server (http://localhost:8000) started

或者,也可以從服務器上的任何位置執行 php -S localhost:8000 /srv/nginx-handbook-projects/php-demo/index.php

該應用程序應該在端口 8000 上運行,但無法從服務器外部訪問它。要進行驗證,請從服務器內部向 http://localhost:8000 發送 get 請求:

curl -I localhost:8000# HTTP/1.1 200 OK# Host: localhost:8000# Date: Sat, 24 Apr 2021 16:22:42 GMT# Connection: close# X-Powered-By: PHP/7.4.3# Content-type: application/json# {"status":"success","message":"You're reading The NGINX Handbook!"}

如果你收到 200 響應,則服務器運行成功。就像 Node.js 配置一樣,現在可以簡單地將請求 proxy_pass 發送到 localhost:8000 – 但是對於 PHP,有更好的方法。

PHP-FPM 中的 FPM 部分代表 FastCGI Process Module。FastCGI 是一種類似於 HTTP 的協議,用於交換二進制數據。此協議比 HTTP 稍快,並提供更好的安全性。

To use FastCGI instead of HTTP, update your configuration as follows:

要使用 FastCGI 而不是 HTTP,請按如下方式更新配置:

events {}http {      include /etc/nginx/mime.types;      server {          listen 80;          server_name nginx-handbook.test;          root /srv/nginx-handbook-projects/php-demo;          index index.php;          location / {              try_files $uri $uri/ =404;          }          location ~ \.php$ {              fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;              fastcgi_param REQUEST_METHOD $request_method;              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;      }   }}

讓我們從新的 index 指令開始。如你所知,NGINX 默認會查找 index.html 文件來提供服務。但在演示項目中,它是 index.php。因此,編寫 index index.php,指示 NGINX 以 root 用戶身份使用 index.php 文件。

該指令可以接受多個參數。對於 index index.php index.html,NGINX 會首先尋找 index.php。如果它沒有找到這個文件,它會尋找 index.html 文件。

第一個 location 上下文中的 try_files 指令與在上一節中看到的相同。最後的 =404 表示如果沒有找到任何文件則拋出錯誤。

第二個 location 塊是魔法發生的地方。如你所見,我們已經用新的 fastcgi_pass 替換了 proxy_pass 指令。顧名思義,它用於將請求傳遞給 FastCGI 服務。

PHP-FPM 服務默認運行在主機的 9000 端口上。因此,可以直接將請求傳遞給 http://localhost:9000,而不是像我在這裏所做的那樣使用 Unix 套接字。但是使用 Unix 套接字更安全。

如果安裝了多個 PHP-FPM 版本,則可以通過執行以下命令簡單地列出所有套接字文件位置:

sudo find / -name *fpm.sock# /run/php/php7.4-fpm.sock# /run/php/php-fpm.sock# /etc/alternatives/php-fpm.sock# /var/lib/dpkg/alternatives/php-fpm.sock

/run/php/php-fpm.sock 文件是指系統上安裝的最新版本的 PHP-FPM。我更喜歡使用帶有版本號的那個。這樣即使 PHP-FPM 得到更新,我也可以知道我正在使用的版本。

與通過 HTTP 傳遞請求不同,通過 FPM 傳遞請求需要我們傳遞一些額外的信息。

將額外信息傳遞給 FPM 服務的一般方法是使用 fastcgi_param 指令。至少,必須將請求方法和腳本名稱傳遞給後端服務才能使代理工作。

fastcgi_param REQUEST_METHOD $request_method; 將請求方法傳遞給後端,fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 行傳遞要運行的 PHP 腳本的確切位置。

在這種狀態下,配置應該可以工作。要測試它,請訪問服務器,應該會看到如下內容:

嗯,這很奇怪。500 錯誤意味着 NGINX 由於某種原因崩潰了。這是錯誤日誌可以派上用場的地方。讓我們看看 error.log 文件中的最後一個條目:

tail -n 1 /var/log/nginx/error.log# 2021/04/24 17:15:17 [crit] 17691#17691: *21 connect() to unix:/var/run/php/php7.4-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.20.20, server: nginx-handbook.test, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.4-fpm.sock:", host: "nginx-handbook.test"

似乎 NGINX 進程沒有訪問 PHP-FPM 進程的權限。

獲得權限被拒絕錯誤的主要原因之一是用戶不匹配。查看擁有 NGINX 工作進程的用戶。

ps aux | grep nginx# root         677  0.0  0.4   8892  4260 ?        Ss   14:31   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;# nobody     17691  0.0  0.3   9328  3452 ?        S    17:09   0:00 nginx: worker process# vagrant    18224  0.0  0.2   8160  2552 pts/0    S+   17:19   0:00 grep --color=auto nginx

如你所見,該進程當前歸 nobody 所有。現在檢查 PHP-FPM 進程。

# ps aux | grep php# root       14354  0.0  1.8 195484 18924 ?        Ss   16:11   0:00 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)# www-data   14355  0.0  0.6 195872  6612 ?        S    16:11   0:00 php-fpm: pool www# www-data   14356  0.0  0.6 195872  6612 ?        S    16:11   0:00 php-fpm: pool www# vagrant    18296  0.0  0.0   8160   664 pts/0    S+   17:20   0:00 grep --color=auto php

另一方面,此進程由 www-data 用戶擁有。這就是 NGINX 被拒絕訪問此進程的原因。

要解決此問題,請按如下方式更新配置:

user www-data;events {}http {      include /etc/nginx/mime.types;      server {          listen 80;          server_name nginx-handbook.test;          root /srv/nginx-handbook-projects/php-demo;          index index.php;          location / {              try_files $uri $uri/ =404;          }          location ~ \.php$ {              fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;              fastcgi_param REQUEST_METHOD $request_method;              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;      }   }}

user 指令負責設置 NGINX 工作進程的所有者。現在再次檢查 NGINX 進程:

# ps aux | grep nginx# root         677  0.0  0.4   8892  4264 ?        Ss   14:31   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;# www-data   20892  0.0  0.3   9292  3504 ?        S    18:10   0:00 nginx: worker process# vagrant    21294  0.0  0.2   8160  2568 pts/0    S+   18:18   0:00 grep --color=auto nginx

毫無疑問,該進程現在歸 www-data 用戶所有。向服務器發送請求以檢查它是否正常工作:

# curl -i http://nginx-handbook.test# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Sat, 24 Apr 2021 18:22:24 GMT# Content-Type: application/json# Transfer-Encoding: chunked# Connection: keep-alive# {"status":"success","message":"You're reading The NGINX Handbook!"}

如果收到帶有 JSON 有效負載的 200 狀態代碼,那麼就可以開始了。

這種簡單的配置適用於演示應用程序,但在實際項目中,必須傳遞一些額外的參數。

出於這個原因,NGINX 包含一個名爲 fastcgi_params 的部分配置。該文件包含最常見的 FastCGI 參數列表。

cat /etc/nginx/fastcgi_params# fastcgi_param  QUERY_STRING       $query_string;# fastcgi_param  REQUEST_METHOD     $request_method;# fastcgi_param  CONTENT_TYPE       $content_type;# fastcgi_param  CONTENT_LENGTH     $content_length;# fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;# fastcgi_param  REQUEST_URI        $request_uri;# fastcgi_param  DOCUMENT_URI       $document_uri;# fastcgi_param  DOCUMENT_ROOT      $document_root;# fastcgi_param  SERVER_PROTOCOL    $server_protocol;# fastcgi_param  REQUEST_SCHEME     $scheme;# fastcgi_param  HTTPS              $https if_not_empty;# fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;# fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;# fastcgi_param  REMOTE_ADDR        $remote_addr;# fastcgi_param  REMOTE_PORT        $remote_port;# fastcgi_param  SERVER_ADDR        $server_addr;# fastcgi_param  SERVER_PORT        $server_port;# fastcgi_param  SERVER_NAME        $server_name;# PHP only, required if PHP was built with --enable-force-cgi-redirect# fastcgi_param  REDIRECT_STATUS    200;

如你所見,此文件還包含 REQUEST_METHOD 參數。可以在配置中包含此文件,而不是手動傳遞它:

user www-data;events {}http {      include /etc/nginx/mime.types;      server {          listen 80;          server_name nginx-handbook.test;          root /srv/nginx-handbook-projects/php-demo;          index index.php;          location / {              try_files $uri $uri/ =404;          }          location ~ \.php$ {              fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;              include /etc/nginx/fastcgi_params;      }   }}

服務應該表現得一樣。除了 fastcgi_params 文件,可能還會遇到包含一組稍微不同的參數的 fastcgi.conf 文件。我建議你避免這種情況發生,因爲它與它的行爲有些不一致。

如何使用 NGINX 作爲負載均衡器

由於 NGINX 的反向代理設計,可以輕鬆地將其配置爲負載均衡器。

我已經在本文附帶的代碼倉庫庫中添加了一個演示。如果你已經在 /srv/nginx-handbook-projects/ 目錄中克隆了代碼倉庫,那麼演示應該在 /srv/nginx-handbook-projects/load-balancer-demo/ 目錄中。

在現實生活中,分佈在多個服務器上的大型項目可能需要負載均衡。但是對於這個簡單的演示,我創建了三個非常簡單的 Node.js 服務,響應服務編號和 200 狀態代碼。

爲了讓這個演示工作,你需要在服務器上安裝 Node.js。可以在此鏈接中找到說明以幫助你安裝它。

除此之外,還需要 PM2 來守護本演示中提供的 Node.js 服務。

如果還沒有安裝,請通過執行 sudo npm install -g pm2 來安裝 PM2。安裝完成後,執行以下命令啓動三個 Node.js 服務:

pm2 start /srv/nginx-handbook-projects/load-balancer-demo/server-1.jspm2 start /srv/nginx-handbook-projects/load-balancer-demo/server-2.jspm2 start /srv/nginx-handbook-projects/load-balancer-demo/server-3.jspm2 list# ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐# │ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │# ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤# │ 0  │ server-1           │ fork     │ 0    │ online    │ 0%       │ 37.4mb   │# │ 1  │ server-2           │ fork     │ 0    │ online    │ 0%       │ 37.2mb   │# │ 2  │ server-3           │ fork     │ 0    │ online    │ 0%       │ 37.1mb   │# └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

三個 Node.js 服務應該分別運行在 localhost:3001、localhost:3002、localhost:3003 上。

現在更新配置如下:

events {}http {    upstream backend_servers {        server localhost:3001;        server localhost:3002;        server localhost:3003;    }    server {        listen 80;        server_name nginx-handbook.test;        location / {            proxy_pass http://backend_servers;        }    }}

server 上下文中的配置與已經看到的相同。但是,upstream上下文是新的。NGINX 中的 upstream 是一組可以被視爲單個後端的服務器。

所以你開始使用 PM2 啓動的三臺服務器可以放在一個 upstream,可以讓 NGINX 均衡它們之間的負載。

要測試配置,必須向服務器發送大量請求。可以使用 bash 中的 while 循環自動執行該過程:

while sleep 0.5; do curl http://nginx-handbook.test; done# response from server - 2.# response from server - 3.# response from server - 1.# response from server - 2.# response from server - 3.# response from server - 1.# response from server - 2.# response from server - 3.# response from server - 1.# response from server - 2.

可以通過按鍵盤上的 Ctrl + C 來取消循環。從服務器的響應中可以看出,NGINX 正在自動對服務器進行負載均衡。

當然,更大的項目規模,負載均衡可能比這複雜得多。但本文的目的是讓你入門,相信你現在對 NGINX 負載均衡有了基本的瞭解。你可以通過執行 pm2 stop server-1 server-2 server-3 命令來停止三個正在運行的服務器(建議如此)。

如何優化 NGINX 以獲得最大性能

在本文的這一部分中,將瞭解使服務器獲得最大性能的多種方法。

其中一些方法是特定於應用程序的,這意味着它們可能需要根據你的應用程序要求進行調整。但其中一些是普適的優化手段。

就像前幾節一樣,在這一節中配置更改會很頻繁,所以不要忘記每次驗證和重新加載配置文件。

如何配置工作進程和工作連接

正如我在上一節中已經提到的,NGINX 可以產生多個工作進程,每個進程能夠處理數千個請求。

sudo systemctl status nginx# ● nginx.service - A high performance web server and a reverse proxy server#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)#      Active: active (running) since Sun 2021-04-25 08:33:11 UTC; 5h 45min ago#        Docs: man:nginx(8)#    Main PID: 3904 (nginx)#       Tasks: 2 (limit: 1136)#      Memory: 3.2M#      CGroup: /system.slice/nginx.service#              ├─ 3904 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;#              └─16443 nginx: worker process

如你所見,現在系統上只有一個 NGINX 工作進程。但是,可以通過對配置文件更改此數字。

worker_processes 2;events {}http {    server {        listen 80;        server_name nginx-handbook.test;        return 200 "worker processes and worker connections configuration!\n";    }}

main 上下文中編寫的 worker_process 指令負責設置要生成的工作進程的數量。現在再次檢查 NGINX 服務,你應該會看到兩個工作進程:

sudo systemctl status nginx# ● nginx.service - A high performance web server and a reverse proxy server#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)#      Active: active (running) since Sun 2021-04-25 08:33:11 UTC; 5h 54min ago#        Docs: man:nginx(8)#     Process: 22610 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)#    Main PID: 3904 (nginx)#       Tasks: 3 (limit: 1136)#      Memory: 3.7M#      CGroup: /system.slice/nginx.service#              ├─ 3904 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;#              ├─22611 nginx: worker process#              └─22612 nginx: worker process

設置工作進程的數量很容易,但確定工作進程的最佳數量需要更多的工作。

工作進程本質上是異步的。這意味着它們將盡可能快地處理傳入的請求。

現在假設你的服務器在單核處理器上運行。如果將工作進程數設置爲 1,則該單個進程將使用 100% 的 CPU 容量。但是如果將其設置爲 2,則兩個進程將能夠分別使用 50% 的 CPU。所以增加工作進程的數量並不意味着更好的性能。

確定最佳工作進程數的經驗法則是 工作進程數 = CPU 內核數

如果你在具有雙核 CPU 的服務器上運行,則應將工作進程數設置爲 2。在四核中,應將其設置爲 4... 明白了吧。

在 Linux 上可以用如下命令確定服務器上的 CPU 數量。

nproc# 1

我在單 CPU 虛擬機上運行,所以 nproc 檢測到有一個 CPU。既然已經知道了 CPU 的數量,剩下要做的就是在配置中設置數量。

這一切都很好,但是每次升級服務器並且 CPU 數量發生變化時,都必須手動更新服務器配置。

NGINX 提供了一種更好的方法來處理這個問題。可以簡單地將工作進程的數量設置爲 auto,NGINX 將根據 CPU 的數量自動設置進程的數量。

worker_processes auto;events {}http {    server {        listen 80;        server_name nginx-handbook.test;        return 200 "worker processes and worker connections configuration!\n";    }}

再次檢查 NGINX 進程:

sudo systemctl status nginx# ● nginx.service - A high performance web server and a reverse proxy server#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)#      Active: active (running) since Sun 2021-04-25 08:33:11 UTC; 6h ago#        Docs: man:nginx(8)#     Process: 22610 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)#    Main PID: 3904 (nginx)#       Tasks: 2 (limit: 1136)#      Memory: 3.2M#      CGroup: /system.slice/nginx.service#              ├─ 3904 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;#              └─23659 nginx: worker process

工作進程的數量又恢復爲 1,因爲這是該服務器的最佳選擇。

除了工作進程之外,還有工作連接,表示單個工作進程可以處理的最大連接數。

就像工作進程的數量一樣,這個數字也與 CPU 核心數量以及操作系統每個核心允許打開的文件數量有關。

在 Linux 上用這個命令查看這個數字:

ulimit -n# 1024

現在有了這個數字,接下來在配置中設置它:

worker_processes auto;

events {
    worker_connections 1024;
}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "worker processes and worker connections configuration!\n";
    }
}

worker_connections 指令負責設置配置中的工作連接數。這也是第一次使用 events 上下文。

在上一節中,我提到此上下文用於設置 NGINX 在一般級別上使用的值。工作連接配置就是這樣的一個例子。

如何緩存靜態內容

優化服務器的第二種技術是緩存靜態內容。無論使用哪種應用程序,總會提供一定數量的靜態內容,例如樣式表、圖像等。

考慮到這些內容不太可能經常更改,最好將它們緩存一段時間。NGINX 實現起來很簡單。

worker_processes auto;

events {
    worker_connections 1024;
}

http {

    include /env/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-demo/static-demo;
        
        location ~* \.(css|js|jpg)$ {
            access_log off;
            
            add_header Cache-Control public;
            add_header Pragma public;
            add_header Vary Accept-Encoding;
            expires 1M;
        }
    }
}

通過編寫location ~* .(css|js|jpg)$,指示 NGINX 匹配請求以 .css.js.jpg 結尾的文件。

在我的應用程序中,即使用戶提交不同的格式,我通常也以 WebP 格式存儲圖像。這樣,配置靜態緩存對我來說變得更加容易。

可以使用 add_header 指令在對客戶端的響應中包含一個標頭。之前你已經看到了 proxy_set_header 指令,用於在對後端服務器的持續請求中設置標頭。另一方面,add_header 指令僅將給定的標頭添加到響應中。

通過將 Cache-Control 標頭設置爲 public,告訴客戶端這個內容可以以任何方式緩存。Pragma 標頭只是一箇舊版本的 Cache-Control 標頭並且或多或少地做了相同的事情。

下一個標頭 Vary 負責讓客戶端知道這個緩存的內容可能會有所不同。

Accept-Encoding 的值意味着內容可能會根據客戶端接受的內容編碼而有所不同。這將在下一節中進一步闡明。

最後,expires 指令可以方便地設置 Expires 標頭 expires 指令佔用此緩存有效的持續時間。通過將其設置爲 “1M”,告訴 NGINX 將內容緩存一個月。還可以將其設置爲 10m 10 minutes、24h 24 hours,等等。

現在要測試配置,從服務器發送對 nginx-handbook.jpg 文件的請求:

curl -I http://nginx-handbook.test/the-nginx-handbook.jpg

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sun, 25 Apr 2021 15:58:22 GMT
# Content-Type: image/jpeg
# Content-Length: 19209
# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT
# Connection: keep-alive
# ETag: "608529d5-4b09"
# Expires: Tue, 25 May 2021 15:58:22 GMT
# Cache-Control: max-age=2592000
# Cache-Control: public
# Pragma: public
# Vary: Accept-Encoding
# Accept-Ranges: bytes

如你所見,標頭已添加到響應中,任何主流瀏覽器都應該能夠解釋它們。

如何壓縮響應

要展示的最後一種優化技術非常簡單:壓縮響應以減小其大小。

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /env/nginx/mime.types;

    gzip on;
    gzip_comp_level 3;

    gzip_types text/css text/javascript;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-demo/static-demo;
        
        location ~* \.(css|js|jpg)$ {
            access_log off;
            
            add_header Cache-Control public;
            add_header Pragma public;
            add_header Vary Accept-Encoding;
            expires 1M;
        }
    }
}

GZIP 是一種流行的文件格式,被應用程序用於文件壓縮和解壓縮。NGINX 可以使用 gzip 指令利用這種格式壓縮響應。

通過在 http 上下文中編寫 gzip on,可以指示 NGINX 壓縮響應。gzip_comp_level 指令設置壓縮級別。可以將其設置爲非常高的數字,但這並不能保證更好的壓縮。設置 1 - 4 之間的數字可提供有效的結果。例如,我喜歡將其設置爲 3。

默認情況下,NGINX 壓縮 HTML 響應。要壓縮其他文件格式,必須將它們作爲參數傳遞給 gzip_types 指令。通過編寫gzip_types text/css text/javascript;,告訴 NGINX 使用 text/css 和 text/javascript 的 mime 類型壓縮任何文件。

在 NGINX 中配置壓縮是不夠的。客戶端必須請求壓縮響應而不是未壓縮響應。我希望你記得上一節關於緩存的 add_header Vary Accept-Encoding; 行。此標頭讓客戶端知道響應可能會根據客戶端接受的內容而有所不同。

例如,如果想從服務器請求未壓縮版本的 mini.min.css 文件,可以執行以下操作:

curl -I http://nginx-handbook.test/mini.min.css# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Sun, 25 Apr 2021 16:30:32 GMT# Content-Type: text/css# Content-Length: 46887# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT# Connection: keep-alive# ETag: "608529d5-b727"# Expires: Tue, 25 May 2021 16:30:32 GMT# Cache-Control: max-age=2592000# Cache-Control: public# Pragma: public# Vary: Accept-Encoding# Accept-Ranges: bytes

如你所見,沒有開啓壓縮的。現在,如果你想請求文件的壓縮版本,則必須發送額外的標頭。

curl -I -H "Accept-Encoding: gzip" http://nginx-handbook.test/mini.min.css# HTTP/1.1 200 OK# Server: nginx/1.18.0 (Ubuntu)# Date: Sun, 25 Apr 2021 16:31:38 GMT# Content-Type: text/css# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT# Connection: keep-alive# ETag: W/"608529d5-b727"# Expires: Tue, 25 May 2021 16:31:38 GMT# Cache-Control: max-age=2592000# Cache-Control: public# Pragma: public# Vary: Accept-Encoding# Content-Encoding: gzip

正如你在響應頭中看到的,Content-Encoding 現在被設置爲 gzip,這意味着這是文件的壓縮版本。

現在,如果要比較文件大小的差異,可以執行以下操作:

cd ~mkdir compression-test && cd compression-testcurl http://nginx-handbook.test/mini.min.css > uncompressed.csscurl -H "Accept-Encoding: gzip" http://nginx-handbook.test/mini.min.css > compressed.cssls -lh# -rw-rw-r-- 1 vagrant vagrant 9.1K Apr 25 16:35 compressed.css# -rw-rw-r-- 1 vagrant vagrant  46K Apr 25 16:35 uncompressed.css

該文件的未壓縮版本爲 “46K”,壓縮版本爲 “9.1K”,幾乎小六倍。線上的站點樣式表會的更大,壓縮可以使響應文件更小、響應速度更快。

如何理解主配置文件

我希望你記得你在前面部分重命名的原始 nginx.conf 文件。根據 Debian wiki,這個文件應該由 NGINX 維護者而不是服務器管理員來更改,除非他們確切地知道他們在做什麼。

是在整篇文章中,我已經教你在這個文件中配置服務器。在本節中,我將介紹如何在不更改 nginx.conf 文件的情況下配置服務器。

首先,首先刪除或重命名修改後的 nginx.conf 文件並恢復原來的文件:

sudo rm /etc/nginx/nginx.confsudo mv /etc/nginx/nginx.conf.backup /etc/nginx/nginx.confsudo nginx -s reload

現在 NGINX 應該回到它的原始狀態。讓我們通過執行 sudo cat /etc/nginx/nginx.conf 文件再次查看該文件的內容:

user www-data;worker_processes auto;pid /run/nginx.pid;include /etc/nginx/modules-enabled/*.conf;events {	worker_connections 768;	# multi_accept on;}http {	##	# Basic Settings	##	sendfile on;	tcp_nopush on;	tcp_nodelay on;	keepalive_timeout 65;	types_hash_max_size 2048;	# server_tokens off;	# server_names_hash_bucket_size 64;	# server_name_in_redirect off;	include /etc/nginx/mime.types;	default_type application/octet-stream;	##	# SSL Settings	##	ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE	ssl_prefer_server_ciphers on;	##	# Logging Settings	##	access_log /var/log/nginx/access.log;	error_log /var/log/nginx/error.log;	##	# Gzip Settings	##	gzip on;	# gzip_vary on;	# gzip_proxied any;	# gzip_comp_level 6;	# gzip_buffers 16 8k;	# gzip_http_version 1.1;	# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;	##	# Virtual Host Configs	##	include /etc/nginx/conf.d/*.conf;	include /etc/nginx/sites-enabled/*;}#mail {#	# See sample authentication script at:#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript# #	# auth_http localhost/auth.php;#	# pop3_capabilities "TOP" "USER";#	# imap_capabilities "IMAP4rev1" "UIDPLUS";# #	server {#		listen     localhost:110;#		protocol   pop3;#		proxy      on;#	}# #	server {#		listen     localhost:143;#		protocol   imap;#		proxy      on;#	}#}

現在應該能夠看懂此文件。在主上下文 user www-data; 中,worker_processes auto; 行已經介紹過,不在贅述。

pid /run/nginx.pid; 行設置 NGINX 進程的進程 ID,include /etc/nginx/modules-enabled/*.conf; 引入 /etc/nginx/ modules-enabled/ 目錄的配置文件。

該目錄用於存放 NGINX 動態模塊。我在本文中沒有涉及動態模塊,所以我將跳過它。

現在在 http 上下文中,在基本設置下,可以看到一些常用的優化技術。以下是這些技術的作用:

keepalive_timeout 指令指示保持連接打開的時間,types_hash_maxsize 指令設置類型哈希映射的大小。默認情況下,它還包括 mime.types 文件。

我將跳過 SSL 設置,因爲我不打算在本文介紹它們。我們已經討論了日誌記錄和 gzip 設置。可能會看到一些關於 gzip 的指令的註釋內容。只要你瞭解自己在做什麼,就可以自定義這些設置。

可以使用 mail 上下文將 NGINX 配置爲郵件服務器。到目前爲止,我們只討論了 NGINX 作爲 Web 服務器,所以我也將跳過這一點。

現在在虛擬主機設置下,應該看到如下兩行:

### Virtual Host Configs##include /etc/nginx/conf.d/*.conf;include /etc/nginx/sites-enabled/*;

這兩行指示 NGINX 引入在 /etc/nginx/conf.d//etc/nginx/sites-enabled/ 目錄中找到的所有配置文件。

看到這兩行後,人們往往會把這兩個目錄作爲放置配置文件的理想位置,但這是不對的。

還有另一個目錄 /etc/nginx/sites-available/ 用於存儲虛擬主機的配置文件。/etc/nginx/sites-enabled/ 目錄用於存儲指向 /etc/nginx/sites-available/ 目錄中文件的符號鏈接。

實際上有一個示例配置:

ln -lh /etc/nginx/sites-enabled/# lrwxrwxrwx 1 root root 34 Apr 25 08:33 default -> /etc/nginx/sites-available/default

如你所見,該目錄包含指向 /etc/nginx/sites-available/default 文件的符號鏈接。

思路是在 /etc/nginx/sites-available/ 目錄中寫入多個虛擬主機,並通過給他們創建到 /etc/nginx/sites-enabled/ 目錄的符號鏈接來激活它。

爲了演示這個概念,讓我們配置一個簡單的靜態服務器。首先,刪除默認的虛擬主機符號鏈接,在進程中停用這個配置:

sudo rm /etc/nginx/sites-enabled/defaultls -lh /etc/nginx/sites-enabled/# lrwxrwxrwx 1 root root 41 Apr 25 18:01 nginx-handbook -> /etc/nginx/sites-available/nginx-handbook

通過執行 sudo touch /etc/nginx/sites-available/nginx-handbook 創建一個新文件,並將輸入以下內容:

server {    listen 80;    server_name nginx-handbook.test;    root /srv/nginx-handbook-projects/static-demo;}

/etc/nginx/sites-available/ 目錄中的文件包含在 main http 上下文中,因此它們應該只包含 server 塊。

現在通過執行以下命令在 /etc/nginx/sites-enabled/ 目錄中創建一個指向此文件的符號鏈接:

sudo ln -s /etc/nginx/sites-available/nginx-handbook /etc/nginx/sites-enabled/nginx-handbookls -lh /etc/nginx/sites-enabled/# lrwxrwxrwx 1 root root 34 Apr 25 08:33 default -> /etc/nginx/sites-available/default# lrwxrwxrwx 1 root root 41 Apr 25 18:01 nginx-handbook -> /etc/nginx/sites-available/nginx-handbook

在驗證和重新加載配置文件之前,必須重新打開日誌文件。否則,可能會收到權限拒絕錯誤。發生這種情況的原因是因爲這次交換舊的 nginx.conf 文件導致進程 ID 不同。

sudo rm /var/log/nginx/*.logsudo touch /var/log/nginx/access.log /var/log/nginx/error.logsudo nginx -s reopen

最後,驗證並重新加載配置文件:

sudo nginx -t# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok# nginx: configuration file /etc/nginx/nginx.conf test is successfulsudo nginx -s reload

訪問服務器,應該會看到最初的 The NGINX 手冊頁面:

如果已經正確配置了服務器並且仍然看到舊的 NGINX 歡迎頁面,請執行硬刷新。瀏覽器通常會緩存舊的靜態資源,需要做進行一些清理。

高級 NGINX 概念系列

服務器配置是一個很大的話題,本文的目的是讓你瞭解 NGINX 的基礎知識。還有一些重要和高級的主題沒講。

我計劃在我的博客上寫一些文章來解釋諸如配置 HTTP2 協議、FastCGI 微緩存、速率限制、SSL 證書籤名、動態模塊等主題。

這樣,該系列將成爲易於參考且面向對基礎有適當瞭解的人的文章集合。

所以請密切關注 https://farhan.info/ 。我希望在 2 或 3 周內發佈第一篇文章。

表達你的支持

除了這本手冊之外,我還編寫了一些複雜主題的手冊,例如 Docker 容器化和 Kubernetes 的服務器編排都可以在 freeCodeCamp 專欄上免費獲得。

這些手冊是我以簡化晦澀技術爲使命的成果。每本手冊都需要花費大量的時間和精力來編寫。

如果你喜歡我的寫作並想讓我保持動力,可以考慮在 GitHub 上 star,並在 [LinkedIn](https://www. linkedin.com/in/farhanhasin/) 認可我的相關技能。

我也願意接受建議和討論。在 Twitter 上關注我,並通過社交軟件或電子郵件聯繫我。

最後,考慮與他人分享資源,因爲

分享知識是友誼最基本的行爲。因爲這是一種你可以給予一些東西而不會失去一些東西的方式。— 理查德 · 斯托曼

尾聲

我衷心感謝你花時間閱讀本文。我希望你享受你的學習時間並學習了 NGINX 的所有基本知識。

如果你喜歡我的作品,你可以在 https://www.freecodecamp.org/news/author/farhanhasin/ 上找到我的其他書籍,個人博客  https://www.farhan.info/ 同步更新。

你可以在 Twitter 上關注我 @frhnhsin 或在 LinkedIn 上與我聯繫 /in/farhanhasin。

原文鏈接:https://www.freecodecamp.org/news/the-nginx-handbook/

作者:Farhan Hasin Chowdhury

譯者:ZhichengChen

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7lM0pzBEHxErynNNRb1v5Q