這個專案使用 Docker、acme.sh 和 Cloudflare DNS-01,為以下名稱申請 Let's Encrypt 公開受信任憑證:
yourdomain.com
*.yourdomain.com
憑證可供家中 NAS、容器服務或反向代理使用。只要手機與電腦使用 AdGuard Home 作為 DNS,就能讓不同 hostname 指向不同內網 IP,同時在瀏覽器中正常使用 HTTPS。
例如在 AdGuard Home 建立以下 DNS Rewrite:
nas.yourdomain.com -> 192.168.1.10
app.yourdomain.com -> 192.168.1.20
cam.yourdomain.com -> 192.168.1.30
使用者就可以透過以下網址連線:
https://nas.yourdomain.com
https://app.yourdomain.com
https://cam.yourdomain.com
這些服務即使位於不同 IP,仍可使用同一張 wildcard 憑證。憑證驗證的是 hostname,不是 IP。
直接使用 https://192.168.1.20 不會通過憑證名稱驗證,必須使用憑證涵蓋的網域名稱。
排程或手動執行 docker run
|
v
acme.sh 簽發容器
|
| 使用 Cloudflare API 建立暫時的 TXT 記錄
v
_acme-challenge.yourdomain.com
|
| Let's Encrypt 從公開 DNS 驗證網域控制權
v
簽發 wildcard 憑證
|
v
/certs/cert.key + /certs/cert.cer
|
v
nginx / Caddy / NPM
|
v
內網應用程式
DNS-01 驗證只檢查 Cloudflare 上的 _acme-challenge TXT 記錄,不會連線到 NAS,也不要求服務具有公開 IP、開放 80/443 port 或進行路由器 port forwarding。
AdGuard Home 則負責內網的 Split DNS:內網裝置查詢 app.yourdomain.com 時,AdGuard Home 直接回答私人 IP,不需要從 Cloudflare 取得 A 記錄。
*.yourdomain.com 可以涵蓋:
app.yourdomain.com
nas.yourdomain.com
anything.yourdomain.com
但不涵蓋:
x.app.yourdomain.com
本專案同時申請 yourdomain.com,所以根網域本身也在憑證涵蓋範圍內。
Dockerfile 建立固定版本的執行環境
entrypoint.sh 驗證設定、申請憑證並輸出憑證檔案
.dockerignore 限制 Docker build context
README.md 使用說明
容器內的重要路徑:
| 路徑 | 用途 | 是否需要持久化 |
|---|---|---|
/usr/local/lib/acme.sh | 固定版本的 acme.sh 程式 | 否,包含在 image 中 |
/opt/acme.sh | ACME 帳號、私鑰、憑證狀態及 Cloudflare 設定 | 是 |
/certs | 給反向代理使用的憑證輸出 | 是 |
/tmp | 容器暫存空間 | 否,使用 tmpfs |
程式本體與狀態目錄刻意分開,因此將 volume 掛載到 /opt/acme.sh 不會覆蓋 image 中的 acme.sh 程式。
yourdomain.com 的權威 DNS 已交由 Cloudflare 管理。登入 Cloudflare Dashboard:
My Profile -> API Tokens。Create Custom Token。Zone -> DNS -> EditZone -> Zone -> ReadInclude -> Specific zone -> yourdomain.com不要使用 Global API Key,也不要將 Token 寫進 Dockerfile 或提交到 Git。
在 NAS 建立只有管理者可讀取的檔案,例如:
sudo mkdir -p /volume1/docker/acme-secrets
sudo vi /volume1/docker/acme-secrets/cf.env
內容如下:
CF_Account_ID=你的_Cloudflare_Account_ID
CF_Token=你的_Cloudflare_API_Token
限制檔案權限:
sudo chmod 600 /volume1/docker/acme-secrets/cf.env
acme.sh 的 Cloudflare DNS plugin 會把 API 資訊保存在 /opt/acme.sh/account.conf,所以 acme_home volume 也必須視為敏感資料,不應公開或任意複製。
容器使用 UID/GID 1000:1000 執行。建立輸出目錄並賦予寫入權限:
sudo mkdir -p /volume1/docker/certs
sudo chown 1000:1000 /volume1/docker/certs
sudo chmod 700 /volume1/docker/certs
如果 NAS 的儲存路徑不同,後續命令中的 /volume1/docker/certs 必須一起修改。
在包含 Dockerfile 和 entrypoint.sh 的目錄執行:
docker build -t home-acme:1.0 .
Dockerfile 目前固定使用:
3.20.103.1.4acme,UID/GID 為 1000:1000確認 image 已建立:
docker image inspect home-acme:1.0
Staging 憑證不受瀏覽器信任,但不會快速消耗正式環境的簽發額度,適合先驗證 Cloudflare Token、DNS 和檔案權限。
建立獨立的測試輸出目錄:
sudo mkdir -p /volume1/docker/certs-staging
sudo chown 1000:1000 /volume1/docker/certs-staging
sudo chmod 700 /volume1/docker/certs-staging
執行:
docker run --rm \
--cap-drop ALL \
--read-only \
--tmpfs /tmp \
--env-file /volume1/docker/acme-secrets/cf.env \
-e CERT_DOMAIN=yourdomain.com \
-e ACME_SERVER=letsencrypt_test \
-v acme_home_staging:/opt/acme.sh \
-v /volume1/docker/certs-staging:/certs:rw \
home-acme:1.0
測試與正式環境務必使用不同的 ACME volume 和輸出目錄。若共用狀態,測試憑證的續期時間可能讓後續正式簽發被判定為「尚未到期」而跳過。
成功後應看到:
/volume1/docker/certs-staging/cert.key
/volume1/docker/certs-staging/cert.cer
Staging 測試成功後執行:
docker run --rm \
--cap-drop ALL \
--read-only \
--tmpfs /tmp \
--env-file /volume1/docker/acme-secrets/cf.env \
-e CERT_DOMAIN=yourdomain.com \
-v acme_home:/opt/acme.sh \
-v /volume1/docker/certs:/certs:rw \
home-acme:1.0
參數說明:
| 參數 | 作用 |
|---|---|
--rm | 執行完成後移除一次性容器 |
--cap-drop ALL | 移除所有 Linux capabilities |
--read-only | 將 image 的 root filesystem 設為唯讀 |
--tmpfs /tmp | 提供不持久化的可寫入暫存空間 |
--env-file | 在執行時注入 Cloudflare 秘密 |
CERT_DOMAIN | 指定根網域,程式會同時加入 wildcard |
acme_home:/opt/acme.sh | 保存帳號、金鑰及續期狀態 |
/certs:rw | 輸出私鑰及完整憑證鏈 |
產出檔案:
| 檔案 | 內容 |
|---|---|
cert.key | 私鑰,權限固定為 0600 |
cert.cer | 伺服器憑證及中繼 CA 的 full chain |
不需要在 NAS 額外安裝 OpenSSL,可以使用 image 中的 OpenSSL:
docker run --rm \
--entrypoint openssl \
-v /volume1/docker/certs:/certs:ro \
home-acme:1.0 \
x509 -in /certs/cert.cer -noout -subject -issuer -dates -ext subjectAltName
輸出中的 Subject Alternative Name 應包含:
DNS:yourdomain.com
DNS:*.yourdomain.com
進入 AdGuard Home 管理介面:
Filters -> DNS rewrites -> Add DNS rewrite
適合每台主機自行提供 HTTPS:
| Domain | Answer |
|---|---|
nas.yourdomain.com | 192.168.1.10 |
app.yourdomain.com | 192.168.1.20 |
cam.yourdomain.com | 192.168.1.30 |
每一個 IP 上的 HTTPS server 都必須取得並載入相同的 cert.key 和 cert.cer。
如果反向代理位於 192.168.1.10,可以加入:
*.yourdomain.com -> 192.168.1.10
反向代理再依照 hostname 將流量送到不同後端:
app.yourdomain.com -> http://192.168.1.20:8080
cam.yourdomain.com -> http://192.168.1.30:8080
nas.yourdomain.com -> http://192.168.1.10:5000
這種架構只需要在反向代理部署一份私鑰,安全性與維護性都較好。
假設 AdGuard Home IP 是 192.168.1.2:
nslookup app.yourdomain.com 192.168.1.2
結果應顯示設定的私人 IP。如果沒有:
內網名稱不一定要在 Cloudflare 建立公開 A 記錄。DNS-01 只需要 Cloudflare 能建立驗證用 TXT 記錄,而內網 A 記錄可完全交由 AdGuard Home 回答。
將憑證目錄唯讀掛載至 nginx 容器,例如 /certs:ro:
server {
listen 443 ssl;
server_name app.yourdomain.com;
ssl_certificate /certs/cert.cer;
ssl_certificate_key /certs/cert.key;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.1.20:8080;
}
}
憑證私鑰權限是 0600,因此 nginx master process 必須以 root 或 UID 1000 讀取;其他非 root 反向代理若使用不同 UID,會遇到 Permission denied。不要為了省事將私鑰改成全體可讀。
憑證更新後必須 reload 反向代理,否則它可能繼續使用記憶體中的舊憑證:
docker exec nginx nginx -s reload
容器名稱不是 nginx 時請替換成實際名稱。
acme.sh 會保存憑證狀態。尚未進入續期窗口時會跳過簽發;本專案將 acme.sh 的「跳過」狀態碼 2 視為正常成功,因此可以安全地週期執行。
建議每 6 小時檢查一次,並只在簽發容器成功時 reload nginx:
17 */6 * * * /usr/bin/docker run --rm --cap-drop ALL --read-only --tmpfs /tmp --env-file /volume1/docker/acme-secrets/cf.env -e CERT_DOMAIN=yourdomain.com -v acme_home:/opt/acme.sh -v /volume1/docker/certs:/certs:rw home-acme:1.0 >>/volume1/docker/acme-renew.log 2>&1 && /usr/bin/docker exec nginx nginx -s reload
Synology 可在 Control Panel -> Task Scheduler 建立 root 使用者的週期工作,命令使用上面相同內容。
不要把輸出全部導向 /dev/null。保留日誌才能發現 Token 失效、DNS 驗證失敗或憑證接近到期等問題,並應搭配 log rotation 避免檔案無限成長。
Wildcard 憑證與 IP 數量無關。可採用兩種方式:
所有 hostname -> 反向代理 IP -> 各內網服務 IP
只有反向代理需要持有私鑰,這是建議方式。
nas.yourdomain.com -> NAS IP
app.yourdomain.com -> App Server IP
cam.yourdomain.com -> Camera Server IP
每台 HTTPS server 都要安全地取得同一張憑證。若要同步私鑰,應使用受限制的 SSH、部署工具或唯讀共享,不應讓所有服務都能寫入 /certs。本專案只負責簽發及輸出,不包含跨主機私鑰同步。
| 名稱 | 必填 | 預設值 | 說明 |
|---|---|---|---|
CERT_DOMAIN | 是 | 無 | 根網域,例如 yourdomain.com;不可填入 *. |
CF_Token | 是 | 無 | Cloudflare API Token |
CF_Account_ID | 是 | 無 | Cloudflare Account ID |
ACME_SERVER | 否 | letsencrypt | 測試時設為 letsencrypt_test |
ACME_DNS_SLEEP | 否 | 20 | 建立 TXT 後等待 DNS 傳播的秒數 |
如果 DNS 傳播較慢,可嘗試:
-e ACME_DNS_SLEEP=60
/opt/acme.sh is not writable by UID 1000持久化 volume 權限不正確。檢查 volume 或 bind mount 的 owner 是否允許 UID 1000 寫入。
/certs is not writable by UID 1000重新設定 NAS 輸出目錄:
sudo chown 1000:1000 /volume1/docker/certs
sudo chmod 700 /volume1/docker/certs
確認:
Zone:DNS:Edit 和 Zone:Zone:Read。yourdomain.com。CF_Token 與 CF_Account_ID 名稱及大小寫完全正確。api.cloudflare.com。將 ACME_DNS_SLEEP 從 20 增加至 60 或更高,並確認沒有其他 DNS provider 或舊 NS 設定。
/certs/cert.cer full chain。something.yourdomain.com,不是直接使用 IP。DNS、TLS 和應用程式連線是三個不同層次。另行檢查:
Skipping這是正常行為,表示憑證尚未進入續期窗口。容器仍會將目前有效憑證安裝到 /certs,並以成功狀態結束。
yourdomain.com,不要授權帳號中的所有 zones。cf.env、acme_home volume 和 cert.key 都是敏感資料。/certs。acme_home,但備份位置必須受到與私鑰相同等級的保護。Content type
Image
Digest
sha256:318c3a9b2…
Size
10.2 MB
Last updated
26 days ago
docker pull poynt2005/acme