SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Ubuntu 24.04 Apache 安裝 Certbot 與 HTTPS

在 Ubuntu 24.04 上用一行命令為 Apache 申請免費 Let's Encrypt 憑證。跳過 snap,apt 提供 Certbot 2.9.0,並排查會阻擋申請的 ServerName 問題。

建置內容

在 Ubuntu 24.04 上建立 Apache 網站,透過 HTTPS 提供服務,使用由 Certbot 申請、受瀏覽器信任且免費的 Let’s Encrypt 憑證,並由 systemd timer 自動續期,之後不必再處理。實際執行工作的命令只有一行。所有問題都會在執行該命令前發生:vhost 缺少 ServerName、供應商防火牆封鎖 port 80,或 DNS 仍指向舊伺服器。因此,本指南大多用於確認前置條件,並列出每個錯誤所顯示的確切錯誤字串。

有兩點範圍說明。如果您的 web server 是 nginx,流程大致相同,但 plugin 與設定不同,請改用 nginx 版本的本指南。如果要保護的服務僅供內部使用,例如位於私有位址的管理面板,或沒有人從其他位置存取的 staging 主機,則完全不需要憑證授權單位;自簽憑證需要的設定較少,也能在離線環境中運作。

先決條件,以及 Certbot 執行前就會失敗的 3 種情況

  • Apache 已經透過一般 HTTP 提供網站。 Certbot 的 Apache plugin 會修改現有網站,不會建立網站。如果你是從全新的 VPS 開始,請先完成 Ubuntu 24.04 上的 LAMP stack,再回到這裡;本指南就是該流程缺少的 TLS 章節。
  • 具備公開網域,且 A record 指向 VPS 位址。 Let's Encrypt 的 HTTP-01 challenge 會讓其驗證伺服器從網際網路連線到你的主機:沒有設定 port forwarding 的 NAT 家用實驗室無法使用,也不能使用 .local 名稱。HTTP-01 可以驗證裸 IP,但 Let's Encrypt 只會在選用 shortlived profile 時核發 IP 憑證(有效期 160 小時);而 apt 的 Certbot 2.9.0 無法要求 ACME profiles 或 IP 位址,因此本指南需要網域名稱。dig +short example.com 必須回傳你的 VPS 位址。如果你在最近 1 小時內變更 DNS,請先等待舊 record 的 TTL 到期,再進行核發。
  • 如果存在 AAAA record,內容必須正確。 發布 AAAA record 時,Let's Encrypt 一律會先嘗試 IPv6。如果該連線逾時,才會改用 IPv4 重試;其他錯誤不會獲得第二次機會:如果 AAAA 指向拒絕連線的位址,或指向一台可運作但不是這台主機的伺服器,即使你的筆電透過 IPv4 連線時 curl 運作正常,驗證仍會失敗。請發布正確的 AAAA,或完全不要發布。

ufw 以及供應商的網路防火牆都必須開放 80 和 443 埠。多數 hosting panel 另有一層作業系統看不到的防火牆。HTTP-01 特別使用 80 埠進行驗證;不能只使用 443 埠執行本流程。

sudo ufw allow "Apache Full"
sudo ufw status

完成上述設定後,整個流程只需 15 分鐘,其中 10 分鐘用來閱讀。

使用 Snap 還是 apt 安裝 Certbot?在 24.04 上,apt 終於可以正常使用

Certbot 多年前改用 snap 發布,原因很明確:發行版套件已經停滯不前。Ubuntu 20.04 提供 Certbot 0.40,之後一直沒有更新,專案也不想再處理五年前的錯誤。在 24.04 上,這個問題已不存在。套件庫提供 Certbot 2.9.0,這是現代 2.x 系列於 2024 年 2 月發布的版本,而 unattended-upgrades 會安裝 Ubuntu 發布的所有安全更新。上游目前已進入 5.x 系列,但本指南不需要該版本。針對此作業系統,我建議使用 apt。如此可省略 snapd daemon,Apache plugin 會在同一筆交易中安裝,續期 timer 也會依照一般 Debian 方式整合 systemd。

sudo apt update
sudo apt install -y certbot python3-certbot-apache
certbot --version

正確結果為:certbot 2.9.0。python3-certbot-apache package 是用來讀取及修改 Apache 設定的 plugin。若缺少該套件,certbot --apache 會以 The requested apache plugin does not appear to be installed 失敗。

以下兩種情況仍適合使用 snap:你希望在最新 Certbot 發布當天取得該版本,或需要只有 snap 提供的 DNS plugin(certbot-dns-* provider plugins 中有數個屬於此類)。如果選擇此方式:

sudo apt remove -y certbot python3-certbot-apache
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

無論選擇哪一種方式,都不要同時執行兩者。兩個安裝會讓兩個續期排程器爭用 /etc/letsencrypt,而 shell 在 PATH 中找到的 certbot,可能不是管理憑證的那一個。上方的 apt remove 行不是可有可無的裝飾。

Certbot 編輯的 vhost 必須已經存在,ServerName 是關鍵

certbot --apache 會尋找連接埠 80 的 virtual host,確認其 ServerName 或 ServerAlias 是否符合你提供的每個 -d 網域,藉此驗證你對網域的控制權,然後建立該 vhost 的 SSL 副本。如果沒有符合的 ServerName,就不會匹配;而 Ubuntu 的預設 000-default.conf 中,ServerName 預設為註解。這行註解是本指南中那個大型指令失敗的最常見原因。

因此,在執行 Certbot 前,先為網站建立正確的 name-based vhost。建立 /etc/apache2/sites-available/example.com.conf:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com
    ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

啟用它,並確認 Apache 能解析設定,且會將該名稱路由至此 vhost:

sudo a2ensite example.com.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
sudo apache2ctl -S

configtest 必須輸出 Syntax OK。如果同時輸出 AH00558: apache2: Could not reliably determine the server's fully qualified domain name,這表示全域 ServerName 有警告,不是你的 vhost 發生問題。在此處可安全忽略,並以 echo "ServerName $(hostname -f)" | sudo tee /etc/apache2/conf-available/servername.conf && sudo a2enconf servername && sudo systemctl reload apache2 消除。

-S 的輸出才是需要確認的項目。你應看到類似 port 80 namevhost example.com (/etc/apache2/sites-enabled/example.com.conf:1) 的行,且其下方有 alias www.example.com。Apache 回報的是實際讀取的 sites-enabled symlink,而不是你在 sites-available 編輯的檔案。如果 example.com 沒有列在連接埠 80 下,Certbot 也找不到它。

簽發憑證:certbot --apache

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

首次執行時會詢問3件事:電子郵件地址(Let's Encrypt 不再將其儲存在 ACME 帳戶中,也不再傳送到期警告,因此續期監控由你負責)、是否同意 Let's Encrypt 條款,以及是否要與 EFF 分享電子郵件地址。現在不會再詢問重新導向:自 Certbot 1.0 起,Apache 安裝程式預設會將 HTTP 重新導向至 HTTPS(該問題本身已在1.4中移除),這正是你需要的設定。只有在確實需要讓純 HTTP 持續提供內容時,才傳入 --no-redirect。

成功時會顯示如下內容,請仔細閱讀,不要略過:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2027-01-09.

Deploying certificate
Successfully deployed certificate for example.com to /etc/apache2/sites-available/example.com-le-ssl.conf
Successfully deployed certificate for www.example.com to /etc/apache2/sites-available/example.com-le-ssl.conf
Congratulations! You have successfully enabled HTTPS on https://example.com and https://www.example.com

在這段訊息背後,Certbot 執行了4項操作:如果 Apache 尚未啟用 ssl 模組,便先啟用該模組;寫入 example.com-le-ssl.conf,也就是將你的 vhost 複製到 *:443,並加入 SSLEngine on 與憑證路徑;啟用該設定;最後在原本的 port-80 vhost 中加入 RewriteRule 區塊,將所有請求以 301 重新導向至 HTTPS。原本的 vhost 檔案會直接編輯,不會被取代;SSL 副本則放在旁邊,你可以逐行查看新增的內容。

憑證實際存放的位置,以及為什麼絕對不要複製它

所有檔案都會放在 /etc/letsencrypt/live/example.com/ 下:fullchain.pem(憑證與中繼憑證鏈,伺服器應指定使用此檔案)、privkey.pem(私密金鑰,僅限 root 讀取),以及供需要分開使用這些元件的軟體使用的 cert.pem 與 chain.pem。這些檔案都是指向 /etc/letsencrypt/archive/ 的符號連結,而這層間接指向正是續期機制:續期時會將新檔案寫入 archive/,再重新指向這些符號連結。讓其他軟體使用 live/ 路徑,它們就能自動取得續期後的檔案;若將檔案複製到其他位置,90 天後就會造成服務中斷。

另一個值得了解的檔案是 /etc/letsencrypt/renewal/example.com.conf。其中記錄此憑證的簽發方式、authenticator = apache、installer = apache 及網域,讓續期程序能在無人介入的情況下重複執行,包括之後重新載入 Apache。

續期工作已排程,請驗證,不要重複建立

Let's Encrypt 憑證目前預設有效期為 90 天,而 apt 套件已安裝必要機制:systemd timer 每天執行 Certbot 2 次,並在隨機時間執行,為距離到期日 30 天內的憑證續期。不要另外建立 cron job;新增第二個排程器只會增加日誌雜訊與觸發速率限制的風險。Let's Encrypt 已宣布,自 February 10, 2027 起,新憑證的預設有效期將為 64 天。30 天的續期窗口仍適用,續期時間會從第 60 天左右提前至第 34 天左右,因此不需要變更此伺服器的設定。

systemctl list-timers certbot.timer
sudo certbot renew --dry-run

第一個命令會顯示 timer 已啟用,NEXT 時間會落在接下來 24 小時內。排程每天執行 2 次,並使用隨機延遲,因此確切時間會刻意保持不可預測(使用 snap 安裝時,timer 則是 snap.certbot.renew.timer)。dry run 會在 Let's Encrypt 的 staging environment 中完整演練續期流程,使用真實 challenge,但不會簽發憑證,也不會消耗速率限制額度。成功結果最後會顯示:

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

如果 dry run 失敗,約 60 天後的實際續期也會以相同方式失敗。請立即修正,因為目前憑證仍處於完整有效期內。最常見的原因是簽發憑證後新增了防火牆規則,再次封鎖 port 80。

使用 curl 驗證,以及鎖頭應顯示的內容

curl -sI http://example.com | head -n 3
curl -I https://example.com
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -dates

第一個指令應回傳 HTTP/1.1 301 Moved Permanently,並包含 Location: https://example.com/ 標頭;這就是 Certbot 安裝的重新導向。第二個指令應回傳 HTTP/1.1 200 OK,且 curl 不應顯示 TLS 錯誤。第三個指令會輸出簽發者、O = Let's Encrypt 行;其中 CN 應是類似 YE1 或 YE2(ECDSA 金鑰),或 YR1 或 YR2(RSA 金鑰)的簡短值,到期日約為 notAfter。在 Let's Encrypt 淘汰較舊的中繼憑證之前簽發的憑證,在下次續期前仍可能顯示類似 R12 或 E7 的 CN;這不是錯誤。在瀏覽器中,您應會看到鎖頭圖示;按一下後會顯示相同的簽發者。如果 curl 可正常運作,但瀏覽器顯示警告,幾乎可以確定您看到的是快取頁面或錯誤的主機名稱,而不是憑證問題。

多個網站:使用單一 SAN 憑證,或每個網站各用一張憑證

兩種方式都可行,續期方式也相同。對於同一台伺服器上彼此無關的網站,請為每個網站各執行一次簽發命令。每個網站都會在 live/ 下取得自己的目錄與續期設定,因此其中一個網域發生問題時,不會阻止其他網域續期。這是我預設採用的方式。

對於包含多個名稱的單一網站,請將這些名稱放在同一張 SAN 憑證中。單張憑證最多可包含 100 個名稱。上方已使用 example.com 和 www.example.com 完成此設定。之後若要將名稱加入現有憑證,請指定憑證名稱重新簽發,並提供變更後的完整名稱清單:

sudo certbot --apache --cert-name example.com -d example.com -d www.example.com -d blog.example.com

Certbot 會偵測網域集合已變更,要求你確認擴充作業,然後在原位置取代憑證,路徑仍為 live/,因此不需要修改其他設定。請注意,這份清單會取代原有內容,而不是附加內容:若從該命令中省略 www,新憑證會在無提示的情況下移除該名稱。

萬用字元憑證需要 DNS-01,而且通常不需要萬用字元

HTTP-01 無法簽發 *.example.com。在 Web 伺服器放置檔案只能證明你控制單一主機名稱,無法證明你控制整個命名空間。萬用字元憑證需要 DNS-01 challenge:Certbot 會在 _acme-challenge.example.com 建立 TXT 記錄。實務上,這表示要使用具備 DNS provider API 憑證的 certbot-dns-* plugin,或在每次續期時手動編輯 TXT 記錄並使用 --manual(這種方式非常麻煩,不應以此規劃)。從 TXT 記錄的運作方式,到可在無人介入的情況下自動續期的 plugin,完整說明請參閱 透過 DNS-01 使用 Certbot 申請萬用字元憑證。實際建議是:如果你已知有 4 個子網域,列出這 4 個名稱的 SAN 憑證比萬用字元憑證更簡單,也不需要將 DNS API key 存放在伺服器上。

錯誤情況,以及你會看到的字串

Certbot 因為 Apache 設定損毀而拒絕啟動。

The apache plugin is not working; there may be problems with your existing configuration.
The error was: MisconfigurationError('Error while running apache2ctl configtest.\n\nAction \'configtest\' failed.\nThe Apache error log may have more information.\n\nAH00526: Syntax error on line 12 of /etc/apache2/sites-enabled/example.com.conf')

外掛程式會先執行 configtest,確認 Apache 本身沒有問題後才會進行其他操作;\ns 是字面內容,因為 Certbot 會輸出例外的 repr。請自行執行 sudo apache2ctl configtest:它會指出檔案與行號。常見原因包括手動編輯時打錯字、SSLCertificateFile 指向已不存在的路徑,或參照了尚未啟用的模組。持續修正,直到輸出 Syntax OK,再重新執行 Certbot。

沒有 vhost 符合該網域。

Unable to find a virtual host listening on port 80 which is currently the only challenge port.

這是前文提到的缺少 ServerName 錯誤,會在申請憑證時被擷取。Certbot 搜尋了所有已啟用的 port-80 vhost,尋找符合你的 -d 的 ServerName/ServerAlias,但沒有找到。sudo apache2ctl -S 會顯示 Apache 實際將請求轉送到哪裡;請在正確的 vhost 加入 ServerName 行,重新載入後再試一次。另一種相近情況是驗證請求到達了錯誤的 vhost。因為其他網站攔截了請求,challenge response 會回傳 Invalid response ... 404。診斷方式相同,使用的工具也是 apache2ctl -S。

驗證逾時。

Certbot failed to authenticate some domains (authenticator: apache).
...
Detail: ...: Timeout during connect (likely firewall problem)

Let's Encrypt 無法在 DNS 宣告的位址上開啟通往 port 80 的 TCP 連線。最可能的原因依序為:供應商的網路防火牆(與 ufw 分開,需在 hosting panel 設定)、ufw 規則只允許 443 或只允許 SSH,或 DNS 仍指向先前的伺服器。單獨存在過期的 AAAA 通常不會造成此錯誤,因為 IPv6 嘗試逾時時,Let's Encrypt 會改用 IPv4 重試;AAAA 問題通常會在錯誤的伺服器回傳 Connection refused 或 Invalid response 時顯現。請從 VPS 外部測試:在筆記型電腦執行 curl -I http://example.com,即可重現驗證器看到的結果。

重試過多而觸發速率限制。

Error creating new order :: too many failed authorizations (5) for "example.com" in the last 1h0m0s, retry after ... UTC: see https://letsencrypt.org/docs/rate-limits/...

Let's Encrypt 對每個 hostname、每個 account 每小時允許 5 次驗證失敗。這項限制採用會回補的額度機制,大約每 12 分鐘恢復 1 次重試;對著損壞的防火牆反覆重試,很快就會耗盡額度。等待即可解除限制,但真正的修正方式是改變操作流程:每次失敗後,先使用 staging environment 進行除錯,直到驗證成功。

sudo certbot certonly --apache --dry-run -d example.com -d www.example.com

請注意 certonly:只有 certonly 和 renew subcommands 接受 --dry-run,直接使用 certbot --apache --dry-run 則完全不會執行,並顯示 --dry-run currently only works with the 'certonly' or 'renew' subcommands。dry run 會對 staging 進行驗證;staging 有較寬鬆的限制,也不會簽發正式憑證,因此可以在那裡反覆測試。只有 staging 通過後,才應重新執行正式指令。至於其他限制,包括每個 registered domain 每週 50 張憑證,以及每週針對相同名稱集合的 5 張重複憑證,只有在 script 持續迴圈重新簽發時才可能遇到。

HTTPS 啟用後,請記住憑證保護的是傳輸,不是伺服器本身:port 22 仍可能整天遭到密碼猜測。接著搭配 在 Ubuntu 24.04 上設定 Fail2ban,是接下來半小時最適合進行的工作。

FAQ

在 Ubuntu 24.04 上為 Apache 安裝 Certbot,應使用 snap 還是 apt?

使用 apt。Ubuntu 24.04 提供 Certbot 2.9.0,版本已足以支援本指南的所有內容,也會透過 unattended-upgrades 取得 Ubuntu 發布的安全性更新,且不需要 snapd。只有在需要立即取得最新版本,或需要僅以 snap 發布的 DNS 外掛程式時,才選擇 snap;如果改用 snap,請先執行 apt remove certbot python3-certbot-apache,避免兩個續期排程器同時運作。

為什麼 Certbot 顯示「找不到在 port 80 上監聽的虛擬主機」?

因為沒有啟用的 port 80 vhost,其 ServerName 或 ServerAlias 符合你透過 -d 傳入的網域。Ubuntu 的預設 vhost 會將 ServerName 設為註解。執行 sudo apache2ctl -S,找出或建立應負責該名稱的 vhost,加入 ServerName example.com,重新載入 Apache,然後再次執行 Certbot。

如何修正「連線期間逾時(可能是防火牆問題)」?

Let's Encrypt 無法從 DNS 發布的位址連線至 port 80。除了 ufw,也要檢查供應商控制面板層級的網路防火牆,確認 dig +short example.com 會回傳這台 VPS,並修正或刪除過時的 AAAA 記錄,因為驗證會先嘗試 IPv6,逾時後才回退至 IPv4。使用 curl -I http://example.com 從伺服器外部確認修正結果,然後在正式申請前使用 sudo certbot certonly --apache --dry-run -d example.com 進行演練。

Certbot 會在 Ubuntu 24.04 上自動續期憑證嗎?

會。apt 套件會安裝 certbot.timer,這是 systemd timer,每天執行 2 次,並續期任何距離到期日 30 天內的憑證,之後重新載入 Apache;snap 則使用 snap.certbot.renew.timer 執行相同工作。使用 systemctl list-timers certbot.timer 驗證設定,並以 sudo certbot renew --dry-run 進行演練;不要另外建立 cron 工作。

如何使用 Certbot 和 Apache 取得萬用字元憑證?

萬用字元憑證需要 DNS-01 challenge:Certbot 必須在 _acme-challenge.example.com 建立 TXT 記錄,因此需要搭配 DNS 供應商 API 憑證的 certbot-dns-* 外掛程式(--manual 替代方案則需要在每次續期時手動編輯 TXT 記錄)。如果只有少數已知的子網域,直接列出這些網域的 SAN 憑證會更簡單,也能避免將 DNS API 金鑰存放在伺服器上。