1. 程式人生 > >使用 Let's Encrypt 和 Nginx 從同一伺服器託管多個 HTTPS 域名

使用 Let's Encrypt 和 Nginx 從同一伺服器託管多個 HTTPS 域名

現在網站越來越需要 HTTPS,而這正是順應了發展趨勢。Chrome 現已將帶有密碼或信用卡欄位的 HTTP 網站明確標記為“不安全的”。在過去的一年裡,我一直在將我的客戶端網站切換到 HTTPS 上。事實證明,作為系統管理員的工作總是這樣做,也存在與此相關的隱藏的挑戰。

HTTPS 簡述

在我們深入下面的細節之前,我們先弄清寫這篇文章的目的。

從同一伺服器上託管多個 HTTPS 網站的問題與 HTTPS 連線的建立方式有關。安全的(讀取:不是pwned)HTTPS 是在 TLS 上執行。當 TLS 連線建立時,我們傾向於認為它是與網站構建的。在技術上這是不正確的。 TLS 連線是連線到特定的 IP 地址。一旦你連線到那個 IP,伺服器會假定這就是你想訪問的網站。這意味著您不能在同一 IP 地址的主機上託管的多個 HTTPS 網站。

進入(和退出)SNI

SNI(伺服器名稱標識)是一項在 21 世紀初期就被引入的協議,引入的目的就是為了解決這個問題。它是一種用於在瀏覽器設定一個 TLS 連線時向網頁伺服器傳送一個域名的方法。通過在初次握手階段傳入域名,網頁伺服器就可以知道你想要訪問的是哪一個域,從而讓其為正確的那一個提供服務。

不過儘管其已經得到了廣泛的支援,但也有不被支援的情況。我自己就曾在處理對 SNI 的依賴時掉進了移動端瀏覽器的坑。從另外一方面來看,IP 地址是廉價的,好像是 1 $/月,或者還要更便宜。因此你索性可以為你的 HTTPS 站點單獨弄一個 IP,這樣做所避免掉的一些在終端/瀏覽器組合上遇到的頭疼事兒,花費的代價可能百倍不止。

新增 IP 地址

對於初學者而言,首先需要給每一個你準備從同一臺伺服器上託管在 HTTPS 上的域名獲取一個 IP。大多數像 Linode 以及 AWS 這樣的託管平臺都提供了為任何東西新增 IP 的能力。

一旦你有那些 IP,你就需要確認你的 web 伺服器識別輸入他們。如果你開始執行 ping,你就可能看到下面的輸出:
$ ping you.new.ip.addr
PING you.new.ip.addr (you.new.ip.addr): 56 data bytes  
Request timeout for icmp_seq 0  Request timeout for icmp_seq 1  
...
這是因為伺服器忽略了沒有配置的 IP 地址。要讓伺服器看到那些輸入的 IP,我們需要更新網路介面。這聽起來很可怕,但實際上並沒有那麼壞。我們開始編輯 /etc/network/interfaces 這個檔案。 你大概會有一個龐大的清單是關於你的伺服器所需要儲存的 IP 地址的。
# Something like this probably already exists
auto eth0  
iface eth0 inet static  
    address X.X.X.X/24
    gateway X.X.X.1
    dns-nameservers 8.8.8.8 8.8.4.4
    dns-search your.dns.info
    dns-options rotate
這裡,我們將會在 eth0 塊下,新增新 IP 地址。
# Add support for our new IP addresses
iface eth0:1 inet static  
    address Y.Y.Y.Y/24
    dns-nameservers 8.8.8.8 8.8.4.4

iface eth0:2 inet static  
    address Z.Z.Z.Z/24
    dns-nameservers 8.8.8.8 8.8.4.4

如何獲得證書

儘管我在之前已經說的很詳細了,但是在這裡我還是想再提一點。使用 Let's Encrypt,我們有幾種選擇來取得證書。

# First we need the Let's Encrypt bin
sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
有了 Let's EncryptOnce 軟體包之後,我們可以用以下幾種方法註冊我們的域名。
# Get a cert for a domain (include all subdomains that apply to this file path, including www)
/opt/letsencrypt/letsencrypt-auto certonly --webroot \
  -d 'mysite.com,www.mysite.com' -w /var/www/mysite/public 

# Get a cert for a domain and subdomain with different filesystem paths 
/opt/letsencrypt/letsencrypt-auto certonly --webroot \
  -d 'mysite.com,www.mysite.com' -w /var/www/mysite/public \
  -d 'blog.mysite.com' -w /var/www/mysite_blog/ 

# Get a cert for an entirely new domain name
/opt/letsencrypt/letsencrypt-auto certonly --webroot \
  -d 'newsite.com,www.newsite.com' -w /var/www/newsite/public

更新 nginx 伺服器

接下來,我們需要做一點 HTTPS 配置的修改,目的是使 nginx 從 域名+埠 認證方法切換到一種 ip+埠 的認證系統。這是在我們 HTTPS 伺服器模組中改變監聽(listen)指令最簡單的配置方式 (並且在我的使用中一直沒有問題) 。

server {  
    # Old method:
    # listen          443 ssl; 

    # Ip-based:
    listen          X.X.X.X:443 ssl;
    server_name:    mysite.com www.mysite.com;

    # ...
}

自動續期證書

最後,當我們在伺服器上維護多個站點時,我們真的應該依賴某種方法自動更新我們的 Let's Encrypt 證書。沒人願意每三個月去手動更新一次。很幸運,Let's Encrypt 官方提供了一段程式碼來處理這個問題 ,而我們只需要建立一個計劃任務,採用 cron 定時任務命令定期自動執行(這段程式碼) 。現在,我們建立一個檔案  /etc/letsencrypt/auto_renew.sh。把下面的程式碼新增進來,你也可以自己做些修改,實現你想要的功能。

#!/bin/sh
# This script renews all the Let's Encrypt certificates with a validity < 30 days

if ! /opt/letsencrypt/letsencrypt-auto renew > /var/log/letsencrypt/renew.log 2>&1 ; then  
    echo Automated renewal failed:
    cat /var/log/letsencrypt/renew.log
    exit 1
fi  
nginx -t && nginx -s reload

現在將以上的指令碼新增到計劃任務, 執行 sudo crontab -e 新增下面一行程式碼:
@daily  /etc/letsencrypt/auto_renew.sh

隱私和安全問題

技術革新領域正在飛速發展,儘管這種發展在使用者的隱私和安全方面並無助益。雖然沒有明確言明,但是使用者變成客戶的過程在互動中已經隱含了雙方的信任。不管是作為開發人員、工程師還是系統管理員,我們都有責任保障使用者權益。

保障使用者用 app 發出的任何連線都安全可靠是最佳方案。顯然,沒有一定程度上安全的連線難以輕鬆實現。Let's Encrypt 巧妙降低了進入開發領域的壁壘,成為開發人員亟需掌握的一步。由此可知,所有以避免為客戶網站提供安全支援為目的的藉口都是疏忽和無知。

希望本文對解決問題有所助益。

總結:如果你對從一個伺服器託管多個 HTTPS 域名一無所知,使用 Let's Encrypt 和 Nginx 吧!輕鬆解決問題。