利用 Certbot 自動更新Let’s Encrtpt 憑證又一章

目前有幾個服務,是透過 docker 設置,使用反向代理網站的型態存在。最近,發現這些網站的服務都停止了,查了一下,原來是網站憑證過期,而我設置 certbot 向 Let’s Encrtpt 申請憑證自動延期的功能失效。

查了一下原因,原來是當初這些網站服務,當初憑證在申請時使用了 –manual(手動驗證,例如手動新增 DNS TXT 紀錄或放置驗證檔案)。

而在執行 certbot renew 時,Certbot 預設是以非互動式(非手動操作)的方式背景執行。又由於使用手動驗證又沒有設定自動化驗證腳本(–manual-auth-hook),所以系統無法自動完成驗證,因此停止自動延期。

要解決這個問題,目前一般來說有三個:

  1. 解法一:以互動模式手動更新(最快立即解決)
  2. 這是在 DNS 伺服器不支援 API,或者你想先立刻將過期憑證展延,可以直接加上 –manual 參數進行互動式更新:

      sudo certbot certonly --manual -d sample.yowlab.idv.tw --preferred-challenges http
    

    (若當初是用 DNS 驗證,可改為 –preferred-challenges dns)

    再按照終端機上的提示,手動新增 DNS TXT 紀錄或驗證檔案後按下 Enter,即可完成本次更新。

    此方法,無法達成我需要自動化延期的需求。

  3. 解法二:改用 Web 伺服器外掛自動化(若該主機有跑 Nginx / Apache)
  4. 因這台機器本身就有架設反向代理(如 Apache)導向 sample 主機,所以將驗證方式改成自動化外掛,未來 certbot renew 就能自動展延:

    若 Nginx:

      sudo certbot certonly --nginx -d sample.yowlab.idv.tw
    

    獨立模式(Standalone)(更新時,要短暫停用佔用 80 port 的服務):

      sudo certbot certonly --standalone -d sample.yowlab.idv.tw
    

    成功申請後,Certbot 會自動覆蓋 /etc/letsencrypt/renewal/sample.yowlab.idv.tw.conf 的驗證方式,之後排程執行 certbot renew 就不會再報錯。

  5. 解法三:改用 DNS API 外掛(推薦,完全無感自動續期)
  6. 若網域是透過 Cloudflare 等支援 API 的託管商管理,可安裝對應的 DNS 外掛,例如 Cloudflare DNS 外掛。

    此方法,目前我不是很依賴 Cloudflare, 所以不考慮採用。

實驗試做了一下,決定採用 獨立模式(Standalone)來解決:

  sudo certbot certonly --standalone -d sample.yowlab.idv.tw

又考慮到要讓系統自動執行,且更新時,要短暫停用佔用 80 port 的服務,所以更新前先暫停 apache2 服務,更新後要重啟 apache2 服務。

 certbot certonly \
    --standalone \
    --non-interactive \
    --agree-tos \
    --pre-hook "systemctl stop apache2" \
    --post-hook "systemctl start apache2" \
    --cert-name sample.yowlab.idv.tw \
    -d sample.yowlab.idv.tw

參數說明:

  1. –non-interactive 確保無人值守自動執行。
  2. 當 Certbot 遇到需要替換或擴充時會直接按標準規範更新,不會卡在終端機等待輸入 (E)/(C) 或 (U)/(R)。

  3. –agree-tos 代表 「Agree to the Terms of Service(同意服務條款)」。
  4. 當使用 Let’s Encrypt(ACME 協議)註冊帳號或申請憑證時,官方規定必須同意其 ACME 憑證認證機構的使用者服務條款(Subscriber Agreement)。這時會停下,等待使用者輸入同意或拒絕。

    這個參數,便是直接向 Certbot 表明「我已同意該服務條款」,不再跳出詢問提示。

  5. –prep-hook, –post-hook 參數,分別表示憑證更新前後,要做的事情。
  6. 憑證更新時,要先暫停 apache2 服務,憑證更新後,要再度啟動 apache2 服務。

  7. –cert-name 明確指定憑證名稱,以及憑證相關目錄名稱。
  8. –quiet:只有出錯時才會輸出訊息,平常無感安靜執行。

再來,因為我有多個服務,所以有多張憑證需要申請延期。因此,用程式來跑:

#!/usr/bin/env bash
set -uo pipefail

# 1. 固定主網域
BASE_DOMAIN="yowlab.idv.tw"

# 2. 變動子網域名單(以後增加直接在括號內補上,用空白或換行隔開)
SUBDOMAINS=(
  "sample01"
  "sample02"
  "sample03"
  "sample04"
)

# 3. 迴圈依序執行獨立簽發
for sub in "${SUBDOMAINS[@]}"; do
  fqdn="${sub}.${BASE_DOMAIN}"
  echo "=============================================="
  echo "開始處理憑證: ${fqdn}"
  echo "=============================================="

  certbot certonly \
    --standalone \
    --non-interactive \
    --agree-tos \
    --pre-hook "systemctl stop apache2" \
    --post-hook "systemctl start apache2" \
    --cert-name "${fqdn}" \
    -d "${fqdn}"  

  echo -e "\n"
done

echo "所有網域處理完成!"

最後再參考「讓 Certbot 可以每90天自動更新 Let’s Encrypt 憑證」這篇文,讓自動更新動起來。

 


特別說明:

  #!/usr/bin/env bash
  set -uo pipefail
  1. #!/usr/bin/env bash 是目前的推薦寫法
  2. /usr/bin/env 是一個標準工具,它的作用是根據當前使用者的 PATH 環境變數動態尋找 bash 的實際安裝路徑。
    這樣,不管 bash 是安裝在 /bin、/usr/bin 還是 /usr/local/bin,都能正確找到並啟動,具有更好的跨平台相容性。

  3. set -uo pipefail ,嚴格模式設定
  4. 預設情況下,Bash 遇到錯誤時的行為非常寬鬆(常常出錯了還繼續往下硬跑),這很容易導致災難。這行指令由兩個關鍵設定組合而成:

    • -u(Nounset:抓出未宣告變數)
    • 預設行為:如果你打錯變數名稱,或讀取一個根本沒被定義的變數,Bash 會把它當作「空字串」,靜悄悄地直接執行。

      危險範例:假設腳本寫 TARGET_DIR=”/tmp/backup”,但刪除時打錯字寫成 rm -rf “${TAGET_DIR}/*”。因為 TAGET_DIR 沒定義,Bash 會把它看作空字串,結果指令變成 rm -rf “/*”,整台系統直接被清空。

      加上 -u 後:只要程式讀取到任何未宣告或未給值的變數,Bash 會立刻中斷執行並印出錯誤訊息(unbound variable),防止災情發生。

    • -o pipefail(Pipe 管道錯誤捕捉)
    • 預設行為:當指令使用 Pipe 管道 | 串接多個程序時,Bash 預設只看最後一個指令的結束狀態(Exit Code)。

      範例:grep “error” non_exist_file.txt | wc -l

      前半段 grep 因為找不到檔案早就出錯了,但後半段的 wc -l 算出來是 0(正常執行完畢),整行指令依然會回傳「成功(Exit Code 0)」,導致錯誤被掩蓋。

      加上 -o pipefail 後:在整個 pipeline 串接中,只要有任何一個指令出錯失敗,整條管線的回傳值就會被判定為失敗,讓錯誤無所遁形。


小結
  1. #!/usr/bin/env bash:確保在任何 Unix/Linux 環境下都能精準抓到 bash 執行。
  2. set -uo pipefail:開啟防呆機制,禁止使用未定義的變數,且確保管線出錯時不會被忽略。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

*