- 將 hospital_media_antmedia.md 分拆為 docs/ 下五份主題文件 (host.md, caddy.md, antmedia.md, ssl.md, maintenance.md) - 新增 README.md / QUICKSTART.md / SUMMARY.md 作為文件入口 - 新增 SOP:Caddy SSL 憑證更新流程 (docs/sop-update-caddy-ssl.md) 含 2026-04-16 實戰踩坑紀錄(.cer 副檔名、私鑰不匹配、sudo、防火牆 TLS 終端) - 更新各文件中 tel.tpech.gov.tw 憑證資訊:HiPKI → TWCA,到期 2026-10-30 - 新增 .gitignore 排除 ssl-staging/(含私鑰,不可進 git) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
SOP:更新 Caddy SSL 憑證(tel.tpech.gov.tw)
本 SOP 用於把新申請到的 SSL 憑證部署到 /opt/caddy/,讓 tel.tpech.gov.tw 使用新憑證。
本 SOP 只處理 Caddy 的憑證(
tel.tpech.gov.tw)。 Ant Media Server 的tel-server.tpech.gov.tw是另一張獨立憑證,更新方式見 ssl.md。
實戰紀錄(2026-04-16 首次執行)
以下是本 SOP 首次實際執行時踩到的坑,記錄下來避免日後重蹈覆轍。
坑 1:TWCA cert.zip 裡的副檔名是 .cer,不是 .crt
TWCA 核發的壓縮檔解開是 root.cer、uca.cer、server.cer,都是 PEM 格式但副檔名為 .cer。指令中的 *.crt 要改成 *.cer,否則找不到檔案。
坑 2:Private Key 不在主機上
主機 /opt/caddy/privkey.pem 裡面是舊的 HiPKI 那把 key。這次向 TWCA 申請時用的是新產生的 CSR / key pair,所以舊 key 和新憑證的 modulus 不匹配。
最後在本機 ~/Downloads/ssl/tel/tel.key(2026-03-26 產生)找到正確的 key。
教訓:申請憑證時,key 和 CSR 一定要放到固定位置並標註日期與用途。建議統一放到 ssl-staging/<網域>-<年月>/ 並保留 .key + .csr 原始檔。
坑 3:Docker 指令需要 sudo
tpech_1duan 使用者不在 docker group 裡,./restart.sh 必須加 sudo。
坑 4:醫院前端有防火牆 / WAF 做 TLS 終端
Caddy 重啟後,主機本地 127.0.0.1:443 驗證是新的 TWCA 憑證,但從外部連 106.105.181.40:443 看到的還是舊的 HiPKI 憑證。
原因:醫院網路在伺服器前面有一層設備(防火牆 / WAF / load balancer)在做 SSL 終端或 SSL inspection。外部 TLS 連線是跟那台設備交握,不是直接跟 Caddy 交握。
需要額外通知網管單位:把新憑證也更新到前端防火牆/WAF 上。憑證檔案可以從主機 /opt/caddy/hospital/ssl_tel_tpech_gov_tw_2026_10/ 取得。
坑 5:OCSP Stapling timeout
Caddy log 中出現 TWCA 的 OCSP 伺服器 twcasslocsp.twca.com.tw 連線 timeout。原因可能是主機對外存取被限制。這只是 warning,不影響 TLS 正常運作,但客戶端可能需要自行查 OCSP。
憑證資訊
目前生效的憑證(2026-04-16 部署)
| 項目 | 值 |
|---|---|
| 網域 | tel.tpech.gov.tw |
| 簽發者 | TWCA Secure SSL Certification Authority |
| 憑證序號 | 95590185165637134231279949412783532535 |
| 生效日 | 2026-04-15 |
| 到期日 | 2026-10-30(僅半年) |
| 憑證檔 | cert.zip(內含 root.cer、uca.cer、server.cer) |
| 私鑰來源 | 本機 ~/Downloads/ssl/tel/tel.key(2026-03-26 產生) |
| 主機備份 | /opt/caddy/hospital/ssl_tel_tpech_gov_tw_2026_10/ |
被替換的舊憑證
| 項目 | 值 |
|---|---|
| 簽發者 | 中華電信 HiPKI OV TLS CA - G1 |
| 有效期 | 2025-04-23 ~ 2026-04-23 |
| 主機備份 | /opt/caddy/hospital/ssl_tel_tpech_gov_tw_pre_2026_10_backup/ |
注意:這張憑證只有半年效期,2026-10 月前必須再次更新。
0. 前置檢查(動手前必確認)
| # | 檢查項 | 指令 / 說明 |
|---|---|---|
| A | cert.zip | TWCA 寄來的附件。解壓後應有 3 個 .cer 檔 |
| B | Private Key | 當初產生 CSR 時留下的私鑰。TWCA 不會寄這個,必須自己保留 |
| C | SSH 可連 | ssh hospital_media "echo ok" |
| D | Caddy 活著 | ssh hospital_media "sudo docker ps | grep caddy" |
找不到私鑰怎麼辦?
- 先搜本機:
find ~/Downloads ~/Desktop ~/Documents -name '*.key' -newer <cert.zip 的日期>- 再搜主機:
ssh hospital_media "find /opt /root /home -maxdepth 4 -name '*.key' 2>/dev/null"- 都找不到 → 重新產生 CSR/key,向 TWCA 申請 rekey(重核發)
1. 本機準備工作目錄
工作區放在 repo 下的 ssl-staging/(已加入 .gitignore,不會 commit)。
cd /Users/timmy/Projects/Freelance/106_105_181_40
mkdir -p ssl-staging/tel-tpech-<年月> # 例:tel-tpech-2026-10
cd ssl-staging/tel-tpech-<年月>
把 cert.zip 和 privkey.pem(或 *.key)都放進來。
2. 解壓
unzip cert.zip
ls
# TWCA 格式:root.cer uca.cer server.cer
# (注意副檔名是 .cer,不是 .crt)
3. 檢查憑證格式與內容
# 確認是 PEM 文字格式
file *.cer
# 應為 "PEM certificate" 或 "ASCII text"
# 確認網域、簽發者、效期
openssl x509 -in server.cer -noout -subject -issuer -dates
若是 DER 格式(file 顯示 "data" 或 "ASN.1"),轉 PEM:
for f in root.cer uca.cer server.cer; do
openssl x509 -inform DER -in "$f" -out "$f.pem" && mv "$f.pem" "$f"
done
4. 合成 fullchain.pem 並命名私鑰
# 合併:server 在前,中繼 CA 在後(順序不可反)
cat server.cer uca.cer > fullchain.pem
# 私鑰統一命名
cp <你的key檔> privkey.pem # 例:cp tel.key privkey.pem
ls privkey.pem # 確認存在
root.cer 不需放進 fullchain.pem,Caddy 只需 leaf + intermediate 就夠。
5. 驗證「憑證」與「私鑰」是否匹配
最重要的一步。錯了貼上去 Caddy 會起不來。
openssl x509 -in fullchain.pem -noout -modulus | openssl md5
openssl rsa -in privkey.pem -noout -modulus | openssl md5
兩個 MD5 必須完全相同。
# 正確範例(兩邊一樣):
# MD5(stdin)= 1c4ae9b6fae3920bb30f76ac792db989
# MD5(stdin)= 1c4ae9b6fae3920bb30f76ac792db989
若不同 → 立刻停。代表這把 key 不是產生 CSR 的那把。回頭找對的 key,參考步驟 0 的搜尋方法。
2026-04-16 實例:主機上的
/opt/caddy/privkey.pem(舊 HiPKI 那把)modulus 為c4b56aed...,新憑證為1c4ae9b6...,不匹配。最後在本機~/Downloads/ssl/tel/tel.key找到匹配的。
6. 備份主機上的舊憑證
ssh hospital_media "sudo mkdir -p /opt/caddy/hospital/ssl_tel_tpech_gov_tw_pre_<年月>_backup && \
sudo cp /opt/caddy/fullchain.pem /opt/caddy/privkey.pem \
/opt/caddy/hospital/ssl_tel_tpech_gov_tw_pre_<年月>_backup/ && \
ls -l /opt/caddy/hospital/ssl_tel_tpech_gov_tw_pre_<年月>_backup/"
7. 上傳新憑證到主機
# 經由 /tmp 中轉,避免權限問題
scp fullchain.pem privkey.pem hospital_media:/tmp/
ssh hospital_media "sudo mv /tmp/fullchain.pem /tmp/privkey.pem /opt/caddy/ && \
sudo chmod 644 /opt/caddy/fullchain.pem && \
sudo chmod 600 /opt/caddy/privkey.pem && \
ls -l /opt/caddy/fullchain.pem /opt/caddy/privkey.pem"
8. 重啟 Caddy
# 注意:需要 sudo,否則 permission denied
ssh hospital_media "cd /opt/caddy && sudo ./restart.sh"
確認 log 無報錯:
ssh hospital_media "sudo docker logs caddy --tail 20"
# 看到 "serving initial configuration" 且無 error 就算成功
# OCSP stapling timeout 是 warning,可忽略
9. 驗證
9a. 主機本地驗證(必做,最可靠)
ssh hospital_media "openssl s_client -connect 127.0.0.1:443 \
-servername tel.tpech.gov.tw </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates"
# 預期:
# subject= ... CN=tel.tpech.gov.tw
# issuer= ... TWCA ...
# notAfter= ...
看到新的 issuer 和到期日 → Caddy 端完成。
9b. 外部驗證(可能看到舊憑證!)
openssl s_client -connect tel.tpech.gov.tw:443 \
-servername tel.tpech.gov.tw </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
已知問題:醫院前端有防火牆 / WAF 做 TLS 終端,從外面連進去看到的可能還是舊憑證。 這不是 Caddy 的問題——需要另外通知網管單位更新防火牆/WAF 上的憑證。 見下方「防火牆 / WAF 注意事項」。
10. 歸檔到主機備份目錄
scp cert.zip fullchain.pem privkey.pem server.cer uca.cer root.cer \
hospital_media:/tmp/
ssh hospital_media "sudo mkdir -p /opt/caddy/hospital/ssl_tel_tpech_gov_tw_<年月> && \
sudo mv /tmp/cert.zip /tmp/fullchain.pem /tmp/privkey.pem \
/tmp/server.cer /tmp/uca.cer /tmp/root.cer \
/opt/caddy/hospital/ssl_tel_tpech_gov_tw_<年月>/ && \
ls -l /opt/caddy/hospital/ssl_tel_tpech_gov_tw_<年月>/"
11. 通知網管 + 更新文件
通知網管更新防火牆/WAF
發信或工單給網管單位,內容:
tel.tpech.gov.tw:443的 SSL 憑證已在伺服器端更新為 TWCA 新憑證(到期 YYYY-MM-DD),請同步更新防火牆 / 負載均衡器上的 TLS 終端憑證。 新憑證檔案位置:主機/opt/caddy/hospital/ssl_tel_tpech_gov_tw_<年月>/
更新本 repo 文件
把以下文件裡 tel.tpech.gov.tw 的 CA 與到期日更新:
- docs/caddy.md —
Caddy SSL 憑證段 - docs/ssl.md —
憑證總覽 - docs/maintenance.md —
SSL 憑證到期時間 - SUMMARY.md — 第 6 節
防火牆 / WAF 注意事項
106.105.181.40 的 :443 前面有醫院的網路設備做 TLS 終端:
外部用戶 → [防火牆/WAF :443 (TLS 終端)] → [106.105.181.40 Caddy :443]
- Caddy 裝了新憑證,只能讓「防火牆 → Caddy」這段用新憑證。
- 「外部用戶 → 防火牆」那段用的是防火牆上安裝的憑證,需要網管另外處理。
- 驗證是否全程更新完成:從外部執行
openssl s_client,看到新的 issuer 才算全部搞定。
回滾流程(萬一 Caddy 起不來)
ssh hospital_media "sudo cp /opt/caddy/hospital/ssl_tel_tpech_gov_tw_pre_<年月>_backup/fullchain.pem \
/opt/caddy/hospital/ssl_tel_tpech_gov_tw_pre_<年月>_backup/privkey.pem \
/opt/caddy/ && \
cd /opt/caddy && sudo ./restart.sh"
回滾後檢查第 5 步的 MD5 是否真的一致、第 4 步的 cat 順序是否正確。
常見地雷
| 症狀 | 通常原因 | 參考步驟 |
|---|---|---|
Caddy 起來但 ERR_SSL_PROTOCOL_ERROR |
憑證/私鑰不匹配 | 第 5 步 |
NET::ERR_CERT_AUTHORITY_INVALID |
fullchain.pem 少了中繼 CA |
第 4 步 |
tls: failed to parse private key |
私鑰格式不對(加密了 / DER 沒轉 PEM) | 第 3 步 |
scp 上去後 Caddy 讀不到 |
權限不對(644/600) | 第 7 步 |
./restart.sh permission denied |
忘了加 sudo |
第 8 步 |
| 外部仍看到舊憑證、本地是新的 | 醫院防火牆/WAF TLS 終端未更新 | 第 9b 步 |
| OCSP stapling timeout(log warning) | 主機對外連線受限,不影響功能 | 可忽略 |
主機上的 privkey.pem 和新憑證不匹配 |
申請新憑證時用了新 CSR/key,舊 key 當然不配 | 第 0/5 步 |
主機備份目錄結構(供日後查閱)
/opt/caddy/hospital/
├── ssl_tel_tpech_gov_tw_2025/ # 2025 HiPKI 憑證原件
├── ssl_tel_tpech_gov_tw_pre_2026_10_backup/ # 2026-04 更新前的舊檔備份
├── ssl_tel_tpech_gov_tw_2026_10/ # 2026 TWCA 憑證原件
├── ssl_tel_server_tpech_gov_tw_2025/ # tel-server 2025 憑證
└── ...