1. 程式人生 > 實用技巧 >【轉】關於Nginx 配置 HTTPS 伺服器

【轉】關於Nginx 配置 HTTPS 伺服器

本文轉自:凹凸實驗室

Nginx 配置 HTTPS 伺服器

Chrome 瀏覽器位址列標誌著 HTTPS 的綠色小鎖頭從心理層面上可以給使用者專業安全的心理暗示,本文簡單總結一下如何在 Nginx 配置 HTTPS 伺服器,讓自己站點上『綠鎖』。

Nginx 配置 HTTPS 並不複雜,主要有兩個步驟:簽署第三方可信任的 SSL 證書和配置 HTTPS

簽署第三方可信任的 SSL 證書

關於 SSL 證書

有關 SSL 的介紹可以參閱維基百科的傳輸層安全協議和阮一峰先生的《SSL/TLS協議執行機制的概述》

SSL 證書主要有兩個功能:加密和身份證明,通常需要購買,也有免費的,通過第三方 SSL 證書機構頒發,常見可靠的第三方 SSL 證書頒發機構有下面幾個:

StartCom機構上的 SSL 證書有以下幾種:

  • 企業級別:EV(Extended Validation)、OV(Organization Validation)
  • 個人級別:IV(Identity Validation)、DV(Domain Validation)

其中 EV、OV、IV 需要付費

免費的證書安全認證級別一般比較低,不顯示單位名稱,不能證明網站的真實身份,僅起到加密傳輸資訊的作用,適合個人網站或非電商網站。由於此類只驗證域名所有權的低端 SSL 證書已經被國外各種欺詐網站濫用,因此強烈推薦部署驗證單位資訊並顯示單位名稱的 OV SSL 證書或申請最高信任級別的、顯示綠色位址列、直接在位址列顯示單位名稱的 EV SSL 證書,就好像

StarCom的位址列一樣:

更多關於購買 SSL 證書的介紹:SSL 證書服務,大家用哪家的?DV免費SSL證書

使用 OpenSSL 生成 SSL Key 和 CSR 檔案

配置 HTTPS 要用到私鑰 example.key 檔案和 example.crt 證書檔案,申請證書檔案的時候要用到 example.csr 檔案,OpenSSL命令可以生成 example.key 檔案和 example.csr 證書檔案。

  • CSR:Cerificate Signing Request,證書籤署請求檔案,裡面包含申請者的 DN(Distinguished Name,標識名)和公鑰資訊,在第三方證書頒發機構簽署證書的時候需要提供。證書頒發機構拿到 CSR 後使用其根證書私鑰對證書進行加密並生成 CRT 證書檔案,裡面包含證書加密資訊以及申請者的 DN 及公鑰資訊
  • Key:證書申請者私鑰檔案,和證書裡面的公鑰配對使用,在 HTTPS 『握手』通訊過程需要使用私鑰去解密客戶端發來的經過證書公鑰加密的隨機數資訊,是 HTTPS 加密通訊過程非常重要的檔案,在配置 HTTPS 的時候要用到

使用OpenSSl命令可以在系統當前目錄生成example.key和example.csr檔案:

1
openssl req -new -newkey rsa:2048 -sha256 -nodes -out example_com.csr -keyout example_com.key -subj "/C=CN/ST=ShenZhen/L=ShenZhen/O=Example Inc./OU=Web Security/CN=example.com"

下面是上述命令相關欄位含義:

  • C:Country ,單位所在國家,為兩位數的國家縮寫,如: CN 就是中國
  • ST 欄位: State/Province ,單位所在州或省
  • L 欄位: Locality ,單位所在城市 / 或縣區
  • O 欄位: Organization ,此網站的單位名稱;
  • OU 欄位: Organization Unit,下屬部門名稱;也常常用於顯示其他證書相關資訊,如證書型別,證書產品名稱或身份驗證型別或驗證內容等;
  • CN 欄位: Common Name ,網站的域名;

生成 csr 檔案後,提供給 CA 機構,簽署成功後,就會得到一個example.crt證書檔案,SSL 證書檔案獲得後,就可以在 Nginx 配置檔案裡配置 HTTPS 了。

配置 HTTPS

基礎配置

要開啟 HTTPS 服務,在配置檔案資訊塊(server block),必須使用監聽命令listen的 ssl 引數和定義伺服器證書檔案和私鑰檔案,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
server {
#ssl引數
listen 443 ssl;
server_name example.com;
#證書檔案
ssl_certificate example.com.crt;
#私鑰檔案
ssl_certificate_key example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}

證書檔案會作為公用實體傳送到每臺連線到伺服器的客戶端,私鑰檔案作為安全實體,應該被存放在具有一定許可權限制的目錄檔案,並保證 Nginx 主程序有存取許可權。

私鑰檔案也有可能會和證書檔案同放在一個檔案中,如下面情況:

1
2
ssl_certificate     www.example.com.cert;
ssl_certificate_key www.example.com.cert;

這種情況下,證書檔案的的讀取許可權也應該加以限制,僅管證書和私鑰存放在同一個檔案裡,但是隻有證書會被髮送到客戶端

命令ssl_protocolsssl_ciphers可以用來限制連線只包含 SSL/TLS 的加強版本和演算法,預設值如下:

1
2
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;

由於這兩個命令的預設值已經好幾次發生了改變,因此不建議顯性定義,除非有需要額外定義的值,如定義 D-H 演算法:

1
2
3
4
5
6
#使用DH檔案
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#定義演算法
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
#...

HTTPS伺服器優化

減少 CPU 運算量

SSL 的執行計算需要消耗額外的 CPU 資源,一般多核處理器系統會執行多個工作程序(worker processes),程序的數量不會少於可用的 CPU 核數。SSL 通訊過程中『握手』階段的運算最佔用 CPU 資源,有兩個方法可以減少每臺客戶端的運算量:

  • 啟用keepalive長連線,一個連線傳送更多個請求
  • 複用 SSL 會話引數,在並行併發的連線數中避免進行多次 SSL『握手』

這些會話會儲存在一個 SSL 會話快取裡面,通過命令ssl_session_cache配置,可以使快取在機器間共享,然後利用客戶端在『握手』階段使用的seesion id去查詢服務端的 session cathe(如果服務端設定有的話),簡化『握手』階段。

1M 的會話快取大概包含 4000 個會話,預設的快取超時時間為 5 分鐘,可以通過使用ssl_session_timeout命令設定快取超時時間。下面是一個擁有 10M 共享會話快取的多核系統優化配置例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
worker_processes auto;

http {
#配置共享會話快取大小
ssl_session_cache shared:SSL:10m;
#配置會話超時時間
ssl_session_timeout 10m;

server {
listen 443 ssl;
server_name www.example.com;
#設定長連線
keepalive_timeout 70;

ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...

使用 HSTS 策略強制瀏覽器使用 HTTPS 連線

HSTS – HTTP Strict Transport Security,HTTP嚴格傳輸安全。它允許一個 HTTPS 網站要求瀏覽器總是通過 HTTPS 來訪問,這使得攻擊者在使用者與伺服器通訊過程中攔截、篡改資訊以及冒充身份變得更為困難。

只要在 Nginx 配置檔案加上以下頭資訊就可以了:

1
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains;preload" always;
  • max-age:設定單位時間內強制使用 HTTPS 連線
  • includeSubDomains:可選,所有子域同時生效
  • preload:可選,非規範值,用於定義使用『HSTS 預載入列表』
  • always:可選,保證所有響應都發送此響應頭,包括各種內建錯誤響應

當用戶進行 HTTPS 連線的時候,伺服器會發送一個Strict-Transport-Security響應頭:

瀏覽器在獲取該響應頭後,在max-age的時間內,如果遇到 HTTP 連線,就會通過 307 跳轉強制使用 HTTPS 進行連線,並忽略其它的跳轉設定(如 301 重定向跳轉):

307 跳轉Non-Authoritative-Reason響應頭

Google HSTS 預載入列表(HSTS Preload List)

由於 HSTS 需要使用者經過一次安全的 HTTPS 連線後才會在 max-age 的時間內生效,因此HSTS 策略並不能完美防止HTTP 會話劫持(HTTP session hijacking),在下面這些情況下還是存在被劫持的可能:

  • 從未訪問過的網站
  • 近期重灌過作業系統
  • 近期重灌過瀏覽器
  • 使用新的瀏覽器
  • 使用了新的裝置(如手機)
  • 刪除了瀏覽器快取
  • 近期沒有開啟過網站且 max-age 過期

針對這種情況,Google 維護了一份『HSTS 預載入列表』,列表裡包含了使用了 HSTS 的站點主域名和子域名,可以通過以下頁面申請加入:

https://hstspreload.appspot.com/.

申請的時候會先驗證站點是否符合資格,一般會檢驗待驗證的站點主域和子域是否能通過 HTTPS 連線、HTTPS 和 HTTP 配置是否有 STS Header 等資訊,通過驗證後,會讓你確認一些限制資訊,如下圖:

當確認提交後,就會顯示處理狀態:

申請通過後,列表內的站點名會被寫進主流的瀏覽器,當瀏覽器更新版本後,只要開啟列表內的站點,瀏覽器會拒絕所有 HTTP 連線而自動使用 HTTPS,即使關閉了 HSTS 設定。

可以在下面兩個連線分別查詢 Chrome 和 Firfox 的『HSTS 預載入列表』內容:

The Chromium Projects - HTTP Strict Transport Security

Firefox HSTS preload list - nsSTSPreloadList.inc

需要注意的是:

  • 一旦把自己的站點名加入『HSTS 預載入列表』,將很難徹底從列表中移除,因為不能保證其它瀏覽器可以及時移除,即使 Chrome 提供有便捷的移除方法,也是要通過出郵件聯絡,註明移除原因,並等到最新的瀏覽器版本更新發布才有機會(使用者不一定會及時更新)
  • 所有不具備有效證書的子域或內嵌子域的訪問將會被阻止

因此,如果自己站點子域名變化比較多,又沒有泛域證書,又沒法確定全站是否能應用 HTTPS 的朋友,就要謹慎申請了。

更多關於 HSTS 配置可參考:

《HTTP Strict Transport Security (HSTS) and NGINX》

MDN的《HTTP Strict Transport Security》

瀏覽器相容性

加強 HTTPS 安全性

HTTPS 基礎配置採取的預設加密演算法是 SHA-1,這個演算法非常脆弱,安全性在逐年降低,在 2014 年的時候,Google 官方部落格就宣佈在 Chrome 瀏覽器中逐漸降低 SHA-1 證書的安全指示,會從 2015 年起使用 SHA-2 簽名的證書,可參閱Rabbit_Run在 2014 年發表的文章:《為什麼Google急著殺死加密演算法SHA-1》

為此,主流的 HTTPS 配置方案應該避免 SHA-1,可以使用迪菲-赫爾曼金鑰交換(D-H,Diffie–Hellman key exchange)方案。

首先在目錄/etc/ssl/certs執行以下程式碼生成dhparam.pem檔案:

1
openssl dhparam -out dhparam.pem 2048

然後加入 Nginx 配置:

1
2
3
4
5
6
7
#優先採取伺服器演算法
ssl_prefer_server_ciphers on;
#使用DH檔案
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#定義演算法
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";

如果伺服器夠強大,可以使用更為複雜的 4096 位進行加密。

一般情況下還應該加上以下幾個增強安全性的命令:

1
2
3
4
5
6
#減少點選劫持
add_header X-Frame-Options DENY;
#禁止伺服器自動解析資源型別
add_header X-Content-Type-Options nosniff;
#防XSS攻擊
add_header X-Xss-Protection 1;

這幾個安全命令在Jerry Qu大神的文章《一些安全相關的HTTP響應頭》有詳細的介紹。

優化後的綜合配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
worker_processes auto;

http {

#配置共享會話快取大小,視站點訪問情況設定
ssl_session_cache shared:SSL:10m;
#配置會話超時時間
ssl_session_timeout 10m;

server {
listen 443 ssl;
server_name www.example.com;

#設定長連線
keepalive_timeout 70;

#HSTS策略
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

#證書檔案
ssl_certificate www.example.com.crt;
#私鑰檔案
ssl_certificate_key www.example.com.key;

#優先採取伺服器演算法
ssl_prefer_server_ciphers on;
#使用DH檔案
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
#定義演算法
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
#減少點選劫持
add_header X-Frame-Options DENY;
#禁止伺服器自動解析資源型別
add_header X-Content-Type-Options nosniff;
#防XSS攻擊
add_header X-Xss-Protection 1;
#...

HTTP/HTTPS混合伺服器配置

可以同時配置 HTTP 和 HTTPS 伺服器:

1
2
3
4
5
6
7
8
server {
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
#...
}

在 0.7.14 版本之前,在獨立的 server 埠中是不能選擇性開啟 SSL 的。如上面的例子,SSL 只能通過使用ssl命令為單個 server 埠開啟

1
2
3
4
5
6
7
8
9
server {
listen 443;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
#ssl命令開啟 https
ssl on;
#...
}

因此沒有辦法設定 HTTP/HTTPS 混合伺服器。於是 Nginx 新增了監聽命令listen引數ssl來解決這個問題,Nginx 現代版本的ssl命令並不推薦使用

基於伺服器名稱(name-based)的 HTTPS 伺服器

一個常見的問題就是當使用同一個 IP 地址去配置兩個或更多的 HTTPS 伺服器的時候,出現證書不匹配的情況:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}

server {
listen 443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}

這種情況下瀏覽器會獲取預設的伺服器證書(如上面例子的www.example.com.crt)而忽視請求的伺服器名,如輸入網址:`www.example.org`,伺服器會發送www.example.com.crt的證書到客戶端,而不是www.exaple.org.crt

這是因為 SSL 協議行為所致,SSL 連線在瀏覽器傳送 HTTP 請求之前就被建立,Nginx 並不知道被請求的伺服器名字,因此 Nginx 只會提供預設的伺服器證書。

解決這個問題最原始最有效的方法就是為每個 HTTPS 伺服器分配獨立的 IP 地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 192.168.1.1:443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}

server {
listen 192.168.1.2:443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}

更多解決方案

除此之外,官方還介紹了兩個方法:泛域證書和域名指示(SNI)

其實 OpenSSL 在0.9.8f版本就支援 SNI 了,只要在安裝的時候加上--enable-tlsext選項就可以。到了0.9.8j版本,這個選項在安裝的時候會預設啟用。如果建立 Nginx 的時候支援 SNI,可以在 Nginx 版本資訊查到以下的欄位:

1
TLS SNI support enabled

因此,如果較新版本的 Nginx 使用預設的 OpenSSL 庫,是不存在使用 HTTPS 同時支援基於名字的虛擬主機的時候同 IP 不同域名證書不匹配的問題。

注意:即使新版本的 Nginx 在建立時支援了 SNI,如果 Nginx 動態載入不支援 SNI 的 OpenSSL 庫的話,SNI 擴充套件將不可用

有興趣的朋友可以看下:

An SSL certificate with several names && Server Name Indication

總結

OK,我們簡單總結一下在 Nginx 下配置 HTTPS 的關鍵要點:

  • 獲得 SSL 證書

    • 通過 OpenSSL 命令獲得 example.key 和 example.csr 檔案
    • 提供 example.csr 檔案給第三方可靠證書頒發機構,選擇適合的安全級別證書並簽署,獲得 example.crt 檔案
  • 通過 listen 命令 SSL 引數以及引用 example.key 和 example.crt 檔案完成 HTTPS 基礎配置

  • HTTPS優化

    • 減少 CPU 運算量
      • 使用 keepalive 長連線
      • 複用 SSL 會話引數
    • 使用 HSTS 策略強制瀏覽器使用 HTTPS 連線
      • 新增 Strict-Transport-Security 頭部資訊
      • 使用 HSTS 預載入列表(HSTS Preload List)
    • 加強 HTTPS 安全性
      • 使用迪菲-赫爾曼金鑰交換(D-H,Diffie–Hellman key exchange)方案
      • 新增 X-Frame-Options 頭部資訊,減少點選劫持
      • 新增 X-Content-Type-Options 頭部資訊,禁止伺服器自動解析資源型別
      • 新增 X-Xss-Protection 頭部資訊,防XSS攻擊
  • HTTP/HTTPS混合伺服器配置

  • 基於伺服器名稱(name-based)的 HTTPS 伺服器

    • 為每個 HTTPS 伺服器分配獨立的 IP 地址
    • 泛域證書
    • 域名標識(SNI)