盡可能使用HTTPS加可信任的憑證,是能讓網站傳輸更安全,也可以避開不信任的警示視窗。
為了折騰自己,我決定每個網站之類的都要盡可能部署憑證,而且還是可受信任的。
不過,如果都要手動申請,就會很麻煩。這邊我之前有寫過一個好用的python GUI。
靈活度有了,但是沒有辦法定時更新。搭配上最近正在學的容器化,要設定出可任意搭配的方式,可以隨附在有需要掛上憑證服務中。
在Dokcer版的Certbot折騰一陣子,發現作為Container,執行指令後會關掉,導致一直會跳出容器不正常的訊息,此外也無法定時更新。
使用Cloudflare是因為想用專屬(花$)的網域,專屬網域名稱更簡潔。而若想使用免費方案,可以使用Dynu去做DNS挑戰方式產生憑證。
我的配置會在檢查既有或產生憑證之後,直接等待到能更新那一刻,才會刷新,不會使容器停擺。

透過,環境變數DOMAIN_CER_PATH、DOMAIN_KEY_PATH,能自定義生成出來的憑證要複製一份放到哪裡,對於用其他容器指定憑證位置會更加方便! ※憑證直接拿fullchain使用,更能備受網站客戶端信任。
配置
- 將配置文件放在專案目錄
- 在專案目錄建立資料夾「acme-data」與「certs」
- 申請Cloudflare Token。 ※ 也可以參考官網所有支援的方式,或是使用dynu api token。
- 在「.env」填入Cloudflare Token,以及其他值具有<>的參數。
- 在「docker-compose.yml」修改DOMAIN。
- docker-compose up,憑證就會放在「$CERT_DIR/$DOMAIN/」的位置。
配置文件
docker-compose.yml
services:
acme:
image: neilpang/acme.sh:latest
container_name: acme-certificate_adg
#restart: unless-stopped
env_file:
- .env
environment:
# 時區設定
TZ: "Asia/Taipei"
DOMAIN: "<your domain>"
CERT_DIR: "/certs"
#DOMAIN_CER_PATH: "/certs/testing/my.cer" # 自定義證書路徑
#DOMAIN_KEY_PATH: "/certs/testing/my.key" # 自定義金鑰路徑
CF_Token: "${CF_Token}"
#CF_Key: "${CF_Key}" # Token與Key二選一
#CF_Email: "${CF_Email}" # Token與Email二選一
# Let’s Encrypt 帳號郵件,用於註冊及到期通知
ACCOUNT_EMAIL: "${ACME_EMAIL}"
volumes:
- ./certs:/certs
- ./acme-data:/acme.sh
- ./acme-entrypoint.sh:/usr/local/bin/acme-entrypoint.sh:ro
entrypoint: ["/bin/sh", "/usr/local/bin/acme-entrypoint.sh"]
acme-entrypoint.sh
#!/bin/sh
set -e
umask 077 # 新檔案預設權限:owner rw, others none
#── 1. 優雅處理停止/重啟訊號 ─────────────────────────────
trap 'echo "[$(date "+%Y-%m-%d %H:%M:%S")] 收到終止訊號,準備退出…"; exit 0' TERM INT
#── 2. 從 .env 讀取環境變數 ──────────────────────────────
: "${DOMAIN:?請在 .env 設定 DOMAIN}"
: "${HOME_DIR:?請在 .env 設定 HOME_DIR}"
: "${CERT_DIR:?請在 .env 設定 CERT_DIR}"
AFTER_RENEW_CMD=${AFTER_RENEW_CMD:-""}
DOMAIN_CER_PATH=${DOMAIN_CER_PATH:-"$CERT_DIR/$DOMAIN/$DOMAIN.cer"}
DOMAIN_KEY_PATH=${DOMAIN_KEY_PATH:-"$CERT_DIR/$DOMAIN/$DOMAIN.key"}
#── 3. Cloudflare 驗證設定 ──────────────────────────────
if [ -n "$CF_Token" ]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 使用 Cloudflare API Token 驗證"
elif [ -n "$CF_Key" ] && [ -n "$CF_Email" ]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 使用 Cloudflare API Key+Email 驗證"
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 錯誤:未偵測到 CF_Token 或 CF_Key+CF_Email" >&2
exit 1
fi
#── 4. 確保輸出路徑存在 ───────────────────────────────────
mkdir -p "$(dirname "$DOMAIN_CER_PATH")" "$(dirname "$DOMAIN_KEY_PATH")"
#── 5. 偵測 acme.sh 憑證目錄(支援 _ecc)──────────
ACME_DOMAIN_DIR=$(find "$HOME_DIR" -maxdepth 1 -type d -name "${DOMAIN}*" | head -n1)
[ -z "$ACME_DOMAIN_DIR" ] && {
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 找不到 $HOME_DIR 下的 ${DOMAIN}* 目錄!" >&2
#── 7. 首次申請(若尚無憑證) ─────────────────────────────
if [ ! -f "$DOMAIN_CER_PATH" ] || [ ! -f "$DOMAIN_KEY_PATH" ]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 開始首次申請憑證..."
# 組裝安裝指令
issue_cmd=
"acme.sh --issue --dns dns_cf -d \"$DOMAIN\" --home \"$HOME_DIR\" \
--fullchain-file \"$DOMAIN_CER_PATH\" \
--key-file \"$DOMAIN_KEY_PATH\""
if [ -n "$AFTER_RENEW_CMD" ]; then
install_cmd="$install_cmd --reloadcmd '$AFTER_RENEW_CMD'"
fi
eval "$install_cmd"
chmod 644 "$DOMAIN_CER_PATH" "$DOMAIN_KEY_PATH"
fi
}
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 偵測到憑證目錄:$ACME_DOMAIN_DIR"
#── 6. 同步已存在的憑證 ───────────────────────────────────
if [ ! -f "$DOMAIN_CER_PATH" ] && [ -f "$ACME_DOMAIN_DIR/fullchain.cer" ]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 同步 fullchain.cer → $DOMAIN_CER_PATH"
cp "$ACME_DOMAIN_DIR/fullchain.cer" "$DOMAIN_CER_PATH"
KEY_SRC=$(ls "$ACME_DOMAIN_DIR"/*.key 2>/dev/null | head -n1)
if [ -n "$KEY_SRC" ]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 同步 private key → $DOMAIN_KEY_PATH"
cp "$KEY_SRC" "$DOMAIN_KEY_PATH"
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 警告:找不到任何 .key 檔,請檢查 $ACME_DOMAIN_DIR" >&2
fi
chmod 644 "$DOMAIN_CER_PATH" "$DOMAIN_KEY_PATH"
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 同步完成。"
fi
#── 8. 無限迴圈:單一網域續期 + 顯示下一次檢查時間 ─────────────
while :; do
if [ -n "$AFTER_RENEW_CMD" ]; then
acme.sh --renew --renew-hook "$AFTER_RENEW_CMD" -d "$DOMAIN" --home "$HOME_DIR" || true
else
acme.sh --renew -d "$DOMAIN" --home "$HOME_DIR" || true
fi
chmod 644 "$DOMAIN_CER_PATH" "$DOMAIN_KEY_PATH"
# 讀取下一次續期 timestamp
CONF_FILE="$ACME_DOMAIN_DIR/$DOMAIN.conf"
if renew_ts=$(grep -o "Le_NextRenewTime='[0-9]\+'" "$CONF_FILE" | cut -d"'" -f2); then
now_ts=$(date +%s)
sleep_sec=$(( renew_ts > now_ts ? renew_ts - now_ts : 12*3600 ))
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 解析續期時間失敗,使用預設 12 小時"
sleep_sec=$(( 12*3600 ))
fi
# 計算絕對時間並格式化
next_ts=$(( now_ts + sleep_sec ))
next_dt=$(date -d "@${next_ts}" "+%Y/%m/%d %Hh:%Mm:%Ss")
echo "[$(date "+%Y-%m-%d %H:%M:%S")] 下次續期檢查時間:${next_dt}"
sleep "$sleep_sec"
done
.env
# Domain 與路徑設定
DOMAIN=<your domain>
HOME_DIR=/acme.sh
CERT_DIR=/certs
AFTER_RENEW_CMD=
ACME_EMAIL=<your email>
# Docker_ACME
CF_Token=<CF_Token>
# 或:
# CF_Key=your_global_api_key
# CF_Email=your_account_email
申請Cloudflare Token
Cloudflare API TOKEN : https://dash.cloudflare.com/profile/api-tokens
在ACME文件中寫道,照著文件步驟就能申請好。這邊不贅述。
1. CloudFlare Option:
Cloudflare Domain API offers two methods to automatically issue certs:
(a) creating a restrictive API token with specific permissions; or
(b) using the global API key associated with your Cloudflare account, which has all permissions.Using method (b) is strongly NOT recommended as leakage of the global API token will completely compromise your account, though the key can be reset if this occurs. By contrast, method (a) is recommended because if a restrictive API token is leaked, the attack surface is small, it can simply be deleted/revoked, and its permissions can also be changed at any time via your Cloudflare profile settings.
(a) Using a restrictive API token
You will need to create an API token which either:
(i) has permissions to edit a single specific DNS zone; or
(ii) has permissions to edit multiple DNS zones.You can do this via your Cloudflare profile page, under the API Tokens section. When your create the token, under Permissions, select Zone > DNS > Edit, and under Zone Resources, only include the specific DNS zones within which you need to perform ACME DNS challenges.
The API token is a 40-character string that may contain uppercase letters, lowercase letters, numbers, and underscores. You must provide it to acme.sh by setting the environment variable
CF_Token
to its value, e.g. runexport CF_Token="Y_jpG9AnfQmuX5Ss9M_qaNab6SQwme3HWXNDzRWs"
.
在「.env」填入Cloudflare Token以及其他參數
CF_Token
從Cloudflare API中取得。其他最重要的就是 DOMAIN以及ACME_EMAIL。
docker-compose up,憑證就會放在「$CERT_DIR/$DOMAIN/」的位置
在docker-compose up,就能看到容器正在執行。
可以查看LOG,如果沒狀況,就能順利產出憑證。

docker-compose up,憑證就會放在「$CERT_DIR/$DOMAIN/」的位置