SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-30

Ubuntu 24.04 安裝 Certbot 與 Nginx 設定教學

在 Ubuntu 24.04 使用 apt 或 snap 安裝 Certbot 並取得 Let's Encrypt 憑證。本文說明 certbot --nginx 指令、apt 與 snap 的差異,以及解決 port 80 驗證超時失敗的關鍵設定。

安裝 Certbot:使用 apt 或 snap

在 Ubuntu 24.04 上,sudo apt install certbot python3-certbot-nginx 能提供可用的 Certbot,用以簽發真實且受公開信任的 Let's Encrypt 憑證。Certbot 的官方文件建議使用 snap;兩者的差異很小,snap 版本追蹤上游發布,而套件庫版本則隨 LTS 發布並僅接收安全性更新。

請擇一使用。若同時安裝兩份 Certbot,會導致兩個續期計時器同時指向同一個 /etc/letsencrypt 目錄,而您遺忘的那一個往往會在關鍵時刻造成問題。

apt 安裝路徑:

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

此指令會安裝 /usr/bin/certbot、nginx 外掛程式、一組 certbot.service + certbot.timer,以及一個在 systemd 下無作用的 /etc/cron.d/certbot 項目。

snap 安裝路徑:

sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

snap 會自行安裝計時器 snap.certbot.renew.timer。在安裝 snap 之前,請務必先移除 apt 套件。

兩種安裝方式在後續的操作行為皆相同。Certbot 2.x 預設使用 ECDSA (P-256) 金鑰,僅在用戶端不支援 ECDSA 時才需傳入 --key-type rsa。所有狀態皆存放於 /etc/letsencrypt 下:archive/ 存放真實的金鑰與憑證檔案,live/ 為指向當前檔案的符號連結,renewal/ 存放每個憑證的設定檔,accounts/ 則存放您的 ACME 帳號金鑰。

HTTP-01 的實際運作方式,以及為何 port 80 不可或缺

HTTP-01 驗證是一種回呼機制。當您向 Let's Encrypt 申請涵蓋 example.com 的憑證時,對方會解析公開 DNS 中的網域名稱,連線至該位址的 port 80,並請求 http://example.com/.well-known/acme-challenge/<token>。您的伺服器必須回應 Certbot 寫入磁碟的確切權杖內容,這就是整個機制的運作方式。此機制衍生出三個關鍵結果,這也是大多數申請失敗的主因。

  • 必須能從公用網際網路存取 port 80,而不僅僅是從您的筆記型電腦。任何僅開放 443 的 ufw 規則、雲端供應商的安全群組(security group)或 VPS 控制台防火牆,都會導致申請失敗,且未來的續期作業也將一併失敗。
  • DNS 必須已指向此伺服器。 驗證伺服器會從外部執行獨立的查詢;您的 /etc/hosts 記錄與瀏覽器快取對其而言毫無意義。
  • 若您發布了 AAAA 記錄,系統會優先嘗試 IPv6。 當 IPv6 連線直接失敗時,Let's Encrypt 會改用 IPv4 重試;但若 AAAA 記錄指向一個過時的主機,且該主機「接受」連線但提供錯誤內容,則會導致直接失敗。

系統允許重新導向:驗證程序會跟隨 HTTP 重新導向至 HTTPS,且不在意目標端的憑證是否缺失、過期或為自簽憑證。但驗證程序絕不會從 port 80 以外的埠口開始。由於 Certbot 並未實作 TLS-ALPN-01,因此「直接使用 443」並非可行的替代方案。

選擇驗證器:--nginx、--webroot、--standalone

若 nginx 已在執行且負責處理該網域,--nginx 是預設的最佳選擇。Certbot 會解析您的設定檔、注入暫時的驗證路徑、重新載入 nginx、完成驗證,最後將 TLS 指令寫入您的 server block。此過程無須停機。

sudo certbot --nginx -d example.com -d www.example.com

適用於全新伺服器的腳本化操作:

sudo certbot --nginx \
  -d example.com -d www.example.com \
  --agree-tos -m ops@example.com --no-eff-email \
  --redirect --non-interactive

若您不希望 Certbot 更動 nginx 設定檔(例如設定檔是透過模板產生、由 git 管理或透過 Ansible 推送),則 --webroot 最為合適。Certbot 僅會將驗證檔案寫入您現有的服務目錄中。

sudo certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com \
  --deploy-hook "systemctl reload nginx"

若目前沒有任何服務監聽 80 埠(例如郵件伺服器、僅使用 443 埠的 API,或是在安裝 nginx 前執行的初始化腳本),則 --standalone 是正確選擇。Certbot 會自行綁定 80 埠數秒以完成驗證。若 nginx 正在執行,此指令會失敗,請在執行前後停止服務:

sudo certbot certonly --standalone -d mail.example.com \
  --pre-hook "systemctl stop nginx" \
  --post-hook "systemctl start nginx"

這些掛鉤(hooks)會記錄在憑證的續期設定中,因此在自動續期時,系統會自動執行相同的停止與啟動程序。

在憑證存在前後均可運作的 server block

先有雞還是先有蛋的問題:若 ssl_certificate 指向不存在的檔案,Nginx 將拒絕啟動,而 Certbot 在 Nginx 關閉時無法進行驗證。請先在 80 埠啟動網站。

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

執行 sudo nginx -t && sudo systemctl reload nginx,確認 curl -I http://example.com/ 能從伺服器「外部」回應,接著再進行簽發。完成後:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com;
        default_type "text/plain";
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

ACME 路徑上的 ^~ 前綴非常重要:它能防止 return 301 block 攔截驗證請求。將此路徑保留在 80 埠,可確保網站全面改用 HTTPS 後,憑證續期仍能正常運作。

上述兩個 block 皆從磁碟提供檔案;若 Nginx 是作為應用程式的前端代理,location / 應改為 proxy_pass block,而 逐行解析反向代理 server block 涵蓋了應用程式所需的標頭設定,至於 ACME 路徑與 TLS 指令則保持不變。

HTTP/2 的語法取決於您的 Nginx 版本,混用兩者會導致啟動錯誤。Ubuntu 24.04 搭載的 Nginx 1.24 採用行內設定 listen 443 ssl http2;。Debian 13 搭載較新的 Nginx,則使用獨立的 http2 on; 指令。請先檢查 nginx -v

請將 Nginx 指向 live/,切勿指向 archive/live/ 符號連結會在每次續期時重新指向;若使用指向 archive/ 的硬路徑,將導致您被鎖定在即將過期的憑證上。

萬用字元憑證需使用 DNS-01,而 DNS-01 需搭配外掛程式

萬用字元憑證 (*.example.com) 無法透過 HTTP-01 進行驗證,因為沒有單一主機名稱可供擷取檔案。DNS-01 是唯一途徑:您必須透過發布 _acme-challenge.example.com TXT 記錄來證明控制權。Certbot 需要您 DNS 提供商的 API 憑證才能自動執行此操作,這正是提供商外掛程式存在的目的。萬用字元憑證完整操作指南 涵蓋了 TXT 記錄的運作機制以及手動模式下的續期陷阱;以下為 Cloudflare 的簡要版本。

sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

在 apt 路徑上,該套件名稱為 sudo apt install python3-certbot-dns-cloudflare。憑證應存放於僅限 root 存取的檔案中:

# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_here

請將 Token 的權限限制在該區域的 DNS 編輯權限。這是您 DNS 的金鑰;請務必妥善保管。

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

請將萬用字元加上引號,以避免 shell 進行 glob 展開。DNS-01 同時解決了 HTTP-01 無法處理的問題:例如沒有公開 80 埠的主機憑證、內部服務、僅能透過 VPS 上的自架 WireGuard VPN 存取的機器,或是私有介面上的管理面板。

憑證續期:90 天效期、計時器與部署掛鉤

Let's Encrypt 憑證的有效期限為 90 天。Certbot 會在憑證剩餘效期少於 30 天時進行續期,這給您留下了 30 天的緩衝期,讓續期失敗時仍有時間修復,而不至於導致服務中斷。Let's Encrypt 不再發送過期提醒郵件,也不會有人主動通知您,因此監控工作必須由您自行負責。

請檢查您安裝時所配置的計時器:

systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificates

certbot renew 會遍歷 /etc/letsencrypt/renewal/ 中的所有設定檔,跳過任何不在 30 天續期視窗內的憑證,並使用與初次執行時完全相同的旗標來更新其餘憑證。這就是為什麼初次執行至關重要:因為該次執行的參數會被記錄下來。

僅僅更新磁碟上的檔案並不會產生任何作用,Nginx 會持續從記憶體中提供舊的憑證,直到有指令要求它重新載入為止。請設定一次部署掛鉤(deploy hook):

sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

任何位於 renewal-hooks/deploy/ 中可執行的程式,都會在憑證成功續期後執行。--deploy-hook 旗標可針對單一憑證執行相同任務,並將 renew_hook = ... 儲存在其續期設定中。certbot --nginx 會自動為您重新載入;但 --webroot--standalone 的設定則不會。遺漏掛鉤正是導致網站持續提供過期憑證,而 certbot certificates 卻顯示憑證已更新的原因。任何在啟動時讀取憑證的服務都需要相同的掛鉤;例如容器化應用程式,如 部署 Docker、TLS 與備份的 Nextcloud VPS,也需要在這裡設定其專屬的重啟或重新載入步驟。

測試實際續期流程

sudo certbot renew --dry-run

此指令會針對 Let's Encrypt 的測試環境執行完整的驗證挑戰:使用相同的程式碼路徑、防火牆規則與 DNS 設定,且不會消耗速率限制額度,也不會寫入任何檔案至磁碟。若測試今日通過,則 60 天後的自動續期亦會成功,前提是伺服器的底層環境未發生變更。

乾跑(dry run)無法驗證您的重新載入掛鉤(reload hook)是否觸發,此行為會隨 Certbot 版本而異。請手動測試該部分:直接執行掛鉤腳本,確認 systemctl reload nginx 執行成功,並檢查 sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf

您實際會遇到的錯誤

Could not bind to IPv4 or IPv6.--standalone,此時 nginx 已佔用 80 埠。請使用 --nginx--webroot,或在執行期間暫停 nginx。使用 sudo ss -lntp | grep ':80' 確認佔用者。

Timeout during connect (likely firewall problem),Let's Encrypt 無法連線至 80 埠。請由外向內檢查:sudo ufw status(使用 sudo ufw allow 'Nginx Full' 開啟它)、VPS 提供商的防火牆,最後是 DNS。請從非伺服器本身的外部環境進行測試:curl -sSv http://example.com/.well-known/acme-challenge/test。過期的 AAAA 記錄也會產生此訊息。

unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404,80 埠可連線,但無法提供 token。請求被導向至錯誤的 server block(請檢查哪個區塊擁有 default_server),或是傳遞給 -w 的目錄並非 nginx 實際服務的目錄。請在 /var/www/example.com/.well-known/acme-challenge/test 放置一個檔案並從外部存取;若出現 404,則問題不在憑證本身。

DNS problem: NXDOMAIN looking up A for example.com,網域名稱無法公開解析。可能是新記錄尚未生效,或是記錄位於註冊商未代管的區域中。

too many certificates already issued for: example.com,速率限制,這是除錯時最常遇到的錯誤。Let's Encrypt 對重複憑證(完全相同的網域名稱組合)設有每週 5 次的上限,並允許每個註冊網域每週申請 50 個新憑證;除了等待時間過去,沒有其他解鎖方式。請使用 --dry-run 對 staging 環境進行除錯。

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory,nginx 設定了尚未核發的憑證,或是該憑證已被 certbot delete 移除。請註解掉 TLS server block,啟動 nginx,申請憑證後再恢復該區塊。

open() "/etc/letsencrypt/options-ssl-nginx.conf" failed,該檔案隨 nginx 外掛套件提供。在沒有 python3-certbot-nginxcertonly 系統上,請安裝該外掛,或是將 include 行替換為您自己的 ssl_protocolsssl_ciphers 設定。

大規模管理憑證

一張憑證最多可包含 100 個名稱,單一 certbot --nginx -d a.example.com -d b.example.com ... 雖然誘人,但一旦其中一筆 DNS 記錄失效導致驗證失敗,該憑證下的所有名稱都會隨之失效。若伺服器託管多個服務,建議為每個網站申請獨立憑證,確保各服務能獨立運作。當網站數量增加時,使用具備 ACME 功能的入口代理程式更為高效:例如 在 Docker Compose 下執行 Traefik 反向代理,它能自動申請並續期憑證,完全無需手動操作 Certbot。至於該選擇哪種代理程式,取決於您希望代理層承擔多少憑證管理與應用程式設定工作,可參考 Nginx、Caddy 與 Traefik 的比較分析

備份 /etc/letsencrypt 時請務必完整保留 sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt,並確保符號連結(symlinks)完整。該目錄樹包含 accounts/ 與您的 ACME 帳號金鑰,此金鑰無法重新產生。遷移至新 VPS 的流程為:使用 -a 同步目錄樹、安裝 Certbot、更新 DNS 指向,並在切換服務前執行 certbot renew --dry-run

重建伺服器或升級至新 LTS 版本時,續期計時器不會自動遷移。在任何遷移、快照還原或發行版升級後,請務必執行 systemctl list-timers 'certbot*' 與一次 --dry-run。若忽略此步驟,憑證將無法自動續期,導致網站於 89 天後的凌晨失效。

上述流程均假設您擁有具備公開 IP 且開放 80 埠的機器(即 VPS)。這些機制適用於任何類型的 VPS。

相同的憑證設定步驟亦適用於 Apache 而非 Nginx 的環境;若無法使用公開憑證,則可參考 在 Ubuntu 上使用自簽憑證 來保護內部服務。

FAQ

若網站僅提供 HTTPS,仍需開放 80 埠嗎?

需要,這是為了 HTTP-01 驗證。Let’s Encrypt 一律從 80 埠發起驗證請求,且 Certbot 並未實作 TLS-ALPN-01,因此若防火牆僅開放 443 埠,將導致首次簽發與後續所有自動續期失敗。將 80 埠重新導向至 HTTPS 是可行的,驗證程序會跟隨導向。若要完全不使用 80 埠,唯一方法是透過 DNS-01 驗證並搭配供應商外掛程式。

在 Ubuntu 24.04 的 Nginx 上,應安裝 apt 還是 snap 版本的 Certbot?

請使用 apt。sudo apt install certbot python3-certbot-nginx 在 Ubuntu 24.04 提供 Certbot 2.9.0,此版本足以應付本指南所有需求,且能透過 unattended-upgrades 取得安全性更新,亦無需安裝 snapd。僅在需要立即取得最新版本,或該 DNS 外掛程式僅以 snap 形式發佈時,才選擇 snap。無論如何,請擇一安裝:若同時安裝兩者,會導致兩個續期計時器指向同一個 /etc/letsencrypt 目錄,而被遺忘的那個計時器將導致續期失敗。

Certbot 能為 Nginx 簽發萬用字元憑證嗎?

僅能透過 DNS-01 驗證。萬用字元憑證(如 *.example.com)沒有單一主機名稱可供放置驗證檔案,因此 --nginx--webroot--standalone 皆不適用。請安裝 DNS 供應商的外掛程式,將具備權限範圍的 API token 存入僅限 root 讀取的憑證檔案中,並執行 certbot certonly --dns-cloudflare -d example.com -d '*.example.com',同時請加上引號以避免 shell 進行 glob 展開。

為何續期成功後,Nginx 仍提供舊憑證?

Nginx 會將憑證載入記憶體,除非重新載入(reload),否則不會偵測到磁碟上的新檔案。certbot --nginx 會自動執行重新載入,但 --webroot--standalone 執行時不會,因此可能發生續期成功,但瀏覽器仍顯示憑證即將過期的情況。請在 /etc/letsencrypt/renewal-hooks/deploy/ 中放入一個可執行的指令碼來執行 nginx -t && systemctl reload nginx,該指令碼會在每次續期成功後自動觸發。

certbot renew --dry-run 能證明續期功能運作正常嗎?

大致上可以。它會針對測試環境(staging environment)執行真實驗證,使用相同的防火牆、DNS 與程式路徑,且無速率限制、不會寫入磁碟,因此通過測試代表網路層面運作正常。不過,這無法可靠地證明部署腳本(deploy hook)會正確觸發。請另外測試:手動執行該腳本並檢查 sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf