跳至主要内容

Nginx HTTPS 設定

在現代網際網路環境中,網站安全性已成為不可忽視的議題,而 SSL/TLS 憑證則是保護網站與用戶之間傳輸數據的關鍵技術。

Let's Encrypt 是一個免費、自動化且開放的憑證頒發機構(Certificate Authority, CA),為全球數百萬網站提供了加密保護,使 HTTPS 普及化變得更加容易。

既然它免費,那我們肯定得捧個場,對吧?

因此我們採用 Let's Encrypt 在 Nginx 伺服器上配置 HTTPS。

本章節要完成的目標包括:

  • 取得 SSL 憑證:使用 Let's Encrypt 的免費憑證來加密網站流量。
  • 強制 HTTPS 傳輸:將所有使用者的 HTTP 請求自動重定向至 HTTPS(301 永久轉址)。
  • Nginx 反向代理:在 Nginx 作為反向代理的架構下,確保前端 SSL 正常運作。
  • 自動續約:設定憑證自動續期機制,避免 90 天有效期的憑證過期失效。
提示

我們在上一章節中簡單介紹了 Let's Encrypt 這個工具的優缺點。

如果你對它不太熟,可以先去看看我們的補充說明文件:Let's Encrypt

Let's Encrypt 安裝

我們採用 Let's Encrypt 的 ACME 協議來獲取 SSL 憑證。

首先,我們需要安裝 Let’s Encrypt 推薦的 ACME 用戶端 Certbot。Certbot 可以自動與 Let's Encrypt 交互,申請和續約憑證。

以 Debian/Ubuntu 為例,可直接透過套件庫安裝:

sudo apt update
sudo apt install -y certbot python3-certbot-nginx

上述命令將安裝 Certbot 以及其 Nginx 外掛模組,使 Certbot 可以自動修改 Nginx 設定檔完成憑證部署。

在進行下一步之前,請確保你已經完成以下準備工作:

  • 擁有一個網域名稱,並將其 DNS 記錄指向你的伺服器 IP。
  • 開放防火牆的 80 和 443 埠,讓 HTTP/HTTPS 流量可以進入以進行驗證和後續訪問。

申請 Let's Encrypt 憑證

有了 Certbot,我們可以向 Let's Encrypt 申請憑證。

Let's Encrypt 採用自動化的 ACME 驗證機制,會要求你證明對網域的控制權。常用的驗證方式是透過 HTTP 驗證(Certbot 會在你網站上放置一個臨時檔案供 Let's Encrypt 驗證)或 DNS 驗證(較複雜,用於通配符憑證申請)。

這裡我們使用簡單的 HTTP 驗證並透過 Nginx 外掛讓 Certbot 自動配置:

# 停止 Nginx(若使用 webroot 模式可能需要,但使用 --nginx 外掛通常不需要手動停止)
# sudo systemctl stop nginx

# 執行 Certbot,使用 Nginx 外掛申請憑證
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

將其中的 your-domain.comwww.your-domain.com 替換為實際的網域名稱。

執行後,Certbot 會與 Let’s Encrypt 伺服器通訊:

  • 驗證網域:Certbot 可能會臨時修改 Nginx 設定或啟動一個臨時的服務,以回答 Let's Encrypt 的驗證請求。例如,Let's Encrypt 伺服器將嘗試透過 http://your-domain.com/.well-known/acme-challenge/ 路徑存取驗證檔案。如果驗證成功,表示你對該網域有控制權。
  • 取得憑證:驗證通過後,Let's Encrypt 會簽發 SSL 憑證(包含完整憑證鏈 fullchain.pem 和私鑰檔案 privkey.pem)。Certbot 會將這些檔案儲存至預設路徑(通常在 /etc/letsencrypt/live/your-domain.com/)。

在執行過程中,Certbot 可能會詢問:

  • 是否同意服務條款、提供電子郵件(用於續約通知)。
  • 是否要將 HTTP 流量自動轉向 HTTPS。建議選擇轉向 (redirect),Certbot 將自動為你設定 301 重定向規則,確保使用者以 HTTPS 存取。
提示

如果沒有使用 --nginx 外掛,亦可使用 certbot certonly --webroot 模式來手動取得憑證,然後自行編輯 Nginx 設定。但使用 --nginx 可省去手動設定的步驟。

配置 Nginx 以啟用 HTTPS

憑證簽發完成後,需要在 Nginx 中開啟 HTTPS(SSL)並載入剛才取得的憑證。

以下是範例設定(假設使用預設安裝路徑的 Certbot):

# 在 Nginx 的設定檔(例如 /etc/nginx/sites-available/your-domain.conf)中加入:

# 80 埠的服務,將所有請求轉址到 HTTPS
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
# 將 HTTP 請求永久轉換為 HTTPS
return 301 https://$host$request_uri;
}

# 443 埠的服務,提供 HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your-domain.com www.your-domain.com;

# SSL 憑證檔案路徑(由 Certbot 簽發)
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

# (以下為反向代理設定範例)
location / {
proxy_pass http://127.0.0.1:8000; # 將請求轉發給後端應用(例如在本機埠 8000 執行的 FastAPI)
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_intercept_errors on;
}
}

每個部分的設定說明如下:

  • 80 埠的 HTTP 服務(強制轉向 HTTPS)

    server {
    listen 80;
    listen [::]:80;
    server_name your-domain.com www.your-domain.com;
    # 將 HTTP 請求永久轉換為 HTTPS
    return 301 https://$host$request_uri;
    }

    這個區塊的作用:

    1. 監聽 HTTP(80 埠)

      • listen 80;:讓 Nginx 在 IPv4 監聽 80 埠(標準 HTTP 連線)。
      • listen [::]:80;:允許 IPv6 連線。
    2. 設定 server_name

      • server_name your-domain.com www.your-domain.com;
      • 這告訴 Nginx 此配置適用於 your-domain.comwww.your-domain.com
      • 這個設定可確保 www 版與非 www 版的流量都能被正確處理。
    3. 強制將 HTTP 轉向 HTTPS

      • return 301 https://$host$request_uri;
      • 這會讓所有 HTTP 請求透過 301 永久重定向,自動轉向 HTTPS。
      • $host 代表請求的主機(your-domain.comwww.your-domain.com)。
      • $request_uri 代表請求的完整 URI(例如 /about)。

  • 443 埠的 HTTPS 服務

    server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your-domain.com www.your-domain.com;

    這個區塊的作用:

    1. 監聽 HTTPS(443 埠)

      • listen 443 ssl http2;
      • ssl:啟用 SSL 加密
      • http2:啟用 HTTP/2,提升效能(減少連線延遲)
      • listen [::]:443 ssl http2;:允許 IPv6 使用 HTTPS
    2. 設定 server_name

      • 與 HTTP 配置一致,適用於 your-domain.comwww.your-domain.com

  • SSL 憑證設定

        # SSL 憑證檔案路徑(由 Certbot 簽發)
    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    這個區塊的作用:

    • 這些是 Let's Encrypt 自動生成的憑證路徑:
      • fullchain.pem:完整憑證(包含中繼 CA 憑證)
      • privkey.pem:私鑰(用於 SSL 加密)

  • 反向代理 FastAPI

        location / {
    proxy_pass http://127.0.0.1:8000; # 將請求轉發給後端應用(FastAPI)
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # 讓 Nginx 處理 FastAPI 回應的錯誤
    proxy_intercept_errors on;
    }

    這個區塊的作用:

    • proxy_pass http://127.0.0.1:8000;

      • 假設 FastAPI 在 127.0.0.1:8000 執行(啟動指令為 uvicorn --host 127.0.0.1 --port 8000
      • 透過 proxy_pass 將 API 請求轉發到本機的 FastAPI 應用
    • 代理請求標頭

      • proxy_set_header Host $host;:保留原請求的 Host 標頭
      • proxy_set_header X-Real-IP $remote_addr;:將真實 IP 傳給後端
      • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;:記錄整個代理鏈
      • proxy_set_header X-Forwarded-Proto $scheme;:確保後端知道原始請求的協議(HTTP/HTTPS)
    • 錯誤處理

      • proxy_intercept_errors on;
        • 讓 Nginx 攔截後端的錯誤(如 502、503)
        • 可用 error_page 指定自訂錯誤頁面

設定完成後,重新載入 Nginx 使新配置生效:

sudo nginx -t  # 測試 Nginx 設定語法是否正確
sudo systemctl reload nginx # 套用配置變更,重新載入服務

Nginx 反向代理與後端服務

在上述架構中,Nginx 同時擔任 HTTPS 端點和反向代理(Reverse Proxy)的角色。

Nginx 負責處理 TLS 握手與加解密,即 SSL 終止發生在 Nginx 層,後端應用只需處理來自 Nginx 的 HTTP 請求,而無需支援 HTTPS。本機內部通訊可保持輕量化,不需處理額外的加密負擔。

這種做法可簡化後端應用的設定,減少 HTTPS 相關的維護工作。

此外,在使用 Nginx 作為反向代理時,後端應用預設無法直接取得用戶端的真實 IP,而是只會看到 Nginx 伺服器的 IP。

也正因如此,需要透過 proxy_set_header 將重要的 HTTP 標頭傳遞給後端應用,以確保後端能正確辨識用戶端資訊。

關鍵標頭包括:

  • X-Forwarded-For → 傳遞用戶端的原始 IP 地址
  • Host → 保留原始請求的主機名稱
  • X-Forwarded-Proto → 指示原始請求使用的協議(httphttps

Nginx 配置:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

假設我們的後端是使用 Python 開發的 FastAPI 應用,那麼 FastAPI 預設不會解析代理傳遞的標頭,因此需要啟用 proxy-headers 模式,以正確辨識 X-Forwarded-For 等資訊:

uvicorn app:app --host 127.0.0.1 --port 8000 --proxy-headers

或者,在 FastAPI 內部使用 StarletteMiddleware.ProxyHeaders

from fastapi import FastAPI
from starlette.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.proxy_headers import ProxyHeadersMiddleware

app = FastAPI()

# 啟用代理標頭解析
app.add_middleware(ProxyHeadersMiddleware)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["*"])

這樣後端應用即可獲取用戶端的真實 IP、請求協議等資訊,避免誤判。

最後,為了確保安全性,FastAPI 應僅監聽本機介面,避免直接對外開放 HTTP 服務,減少未經授權存取的風險。

最佳做法是:

  • FastAPI 監聽本機埠
    uvicorn app:app --host 127.0.0.1 --port 8000
  • Nginx proxy_pass 應指向本機埠
    location / {
    proxy_pass http://127.0.0.1:8000;
    }

這樣外部用戶無法直接存取後端 FastAPI 應用,只能透過 Nginx 代理存取,提升安全性;內部流量維持非加密 HTTP,減少 HTTPS 帶來的額外負擔,提升效能。

設定憑證自動續約

Let's Encrypt 的憑證有效期僅 90 天,這樣的設計是為了提高安全性,同時促使網站管理員實施自動化的續約機制。手動續約可能導致憑證過期,影響網站的 HTTPS 連線,因此推薦使用 Certbot 自動續約來確保憑證始終有效。

Certbot 的自動續約機制

Certbot 會根據系統的初始化系統(init system)使用不同的自動續約方式:

對於 systemd 系統(大多數現代 Linux 版本)Certbot 會使用 systemd timer 來管理續約,而不執行 cron 任務。

你可以檢查 systemd timer 是否已啟用:

systemctl list-timers | grep certbot

如果啟用,應該會看到類似:

certbot.timer               2025-02-13 02:00:00 UTC   12h left

你也可以手動檢查 systemd timer 的狀態:

systemctl status certbot.timer

systemd 會根據 certbot.timer 設定,每天執行 2 次續約檢查。只有當憑證到期前 30 天內 才會執行真正的續約。


對於非 systemd 系統 Certbot 會使用 cron 任務,預設存放於 /etc/cron.d/certbot

系統內 cron 設定可能類似:

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --no-random-sleep-on-renew

這代表每 12 小時執行一次。若系統使用 systemd,則此 cron 任務不會執行。

此外,為了避免伺服器在同一時間進行續約,會隨機延遲最多 12 小時進行靜默續約。

確認自動續約是否正常

不論是 systemd timercron,你都可以手動測試續約機制:

sudo certbot renew --dry-run

如果輸出沒有錯誤,表示自動續約機制正常。

你也可以檢查已安裝的憑證:

sudo certbot certificates

這會列出所有憑證的有效期限與存放路徑,確保憑證未過期。

確保憑證續約後自動生效

Let's Encrypt 續約憑證後,Nginx/Apache 仍然會載入舊憑證,直到它們被重新載入。

可以使用以下方式確保新憑證生效:

sudo systemctl reload nginx  # 或 systemctl reload apache2

你也可以在 certbot renew 指令後加入 --post-hook "systemctl reload nginx",讓 Certbot 續約成功後自動重新載入伺服器。

確認憑證是否成功續約

你可以使用以下指令,檢查目前的憑證有效期限:

sudo certbot certificates

這會列出所有由 Certbot 管理的憑證,包括每張憑證的到期日和存放位置。確保你的憑證續約後,其到期日已更新。

測試與驗證 HTTPS 配置

完成 HTTPS 設定後,請執行以下測試,確保一切正常且符合安全標準:

瀏覽器測試

在瀏覽器輸入 https://your-domain.com,確認顯示安全鎖 🔒,點擊查看憑證資訊,確保發行者為 Let's Encrypt,憑證有效且匹配網域。

嘗試輸入 http://your-domain.com,應自動跳轉至 HTTPS。

檢查 HTTP 轉 HTTPS 重定向

使用 curl 測試:

curl -I http://your-domain.com

確認返回 301 Moved PermanentlyLocation 標頭應指向 https://your-domain.com/...

確保網站功能正常

確認後端 API / 網站可正常運作,例如:

  • FastAPI 的 API 是否能正確回應?
  • 網站內部的 HTTPS 連結是否有效?
  • 是否考慮 X-Forwarded-Proto 來處理 HTTPS 轉向?

檢查 Nginx 日誌

查看錯誤與存取日誌,確保沒有異常:

sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log

若發現 TLS 連線錯誤,可能是舊設備不支援 TLS 1.2,可根據需求調整設定。

結論

我們好像做了不少事情。

在本章節中,我們成功地為 Nginx 啟用 HTTPS,確保所有流量都透過 Let's Encrypt 簽發的 SSL 憑證進行加密,並強制將 HTTP 轉向 HTTPS,提供更安全的連線環境。

除了基本的 HTTPS 部署,我們還透過 Nginx 反向代理 FastAPI,確保前端 Nginx 能夠安全地將請求轉發至後端服務,並保留原始請求資訊,如 客戶端 IP、請求協議(HTTP/HTTPS)等,以確保後端應用的完整性與可追溯性。

最後,我們還確保了憑證的自動續約機制,讓 Let's Encrypt 的憑證能夠自動更新,並在續約後自動生效,確保網站的 HTTPS 連線始終有效。

到這裡,網站的安全標準已經完成了嗎?

其實還沒,我們在下個章節來學習一下安全性的進階設定,包括 HSTS、CSP 等安全標頭的配置,以及如何防止常見的網站攻擊。

Cookie 使用告知

我們使用 Cookie 分析流量並提升使用者體驗。持續使用即表示您同意。詳情請見我們的隱私政策:Cookie Policy