Ubuntu 24.04 安裝 Certbot 與 nginx 憑證教學
本文介紹在 Ubuntu 24.04 使用 apt 或 snap 安裝 Certbot 以取得 Let's Encrypt 憑證。詳細說明 Certbot 2.x 預設使用 ECDSA 金鑰,並提醒若防火牆未開啟 port 80 將導致 HTTP-01 驗證失敗,進而造成憑證無法自動續期。
安裝 Certbot:使用 apt 或 snap
在 Ubuntu 24.04 上,使用 sudo apt install certbot python3-certbot-nginx 可獲得可發行真實且受大眾信任的 Let's Encrypt 憑證的 Certbot。Certbot 官方文件建議使用 snap;兩者差異極小:snap 會追蹤上游版本,而 apt 軟體包則追蹤 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/certbotsnap 會附帶自己的排程 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 剛寫入磁碟的精確 token 內容。這就是整個機制。此機制會導致三個結果,這也是大多數憑證核發失敗的原因。
- 必須能從公用網路存取 port 80,而不僅限於您的筆記型電腦。若
ufw規則、雲端供應商的安全群組 (security group) 或 VPS 主控台防火牆僅開啟 443,將導致憑證核發失敗,且後續的所有續期也會失敗。 - 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 設定檔,請使用 --webroot。這適用於從範本產生、存放於 git 或透過 Ansible 部署的設定。Certbot 只會將驗證檔案寫入您已設定好的服務目錄中。
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"若 80 port 沒有任何服務在監聽(例如:郵件伺服器、僅使用 443 port 的 API,或是在 nginx 啟動前的首次開機腳本),請使用 --standalone。Certbot 會自行佔用 80 port 幾秒鐘。若 nginx 正在執行,此操作會失敗,請在執行時將其停止:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"這些 hook 會記錄在憑證的續訂設定中,因此續訂時會自動執行相同的停止與啟動動作。
在憑證建立前後皆可運作的 server block
這是一個先有雞還是先有蛋的問題:若 ssl_certificate 指向不存在的檔案,nginx 會拒絕啟動;而若 nginx 停止運作,Certbot 也無法進行驗證。請先在 port 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 location 中至關重要:它能防止 return 301 block 攔截驗證請求。將該 location 保留在 port 80,可確保網站在全面轉向 HTTPS 後,憑證續訂仍能正常運作。
HTTP/2 的語法取決於你的 nginx 版本,混用兩種格式會導致啟動錯誤。Ubuntu 24.04 搭載 nginx 1.24,需使用 inline 寫法 — listen 443 ssl http2;。Debian 13 搭載較新版本的 nginx,需使用獨立的 http2 on; 指令。請先檢查 nginx -v。
請將 nginx 指向 live/,切勿指向 archive/。live/ 符號連結會在每次續訂時重新指向;若直接使用 archive/ 的絕對路徑,會導致憑證過期後無法自動更新。
Wildcards 代表 DNS-01,而 DNS-01 代表需要套件
Wildcard 憑證 (*.example.com) 無法透過 HTTP-01 進行驗證,因為無法針對單一主機名稱擷取檔案。DNS-01 是唯一的途徑:你必須透過發布 _acme-challenge.example.com TXT 紀錄來證明擁有該網域的控制權。Certbot 需要 DNS 提供商的 API 憑證才能進行自動化操作,這也是 DNS 提供商套件存在的原因。完整的 Wildcard 憑證操作指南 介紹了 TXT 紀錄的運作機制,以及手動模式下的續期陷阱;後續則提供 Cloudflare 的簡短版本。
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare在 sudo apt install python3-certbot-dns-cloudflare 的 apt 路徑下,做法有所不同。憑證資訊需存放在僅限 root 使用者的檔案中:
# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_here請將 Token 的權限限制在該單一 Zone 的 DNS 編輯權限。這等同於你的 DNS 金鑰,請妥善保管。
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'請為 Wildcard 加上引號,避免 Shell 進行 glob 匹配。DNS-01 還能解決 HTTP-01 無法處理的問題:為無法開啟公用 80 埠的主機核發憑證,例如內部服務、僅能透過 部署在 VPS 上的 self-hosted WireGuard VPN 存取的設備,或是位於私有介面的管理面板。
續期:90 天期限、計時器與部署鉤子 (deploy hook)
Let's Encrypt 憑證有效期為 90 天。當剩餘天數少於 30 天時,Certbot 會執行續期作業。這提供了 30 天的緩衝期,若續期失敗,您仍有時間修復,而不至於導致服務中斷。Let's Encrypt 不再發送過期警告郵件,系統不會主動通知,因此您必須自行進行監控。
檢查安裝時預設的計時器:
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renew 會檢查 /etc/letsencrypt/renewal/ 中的所有設定,跳過不在 30 天期限內的項目,並使用原始執行的完全相同的 flags 來續期其餘項目。這就是為何第一次執行至關重要:因為系統會記錄該次的參數。
僅更新磁碟上的檔案並不會生效 —— nginx 會持續從記憶體中提供舊憑證,直到收到重新載入指令。請設定一次部署鉤子:
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.shrenewal-hooks/deploy/ 中的任何可執行檔都會在續期成功後執行。--deploy-hook flag 對單一憑證執行相同功能,並將 renew_hook = ... 儲存在其續期設定中。certbot --nginx 會自動為您重新載入;--webroot 與 --standalone 設定則不會。若缺少鉤子,會導致 certbot certificates 顯示憑證已更新,但網站仍在使用過期的憑證。任何在啟動時讀取憑證的服務都需要相同的鉤子 —— 例如 使用 Docker、TLS 與備份的 Nextcloud VPS 安裝 等容器化應用程式,也必須在此處設定其自身的重啟或重新載入步驟。
進行實際續期測試
sudo certbot renew --dry-run這會在 Let's Encrypt 的 staging 環境執行完整的挑戰程序:使用相同的程式碼路徑、防火牆與 DNS,且不會觸發速率限制(rate-limit),也不會寫入任何資料到磁碟。若今日測試通過,且伺服器環境保持不變,則 60 天後的自動續期(unattended renewal)也會通過。
dry run 無法證明 reload hook 是否正常觸發,因為其行為會隨 Certbot 版本而異。請手動測試該部分:直接執行 hook 腳本,確認 systemctl reload nginx 成功,並檢查 sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf。
您實際會遇到的錯誤
Could not bind to IPv4 or IPv6. — nginx 已佔用 port 80,導致 --standalone。請使用 --nginx 或 --webroot,或者在執行前停止 nginx。使用 sudo ss -lntp | grep ':80' 確認佔用該 port 的程序。
Timeout during connect (likely firewall problem) — Let's Encrypt 無法連線至 port 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 — port 80 可連線,但無法取得 token。請求可能被導向不同的 server block(請檢查哪個 block 擁有 default_server),或是傳遞給 -w 的目錄並非 nginx 服務的目錄。請在 /var/www/example.com/.well-known/acme-challenge/test 放置一個檔案並從外部存取;若出現 404 錯誤,則問題不在憑證。
DNS problem: NXDOMAIN looking up A for example.com — 該名稱無法進行公開解析。原因為新紀錄尚未生效,或是該紀錄位於註冊商未提供服務的 zone 中。
too many certificates already issued for: example.com — 觸發速率限制 (rate limit),通常發生在循環除錯時。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,核發憑證後,再還原該 block。
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed — 該檔案隨 nginx plugin 套件提供。若在沒有 python3-certbot-nginx 的 certonly 系統上,請安裝該 plugin,或將 include 行替換為您自訂的 ssl_protocols 與 ssl_ciphers 設定。
大規模管理
單一憑證最多可包含 100 個名稱,使用單一 certbot --nginx -d a.example.com -d b.example.com ... 雖然看似方便,但若其中一個過時的 DNS 紀錄驗證失敗,會導致該憑證上的所有名稱同時失效。若為每個網站使用獨立憑證,則各網站會獨立失效,這在託管多個服務的機器上是較理想的配置。當網站數量超過一定規模時,具備 ACME 功能的入口閘道(front door)會變得非常重要:例如 在 Docker Compose 下執行多個應用程式的 Traefik reverse proxy 會自動請求並續訂憑證,此時完全不需要使用 Certbot。
請完整備份 /etc/letsencrypt,並確保 sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt 的 symlinks 完整。該目錄結構包含 accounts/(您的 ACME 帳戶金鑰),此金鑰無法重新生成完全相同的內容。遷移至新 VPS 的步驟如下:使用 -a rsync 該目錄、安裝 Certbot、重新指向 DNS,並在切換服務前執行 certbot renew --dry-run。
重新建置機器或升級至新的 LTS 版本時,續訂計時器並不會隨之遷移。在任何遷移、快照還原或發行版升級後,請執行 systemctl list-timers 'certbot*' 與 --dry-run。若跳過此步驟,可能會導致網站在 89 天後的凌晨 3 點突然斷線,因為該憑證原本應自動續訂。
以上所有前提是您擁有一台具備公用 IP 且 80 port 對外開放的受控機器——換言之,即 VPS。上述機制適用於任何此類機器。
相同的憑證步驟也適用於 使用 Apache 而非 nginx;若無法使用公用憑證,則可使用 在 Ubuntu 上的自簽憑證 來處理內部服務。
FAQ
如果網站僅提供 HTTPS,我需要開啟 port 80 嗎?
需要,這是為了進行 HTTP-01 驗證。Let's Encrypt 始終從 port 80 發起驗證請求,且 Certbot 並未實作 TLS-ALPN-01。因此,若防火牆僅開啟 443,會導致首次核發與後續自動續期皆會失敗。將 port 80 重新導向至 HTTPS 是可行的,驗證程序會跟隨該導向。若要完全跳過 port 80,唯一的方案是使用 DNS 供應商套件進行 DNS-01 驗證。
在 Ubuntu 24.04 上為 nginx 安裝 Certbot,該選擇 apt 還是 snap?
請使用 apt。sudo apt install certbot python3-certbot-nginx 在 Ubuntu 24.04 上提供 Certbot 2.9.0 版本,足以應付本指南中的所有需求,並能透過 unattended-upgrades 取得安全性更新,且不需要安裝 snapd。僅在需要立即取得最新版本,或需要使用僅透過 snap 發行的 DNS 套件時,才選擇 snap。無論選擇哪種方式,請僅安裝其中一種:安裝兩個版本會導致兩個續期排程同時指向同一個 /etc/letsencrypt 目錄,進而導致其中一個被遺忘而造成問題。
Certbot 可以為 nginx 核發萬用字元 (wildcard) 憑證嗎?
僅能透過 DNS-01 驗證。由於 *.example.com 等萬用字元沒有單一主機名稱來下載驗證檔案,因此 --nginx、--webroot 與 --standalone 皆無法使用。請安裝您 DNS 供應商的套件,將具備權限限制的 API token 放入僅限 root 讀取的憑證檔中,然後執行 certbot certonly --dns-cloudflare -d example.com -d '*.example.com',並對萬用字元使用引號以避免 shell globbing 錯誤。
為什麼續期成功後,nginx 仍在提供舊的憑證?
nginx 會將憑證保留在記憶體中,在重新載入 (reload) 之前不會偵測到磁碟上的新檔案。certbot --nginx 會自動為您重新載入,但 --webroot 與 --standalone 則不會,這會導致續期成功但瀏覽器仍顯示即將過期的憑證。您可以在 /etc/letsencrypt/renewal-hooks/deploy/ 中放置一個執行 nginx -t && systemctl reload nginx 的執行檔腳本,使其在每次續期成功後觸發。
certbot renew --dry-run 能證明續期功能正常嗎?
大部分可以。它會在測試環境 (staging environment) 執行真實驗證——使用相同的防火牆、DNS 與程式碼路徑——且不會消耗速率限制 (rate-limit) 或寫入磁碟,因此測試通過代表網路層面正常。然而,它無法可靠地證明您的部署鉤子 (deploy hook) 有正常觸發。請另外測試:手動執行該鉤子腳本並檢查 sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf。