跳转到帖子
在手机APP中查看

一个更好的浏览方法。了解更多

PHP论坛人

主屏幕上的全屏APP,带有推送通知、徽章等。

在iOS和iPadOS上安装此APP
  1. 在Safari中轻敲分享图标
  2. 滚动菜单并轻敲添加到主屏幕
  3. 轻敲右上角的添加按钮。
在安卓上安装此APP
  1. 轻敲浏览器右上角的三个点菜单 (⋮) 。
  2. 轻敲添加到主屏幕安装APP
  3. 轻敲安装进行确认。

安裝 acme.sh + Google Public CA 自動申請與續簽 TLS 憑證

精选回复

適用環境:Debian 13、LNMP 架構(Linux + Nginx + MariaDB + PHP-FPM)

VPS規格:2 vCPU / 2GB RAM

驗證方式: HTTP-01(需對外開放 TCP 80 port)

憑證類型:ECC(P-256),由 Google Trust Services 簽發


------------------------------------------------
前言:TLS憑證 與 SSL憑證 有什麼不同?
------------------------------------------------

許多教學、面板、甚至官方文件都將憑證稱為「SSL 憑證」,但你現在安裝的實際上是 TLS 憑證。

這兩個詞彙常被混用,理解其差異有助於閱讀各類技術文件。


歷史沿革


協定版本:SSL 2.0
發佈年份:1995
現況:已廢棄,存在嚴重漏洞

協定版本:SSL 3.0
發佈年份:1996
現況:已廢棄(POODLE 攻擊)

協定版本:TLS 1.0
發佈年份:1999
現況:已廢棄(RFC 8996)

協定版本:TLS 1.1
發佈年份:2006
現況:已廢棄(RFC 8996)

協定版本:TLS 1.2
發佈年份:2008
現況:現行主流,仍安全

協定版本:TLS 1.3
發佈年份:2018
現況:現行最新,效能更佳


SSL(Secure Sockets Layer)協定最終版本為 SSL 3.0,之後由 IETF 標準化為 TLS(Transport Layer Security)協定,兩者在本質上是不同的協定。

現代瀏覽器與伺服器均已停用所有 SSL 版本,實際使用的是 TLS 1.2 或 TLS 1.3。



為什麼還在叫「SSL 憑證」?

憑證本身(X.509 格式)與底層使用 SSL 還是 TLS 協定無關,

憑證的用途是驗證伺服器身份與加密金鑰交換,它可以搭配 SSL 或 TLS 使用。

「SSL 憑證」這個說法在行銷與習慣上沿用至今,本質上就是 TLS 憑證,即 X.509 數位憑證。


本教學的正確術語

協定:TLS 1.2 / TLS 1.3(Nginx 設定中的 ssl_protocols)

憑證:X.509 TLS 憑證,由 Google Trust Services 簽發

「SSL」:出現於 Nginx 指令(如 ssl_certificate、listen 443 ssl)時,是歷史遺留的指令名稱,實際協定仍是 TLS




-----------------------------------
1. 前置條件與注意事項
-----------------------------------

在開始之前,請確認以下所有條件均已滿足,否則後續步驟將無法順利完成。


環境需求

項目:作業系統
要求:Debian 13 (Trixie)

項目:Web 伺服器
要求:Nginx(已安裝並運行中)

項目:執行身份
要求:root(本教學全程以 root 執行)

項目:TCP 80 port
要求:必須對外開放,且 Nginx 正在監聽

項目:網域 DNS
要求:A 記錄已指向本VPS的 IP,且 DNS 已全球生效

項目:Google 帳號
要求:需要一個 Google 帳號以使用 Google Cloud Console



1.1 確認 DNS 已正確解析

提醒:DNS 生效時間通常需要數分鐘至數小時不等,視 TTL 設定而定。

申請憑證前請先確認 DNS 已正確指向 VPS 的公網 IP。


確認 DNS A 記錄(IPv4)
dig +short A 域名.com


確認 DNS AAAA 記錄(IPv6,若有設定)
dig +short AAAA 域名.com



說明:統一使用 dig +short 搭配查詢類型放在前方的語法,避免不同 DNS 工具的顯示格式差異造成混淆。

輸出的 IP 應與你 VPS 的公網 IP 完全一致,方可繼續。





使用系統解析器(getent)交叉驗證

dig 查詢的是 DNS 伺服器的權威回應,但實際連線時,

系統與應用程式(包括 acme.sh、Nginx)使用的是系統解析器(/etc/nsswitch.conf 與本機 /etc/hosts、systemd-resolved 快取等)。

兩者結果理論上應一致,但若本機 /etc/hosts 中存在錯誤的對應項,或 systemd-resolved 快取了過期紀錄,

dig 結果正常但系統實際解析卻錯誤,這類問題不易察覺。



建議額外執行:
getent ahosts 域名.com



說明:getent ahosts 會依照系統的 NSS(Name Service Switch)設定,回傳系統實際會使用的解析結果(同時包含 IPv4 與 IPv6)。

若 dig +short A 與 getent ahosts 的結果不一致,通常代表 /etc/hosts 中有手動加入的舊紀錄,或本機 DNS 快取尚未更新,

需先排除後才繼續,否則即使 DNS 本身正確,acme.sh 在本機進行的部分檢查或 Nginx 反向查詢仍可能受到影響。













1.2 系統時間校準(重要,常被忽略)

Google Public CA 在進行 ACME 驗證時,會嚴格核對請求時間戳記。

若伺服器系統時間與標準時間差超過數分鐘,可能出現以下錯誤導致申請失敗:

badNonce(隨機數時間戳記不符)

invalid JWS(JWT 簽章時間範圍不符)


在進行後續所有步驟前,請先確認系統時間正確:
timedatectl status



預期輸出範例(關鍵欄位):
Local time: Tue 2026-06-09 10:30:00 CST
Universal time: Tue 2026-06-09 02:30:00 UTC
RTC time: Tue 2026-06-09 02:30:00
Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no



重點確認:
Time zone — 時區設定是否正確
System clock synchronized: yes  — 系統時鐘已與 NTP 同步
NTP service: active — NTP 服務正在運行



若 NTP 服務未啟用,或 System clock synchronized: no,請依之前的步驟修正:



啟用 NTP 同步
timedatectl set-ntp true


等待約 30 秒後再次確認
timedatectl status



說明:
Debian 13 預設使用 systemd-timesyncd 作為 NTP 客戶端,VPS通常已預設啟用。

但部分最小化安裝映像或 VPS 初始化後可能未啟用,務必確認後方可繼續。

如需完整的時區設定與時間同步教學,請參考:https://phpforumer.com/topic/857/








1.3 重要時效性說明

Google Public CA 發放的 EAB(External Account Binding)金鑰有效期約7天。

請務必在取得金鑰後的7天內完成帳號註冊,逾期金鑰將自動失效,屆時必須重新申請。



注意:帳號一旦以 EAB 金鑰成功註冊,該帳號即永久有效,後續續簽無需再次使用 EAB 金鑰。







1.4 確認 TCP 80 port 可對外連線

HTTP-01 驗證的原理是讓 Google 的驗證伺服器從外部網路存取你的VPS TCP 80 port,下載一個臨時驗證檔案。

若 TCP 80 port 被防火牆封鎖,驗證必然失敗。



在VPS上確認 Nginx 正在監聽 TCP 80
ss -tlnp | grep ':80'



確認防火牆規則允許 TCP 80(以 nftables 為例)
nft list ruleset | grep 80



從外部測試(在另一台電腦或使用線上工具)
curl -v http://域名.com/





雲端VPS額外注意:若使用 GCP、AWS、Azure 等雲端平台,除了 OS 層防火牆外,

還需確認雲端平台的安全群組(Security Group)或防火牆規則同樣允許 TCP 80 入站流量。

兩層防火牆皆需放行,驗證才能通過。




1.5 IPv6 監聽驗證

若 DNS 設定了 AAAA 記錄,Google CA 可能優先以 IPv6 發起 HTTP-01 驗證。

此時 Nginx 若未監聽 IPv6,驗證必然失敗,且錯誤訊息不易直接指出原因。


確認 Nginx 有監聽 IPv6 TCP 80
ss -tln | grep '\[::\]:80'



確認 Nginx 有監聽 IPv6 TCP 443
ss -tln | grep '\[::\]:443'




應分別看到 [::]:80 與 [::]:443 的監聽條目。

若沒有出現,表示 Nginx 設定檔中的 listen [::]:80; 或 listen [::]:443 ssl; 未正確套用,需排查設定後重新載入。






--------------------------------------------------
2. 在 Google Cloud Console 建立 EAB 金鑰
--------------------------------------------------

本節操作在 Google Cloud Shell(瀏覽器內的終端機)中執行,無需任何本機安裝。


2.1 建立或選取 Google Cloud 專案

前往 Google Cloud Console 首頁:

https://console.cloud.google.com/home/dashboard

1.png

2.png

登入後,在頁面頂端的專案選擇器中,選取一個現有專案,或點擊「建立專案」新增一個。

記下該專案的 專案 ID(Project ID,格式類似 my-project-123456),稍後指令中會用到。



說明:專案 ID 一旦建立即固定不變,下次更換VPS時不需要重新建立新專案,直接沿用即可。

需要重新申請的只有 EAB 金鑰(b64MacKey 和 keyId)。








2.2 開啟 Cloud Shell

在 Cloud Console 右上角點擊 Cloud Shell 圖示(>_),等待終端機視窗在頁面底部開啟。

Cloud Shell 是 Google 提供的免費瀏覽器內終端機環境,已預先安裝 gcloud CLI,無需另行設定。

進入Cloud Shell 終端機

3.png

4.png

5.png

6.png


2.3 授予 IAM 權限

在 Cloud Shell 中執行以下指令,將建立 EAB 金鑰所需的 IAM 角色授予你的帳號:


gcloud projects add-iam-policy-binding 你的專案ID \
--member=user:你的電子郵件位址 \
--role=roles/publicca.externalAccountKeyCreator


將 你的專案ID 替換為 步驟 2.1 中記錄的專案 ID,將  你的電子郵件位址  替換為你的Google帳號信箱。

說明:若你是專案的擁有者(Owner),理論上已具備此權限,但明確授予角色可以避免潛在的權限問題。








2.4 啟用 Public CA API

gcloud services enable publicca.googleapis.com


此步驟只需執行一次。若 API 已啟用,指令仍可安全重複執行,不會造成任何影響。



啟用後,立即驗證 API 確實已啟用:

gcloud services list \
  --enabled | grep publicca



預期應看到:
NAME: publicca.googleapis.com




說明:

若輸出中未出現 publicca.googleapis.com,表示 API 尚未成功啟用。

請稍候片刻(API 啟用最長需 1~2 分鐘生效),再重新執行 gcloud services enable publicca.googleapis.com 後確認。








2.5 建立 EAB 金鑰

gcloud publicca external-account-keys create



預期顯示:
Created an external account key
[b64MacKey: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 keyId: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY]




重要:請立即將 b64MacKey 與 keyId 記錄至安全的地方(如密碼管理器)。

這兩個值僅在後續步驟中使用一次,且不會再次顯示於 Google Cloud Console。

一旦遺失,只能重新建立新的 EAB 金鑰。







2.6 清除 Cloud Shell 設定並關閉

gcloud config unset api_endpoint_overrides/publicca


執行完畢後,即可關閉Cloud Shell 瀏覽器視窗。






---------------------------------
3. 建立 Nginx SSL 目錄
---------------------------------

回到你的VPS,SSH登入


在安裝 acme.sh 之前,先建立好 Nginx 存放憑證的目錄。



3.1 建立憑證目錄

mkdir -p /etc/nginx/ssl


chmod 750 /etc/nginx/ssl


chown root:www-data /etc/nginx/ssl


說明:權限 750 表示 root 可讀寫執行、www-data 群組可讀取與進入目錄、其他使用者無任何權限,確保私鑰不會被非授權程序存取。







3.2 設定 umask 以確保新建憑證檔案的安全預設權限

--install-cert 複製憑證檔案至 /etc/nginx/ssl/ 時,檔案權限由 shell 的 umask 決定。

預設 umask 022 會產生 644 權限(任何人可讀),私鑰不應如此。




在 root shell 內執行以下指令,設定本次 session 的 umask:

umask 027




說明:umask 027 使新建檔案預設為 640(owner 可讀寫,group 可讀,其他人無權限)。
此設定僅對本次 SSH session 有效;若重新登入,需再次執行。
若希望永久套用,可加入 /root/.profile,但請注意這會影響所有 root 新建檔案。
後續步驟會以 chmod 明確修正私鑰權限為 600,umask 027 僅為安全底線保障。








3.3 設定目錄與後續新增憑證的統一權限

為避免後續新增憑證時忘記手動調整每個檔案的權限,建議直接以遞迴方式設定整個目錄:


所有憑證檔案(root 可讀寫,www-data 可讀,其他人無權限):

find /etc/nginx/ssl -type f -exec chmod 640 {} \;





所有子目錄(root 可讀寫執行,www-data 可讀與進入,其他人無權限):

find /etc/nginx/ssl -type d -exec chmod 750 {} \;





確認擁有者正確:
chown -R root:www-data /etc/nginx/ssl





說明:每次執行 --install-cert 安裝新域名憑證後,建議重新執行上述 find 指令,
確保整個 /etc/nginx/ssl/ 目錄下的所有檔案與子目錄權限保持一致。
安裝憑證完成後,私鑰(.key)還需另外以 chmod 600 收緊至最小必要權限。














----------------------------------
4. 在VPS安裝 acme.sh
----------------------------------

acme.sh 是一個以純 Shell 腳本撰寫的 ACME 協定客戶端,

預設安裝至執行安裝指令的使用者家目錄(本教學為 /root/.acme.sh/)。





4.1 安裝前置相依套件

apt update && apt install -y curl socat ca-certificates











4.2 安裝 acme.sh

官方標準安裝方式(安裝至預設目錄 /root/.acme.sh/)

curl https://get.acme.sh | sh -s email=你的email


將  你的email  替換為你希望接收憑證到期提醒通知的電子郵件地址。

這個 email 地址會用於 ACME 帳號的通知用途,建議使用能長期收信的信箱。



說明:

curl https://get.acme.sh | sh -s email=...  是 acme.sh 官方推薦的標準安裝語法。

安裝腳本會自動將 acme.sh 安裝至 $HOME/.acme.sh/,並在 ~/.bashrc 中加入 alias,以及建立預設的 cron job 自動續簽排程。





預期顯示

Installing to /root/.acme.sh
Installed to /root/.acme.sh/acme.sh
Installing alias to '/root/.bashrc'
Close and reopen your terminal to start using acme.sh
Installing cron job
bash has been found. Changing the shebang to use bash as preferred.
OK
Install success!










4.3 重新載入 Shell 環境

acme.sh 安裝腳本會自動在 ~/.bashrc 中加入 alias。

SSH 互動式登入 shell 通常會載入 ~/.bashrc,

但某些情況下(例如非互動式執行、或 PATH 問題)直接將 acme.sh 加入 PATH 更為可靠。

以下指令會在 ~/.profile 中尚未有 acme.sh PATH 時補上一行,然後立即套用:


grep -q 'acme.sh' ~/.profile || \
  echo 'export PATH="$HOME/.acme.sh:$PATH"' >> ~/.profile

source ~/.profile




驗證安裝是否成功:
/root/.acme.sh/acme.sh --version




預期顯示
https://github.com/acmesh-official/acme.sh
v3.1.x







說明:

~/.bashrc 適用於互動式非登入 shell(多數 SSH 直接進入的 bash 屬於此類)。

~/.profile 適用於登入 shell 且系統未載入 ~/.bash_profile 時。

本教學同時更新 ~/.profile 並使用絕對路徑 /root/.acme.sh/acme.sh 呼叫,

確保在任何環境(包含 cron、systemd)下皆可正確執行。










4.4 啟用自動更新

讓 acme.sh 在每次自動續簽時一併更新自身至最新版本:

/root/.acme.sh/acme.sh --upgrade --auto-upgrade





預期顯示
Already up to date!
Upgrade successful!








4.5 確認並設定自動續簽排程機制

cron 與 systemd timer 只能保留其中一種,絕對不可同時啟用。

acme.sh 安裝腳本在安裝完成後,預設會在當前使用者的 crontab 中加入一個每日執行的 cron job。

本教學改用 systemd timer,必須先刪除 cron job,再建立 systemd timer。


若兩者同時存在,acme.sh 將在同一天被觸發兩次。

輕則產生無謂的 CA API 請求與日誌噪音,重則在速率限制敏感的環境中加速觸發限制,甚至在競態條件下造成憑證更新不一致。




一.確認移除 cron job 的 acme.sh 排程

這是必須執行的步驟,不可跳過:



編輯 crontab
crontab -e




刪除含有 acme.sh 那一行(類似下方內容)
5 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null




儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter






再確認 crontab 內容中已移除 acme.sh 的排程
crontab -l | grep acme



預期:無任何輸出 (代表 cron 中的 acme.sh 排程已移除)






二.systemd timer(本教學使用此方式)

Debian 13 已全面 systemd 化,

若想改用 systemd timer 取代 cron,需要手動建立 service 與 timer unit 檔案,

因為 acme.sh 安裝程式本身並不會自動建立這些檔案。



步驟一:建立 service unit


建立
vi /etc/systemd/system/acme.sh.service



貼上以下內容



[Unit]
Description=Renew ACME certificates via acme.sh
After=network-online.target
Wants=network-online.target
ConditionFileIsExecutable=/root/.acme.sh/acme.sh

[Service]
Type=oneshot
User=root
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/root/.acme.sh/acme.sh --cron --home /root/.acme.sh
StandardOutput=journal
StandardError=journal
SyslogIdentifier=acme.sh





儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter




補充說明:
此 service unit 屬於 Type=oneshot,由 timer 觸發,不需要 [Install] 區塊(不直接 enable 此 service)。
WantedBy= 僅在 timer unit 中設定即可。









步驟二:建立 timer unit


建立
vi /etc/systemd/system/acme.sh.timer



貼上以下內容



[Unit]
Description=Daily renewal of ACME certificates
Requires=acme.sh.service

[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target





儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter




說明:
OnCalendar=*-*-* 03:15:00:每天凌晨 3:15 執行,避開流量尖峰。

RandomizedDelaySec=1h:在設定時間後隨機延遲最多 1 小時,分散多台伺服器同時向 CA 發出請求的負荷。

Persistent=true:若系統在預定時間處於關機狀態,開機後會立即補跑一次,避免遺漏。

Requires=acme.sh.service:明確聲明 timer 依賴對應的 service unit,確保 systemd 能正確追蹤兩者的關聯。






步驟三:啟用 timer

systemctl daemon-reload


systemctl enable --now acme.sh.timer





確認 timer 已正常啟用:


預期顯示 enabled
systemctl is-enabled acme.sh.timer


預期顯示 active
systemctl is-active acme.sh.timer



查看下次執行時間(加 --all 可避免因 systemd 版本差異造成顯示結果不同)
systemctl list-timers --all | grep acme






步驟四:最終確認排程狀態

完成 systemd timer 設定後,請再次確認兩種排程機制的狀態,確保「acme 只有 systemd timer,沒有 cron job」:



確認 cron job 已移除 acme(應無輸出),若仍有輸出,必須再次進入 crontab -e 刪除
crontab -l 2>/dev/null | grep acme



確認 systemd timer 正在運行(應顯示 active)
systemctl is-active acme.sh.timer



啟動服務
systemctl start acme.sh.service



查看最近執行的詳細結果
journalctl -u acme.sh.service -n 50 --no-pager








補充:

--cron 與 --renew 的差異

acme.sh --cron:批次檢查所有已管理域名,僅在距到期 30 天內(或更早)才實際續簽。這是 timer/cron 的正確用法。

acme.sh --renew -d 域名.com:僅針對單一域名執行,預設同樣需距到期 30 天內才執行;加上 --force 可強制執行。

日常自動續簽排程應使用 --cron,手動測試才使用 --renew。










---------------------------------------------------------------------
5. 切換預設 CA 為 Google Public CA,並完成 EAB 帳號註冊
---------------------------------------------------------------------

acme.sh 預設 ZeroSSL 作為 CA(新版)或 Let's Encrypt(舊版)。

本教學使用 Google Public CA,需要切換並完成 EAB 帳號註冊。



5.1 切換預設 CA

使用 Google Public CA,需要切換

/root/.acme.sh/acme.sh --set-default-ca --server google





預期顯示
Changed default CA to: https://dv.acme-v02.api.pki.goog/directory





補充說明:

--server google 是 acme.sh 內建的 Google Public CA(https://dv.acme-v02.api.pki.goog/directory)的別名。

Google Trust Services 的根憑證已廣泛受到主流瀏覽器與作業系統信任,適合正式生產環境使用。

切換預設 CA 只影響後續,新申請的憑證,不影響已申請的憑證的續簽 CA。






5.2 使用 EAB 金鑰完成帳號註冊

Google Public CA 採用 EAB(External Account Binding)機制,與 Let's Encrypt 不同:acme.sh 無法直接匿名註冊帳號,

必須使用第 2 章取得的 keyId 與 b64MacKey,向 Google Public CA 完成一次性帳號註冊。

這個步驟若被跳過,後續第 8 章的 --issue 會直接失敗,並回傳類似 Account does not exist / external account required 的錯誤。




/root/.acme.sh/acme.sh --register-account \
  -m 你的電子郵件 \
  --server google \
  --eab-kid 你的keyId \
  --eab-hmac-key 你的b64MacKey





將 你的keyId 與 你的b64MacKey 替換為第 2.5 節取得並妥善保存的兩個值,

將 你的電子郵件 替換為與第 4.2 節相同(或欲使用的)通知信箱。




預期顯示
Account key creation OK.
Registering account: https://dv.acme-v02.api.pki.goog/directory
Registered
ACCOUNT_THUMBPRINT='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'





帳號註冊成功後會記錄於 /root/.acme.sh/account.conf 與 /root/.acme.sh/ca/dv.acme-v02.api.pki.goog/ 目錄下。

此後所有續簽皆使用此已註冊帳號,EAB 金鑰不會再被用到,即使7天有效期過了也不影響已完成的註冊。

若你不確定是否已完成註冊,可重複執行此指令;acme.sh 會偵測帳號已存在並直接回報成功,不會造成重複註冊的問題。












---------------------------------------
6. 設定 Nginx HTTP-01 驗證路由
---------------------------------------

HTTP-01 驗證的流程是:acme.sh 在VPS本機建立一個臨時檔案,

Google CA 的驗證伺服器從公網以 HTTP 方式存取 

http://域名.com/.well-known/acme-challenge/<token>  來確認你確實掌控該域名。

因此,Nginx 需要正確地將 /.well-known/acme-challenge/ 路徑對應到該臨時檔案所在的目錄。







6.1 建立 ACME 驗證用的根目錄

此目錄與網站根目錄分開,避免兩者互相干擾:

mkdir -p /var/www/acme-challenge






6.2 修改域名的 Nginx 設定檔

編輯你的 HTTP 虛擬主機設定:
vi /etc/nginx/sites-available/域名.com.conf




將內容修改為以下設定(完整覆蓋原有內容):



server {
    listen 80;
    listen [::]:80;
    server_name 域名.com www.域名.com;

    # 網站根目錄(目前僅供 HTTP 使用,後續 HTTPS 設定完成後可調整)
    root /var/www/域名.com;
    index index.html index.htm;

    # ACME HTTP-01 驗證專用路徑
    # ^~ 前綴確保此 location 優先於正規表達式 location
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/acme-challenge;
        default_type text/plain;
        try_files $uri =404;
    }

    # 一般請求處理
    location / {
        try_files $uri $uri/ =404;
    }
}





儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter






設定說明:

listen [::]:80; 同時啟用 IPv6 監聽,Google CA 的驗證伺服器可能以 IPv6 存取。

^~ 修飾符確保此 location 優先匹配,不會被後面的正規表達式 location 覆蓋(在 Nginx 的比對規則中,前綴 location 本就會依「最長前綴優先」,

^~ 的作用是若此前綴勝出,則跳過後續正規表達式 location 的比對)。

default_type text/plain 確保驗證檔案以純文字 MIME 類型回傳,符合 ACME 規範要求。

try_files $uri =404 若檔案不存在直接回傳 404,避免意外地重新導向或顯示其他內容。

root /var/www/acme-challenge; 這裡指定的是父層目錄,Nginx 會將完整 URI(含 /.well-known/acme-challenge/...)接在 root 之後組成實際檔案路徑,

因此實際對應到磁碟路徑為 /var/www/acme-challenge/.well-known/acme-challenge/<檔名>







6.3 確認設定檔連結已啟用

確認 sites-enabled 中有對應的符號連結:
ls -la /etc/nginx/sites-enabled/域名.com.conf





若連結不存在,需要建立 (本教學的使用者,前面步驟已有做過)

ln -s /etc/nginx/sites-available/域名.com.conf /etc/nginx/sites-enabled/域名.com.conf




常見遺漏:

Debian 官方 Nginx 套件預設啟用 /etc/nginx/sites-enabled/default,其中包含一個 listen 80 default_server; 的區塊,

會顯示 Nginx 的預設頁(或 Debian 預設頁面)。若這個 default 設定檔仍存在且啟用,

雖然「正常情況下」不會影響到 server_name 完全匹配的 域名.com,但若你的 server_name 寫錯(拼字錯誤、漏寫、大小寫不一致等),

請求就會落到這個 default_server 上,回應 Debian 預設頁面而非你的驗證檔案。


建議確認:
cat /etc/nginx/sites-enabled/default 2>/dev/null | grep -E "listen|server_name"









6.4 驗證 Nginx 設定語法並重新載入


驗證語法,若顯示 syntax is ok 與 test is successful 即表示正確
nginx -t




重新載入設定
systemctl reload nginx






------------------------------------
7. 建立驗證目錄並設定權限
------------------------------------


7.1 建立 ACME challenge 子目錄

建立目錄
mkdir -p /var/www/acme-challenge/.well-known/acme-challenge





設定目錄權限

chmod -R 755 /var/www/acme-challenge


chown -R root:root /var/www/acme-challenge





說明:

Challenge 目錄權限不建議 chown www-data,acme.sh 本身以 root 執行,不需要讓 nginx 具有寫入權限,符合最小權限原則(Least Privilege)。

雖然 acme.sh 在執行驗證時會自動建立此目錄,但提前建立並設定正確權限,可避免因權限問題導致驗證失敗,在生產環境中值得養成這個習慣。

目錄需要執行(x)位元才能被「進入」(traverse),因此目錄用 755,而非檔案用的 644。












7.2 測試驗證路徑是否可正常存取

在進行正式申請前,務必手動測試驗證路徑能否從外部存取。若此步驟失敗,申請憑證時同樣會失敗。




步驟零:確認 Nginx 設定無誤並重新載入


修改設定檔後,必須先重新載入 Nginx,否則後續的 curl 測試等於測試舊設定:


驗證語法,預期顯示 syntax is ok 與 test is successful
nginx -t




重新載入設定
systemctl reload nginx



步驟一:建立測試檔案
echo "acme-test" > /var/www/acme-challenge/.well-known/acme-challenge/testfile





步驟二:從 VPS 本機使用網域測試

curl -sv http://域名.com/.well-known/acme-challenge/testfile



預期輸出

HTTP/1.1 200 且回應內容為 acme-test







步驟三:從外部主機測試(確認公網可達)

若有其他主機(如家用電腦、手機行動網路)可用,從外部執行相同指令,確認公網可存取:

curl -sv http://域名.com/.well-known/acme-challenge/testfile


應回應 HTTP/1.1 200 與 acme-test






7.3 若 testfile 仍然回應 404 Not Found:系統化排查流程

這是最常卡關的環節,原因可能出在路徑、權限、Nginx 設定優先順序,或是論壇本身的 rewrite 規則。

請依照以下順序逐項排查,每一步都使用 域名.com 進行測試。


① 確認檔案確實存在於正確的磁碟路徑

ls -la /var/www/acme-challenge/.well-known/acme-challenge/testfile


cat /var/www/acme-challenge/.well-known/acme-challenge/testfile


若檔案不存在,重新執行步驟一;若檔案存在但內容空白,重新建立。



② 用 namei 一次檢查整條路徑的每一層權限(新手與老手都容易忽略的工具)

755 目錄權限即使設定正確,只要上層任何一層目錄缺少 x(執行/進入)權限,

Nginx worker process(以 www-data 身分執行)就無法走到最終的檔案,

回應同樣是 404,而不是 403,非常容易誤判為「路徑設定錯誤」。


namei -l /var/www/acme-challenge/.well-known/acme-challenge/testfile




輸出範例

f: /var/www/acme-challenge/.well-known/acme-challenge/testfile
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-xr-x root root acme-challenge
drwxr-xr-x root root .well-known
drwxr-xr-x root root acme-challenge
-rw-r--r-- root root testfile



逐行確認每一層目錄都至少有 r-x 給 other(最右側的三個字元),且 testfile 本身至少有 r-- 給 other。

若 /var/www 或 /var/www/acme-challenge 任一層被先前的設定改成 700 或 750 但群組非 www-data,就會在這裡看出來。



③ 確認 Nginx 實際載入並生效的設定內容(而非你「以為」生效的設定)

nginx -t 只檢查語法,不代表你修改的那個檔案真的被載入、或沒有被其他檔案的同名 server_name 覆蓋。

請用 -T(大寫)直接列印 Nginx 合併後的完整生效設定:


nginx -T | grep -n -A 8 "server_name.*域名.com"



確認:

確實有一個 server { listen 80; ... server_name 域名.com ...} 區塊。

該區塊內含有 location ^~ /.well-known/acme-challenge/ 且 root /var/www/acme-challenge;。

沒有「拼字不同」的第二個重複 server_name(例如 域名.com  結尾多了空白或不可見字元、

www.域名.com 與 域名.com 分別寫在兩個不同檔案、但其中一個忘了加 ^~ location)。




④ 確認沒有「conflicting server name」被忽略的設定檔

若同一個 server_name 出現在多個 .conf 檔案中(例如同時存在新舊版本、或 域名.com.conf 與 域名.com.conf.bak 都被 include),

Nginx 只會使用第一個載入的那份,其餘的會在啟動或 reload 時於錯誤日誌留下 conflicting server name "域名.com" ... ignored 的警告,

而你修改的可能正是被忽略的那一份:


nginx -t 2>&1 | grep -i conflict
journalctl -u nginx --since "10 min ago" | grep -i conflict




⑤ 確認該 location 是否被「論壇的 rewrite 規則」搶先處理

若你已經依照之前的教學部署了論壇的 Nginx 設定,原始設定中常見如下的 catch-all:

location / {
    try_files $uri $uri/ /index.php?$query_string;
}


由於 /.well-known/acme-challenge/ 這個前綴(長度 28 個字元)比 /(長度 1 個字元)更長,

正常情況下 Nginx 的最長前綴比對規則會讓 ^~ /.well-known/acme-challenge/ 優先勝出,不會被論壇的 location / 搶走。

但若你在新增 acme-challenge 的 location 時:

漏掉了 ^~ 前綴,且論壇設定中還有一個比 /.well-known/acme-challenge/ 更長或同樣使用正規表達式且優先權更高的 location(例如 location ~ ^/(.*)$),

就有可能被攔截。

或是把 acme-challenge 的 location 加在了論壇的 443 (HTTPS) server block,但 HTTP-01 驗證走的是 80 port,自然 404。



排查方式:用 curl -v 觀察回應內容是 Nginx 原生的 404(標頭通常只有 Server: nginx,body 很簡短),

還是論壇自訂的 404 頁面(通常是完整 HTML,含論壇的版型、Server: nginx 之外可能還有 X-Powered-By: PHP/...):

curl -sv http://域名.com/.well-known/acme-challenge/testfile



若回應是論壇樣式的 HTML 404 頁面 → 代表請求被 PHP-FPM / 論壇接管,acme-challenge 的 location 沒有匹配到,

請回到 6.2 節確認該 location 寫在「80 port、server_name 正確比對到 域名.com」的 server block 內,

且 ^~ 前綴存在、路徑結尾的斜線 / 沒有漏打。


若回應是極簡短的 Nginx 原生 404(例如純文字 404 Not Found 加上 nginx 字樣)→ 代表 location 本身已匹配,

但磁碟路徑或權限有問題,回到 ① ② 排查。






⑥ 查看 Nginx 錯誤日誌的第一手線索

tail -n 50 /var/log/nginx/error.log



重點關注:

open() "/var/www/acme-challenge/.well-known/acme-challenge/testfile" failed (2: No such file or directory) → 路徑不一致,

回到 ① 與 ③ 重新核對 root 設定與實際建立的目錄是否完全對應。

failed (13: Permission denied) → 權限問題,回到 ②。

完全沒有任何相關紀錄 → 代表這個 location 根本沒有被匹配到,回到 ⑤。




⑦ 確認沒有反向代理 / CDN 介入

若該網域的 DNS 是透過 Cloudflare 等服務代理(橙色雲朵 / Proxied 模式),

則 curl http://域名.com/... 實際連到的是 CDN 節點,而非你的VPS。

CDN 可能快取了舊的 404 回應,或本身也有獨立的路由規則。

請暫時將 DNS 改為「僅 DNS」模式(灰色雲朵)直連 VPS 完成憑證申請與測試後,

再視需求改回代理模式(並確認該 CDN 是否支援透傳 /.well-known/acme-challenge/)。





測試完畢後務必刪除測試檔案

rm /var/www/acme-challenge/.well-known/acme-challenge/testfile


重要:
測試檔案用畢必須立即刪除,避免洩露伺服器資訊(雖然 testfile 本身內容無害,但保持驗證目錄乾淨、僅在實際申請時才有檔案存在,

是較嚴謹的做法,也方便日後排查問題時不會被舊測試檔案干擾)。

確認 http://域名.com/.well-known/acme-challenge/testfile 此時應回應 404(檔案已刪除),這是預期的正常結果。













--------------------------------------------------
8. 申請憑證
--------------------------------------------------

確認第 7.2 節的測試成功(回應 200 與 acme-test)後,即可正式申請憑證。


8.1 使用 Webroot 模式申請憑證


/root/.acme.sh/acme.sh --issue \
  -d 域名.com -d www.域名.com \
  -w /var/www/acme-challenge \
  -k ec-256






參數說明

-d 域名.com -d www.域名.com:同一張憑證同時包含「裸網域」與 www 子網域(SAN,Subject Alternative Name)。

若你的論壇不使用 www 子網域,可移除 -d www.域名.com,但要確保使用者實際存取的網址都包含在 -d 清單中,否則瀏覽器會出現「憑證與網址不符」的警告。

-w /var/www/acme-challenge:Webroot 模式,acme.sh 會自行在此目錄下建立 .well-known/acme-challenge/<token> 驗證檔案。

這個路徑必須與第 6.2 節 Nginx 設定中的 root /var/www/acme-challenge; 完全一致,

否則 Nginx 找不到 acme.sh 建立的驗證檔案,憑證申請會失敗(與第 7.3 節的排查邏輯相同)。

-k ec-256:使用 ECC P-256 金鑰演算法,憑證體積小、TLS 交握效能較佳,現代瀏覽器與作業系統均已廣泛支援。

由於第 5 章已設定 --set-default-ca --server google 並完成 EAB 帳號註冊,此處不需要再額外加上 --server google 或 --eab-kid / --eab-hmac-key




8.2 預期顯示


成功時會看到類似

Verifying: test.phpforumer.com
Processing. The CA is processing your order, please wait. (1/30)
Success
Verification finished, beginning signing.
Let's finalize the order.
Le_OrderFinalize='https://dv.acme-v02.api.pki.goog/order/xxxxxxxxxxx'
Order status is 'processing', let's sleep and retry.
Polling order status: https://dv.acme-v02.api.pki.goog/order/xxxxxxxxxxx
Downloading cert.
Le_LinkCert='https://dv.acme-v02.api.pki.goog/cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Cert success.
...........
Your cert is in: /root/.acme.sh/域名.com_ecc/域名.com.cer
Your cert key is in: /root/.acme.sh/域名.com_ecc/域名.com.key
The intermediate CA cert is in: /root/.acme.sh/域名.com_ecc/ca.cer
And the full-chain cert is in: /root/.acme.sh/域名.com_ecc/fullchain.cer




憑證相關檔案會儲存於 /root/.acme.sh/域名.com_ecc/ 目錄下:

檔案:域名.com.key
用途:私鑰

檔案:域名.com.cer
用途:伺服器憑證(不含中繼憑證)

檔案:fullchain.cer
用途:伺服器憑證 + 中繼憑證(Nginx 應使用此檔案)

檔案:ca.cer
用途:中繼/根憑證




重要:

/root/.acme.sh/ 目錄下的檔案是 acme.sh 的內部工作目錄,不要直接將 Nginx 的 ssl_certificate 指向此目錄,

也不要直接編輯或移動其中的檔案。正確做法是透過 --install-cert 指令,讓 acme.sh 把憑證複製一份到 /etc/nginx/ssl/,並在每次續簽後自動覆蓋更新。

這樣即使未來 acme.sh 的內部目錄結構變動,也不會影響 Nginx 的設定路徑。










-------------------------------
9. 將憑證安裝至 Nginx
-------------------------------


9.1 執行 --install-cert


/root/.acme.sh/acme.sh --install-cert -d 域名.com -d www.域名.com \
  --key-file       /etc/nginx/ssl/域名.com.key \
  --fullchain-file /etc/nginx/ssl/域名.com.fullchain.pem \
  --reloadcmd      "/usr/sbin/nginx -t && /usr/bin/systemctl reload nginx"






預期顯示

The domain '域名.com' seems to already have an ECC cert, let's use it.
Installing key to: /etc/nginx/ssl/域名.com.key
Installing full chain to: /etc/nginx/ssl/域名.com.fullchain.pem
Running reload cmd: /usr/sbin/nginx -t && /usr/bin/systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload successful








安裝完成後確認憑證檔案存在
ls -lh /etc/nginx/ssl/



應看到至少兩個檔案(此時擁有者為 root,下一步會調整為 root:www-data)
-rw-r--r-- 1 root root   ... 域名.com.fullchain.pem
-rw------- 1 root root   ... 域名.com.key




重要提醒:--install-cert 僅需執行一次

--install-cert 僅需在以下情況執行:

(1)首次設定憑證,將憑證路徑與 --reloadcmd 寫入設定

(2)日後變更憑證存放路徑或 reload 指令時


執行後,路徑與 --reloadcmd 會儲存至:~/.acme.sh/域名.com_ecc/域名.com.conf

後續每次自動續簽完成,acme.sh 會依照上述設定,

自動更新 /etc/nginx/ssl/ 下的憑證檔案並執行 --reloadcmd 重新載入 Nginx,

完全不需要人工介入,亦無需重新執行 --install-cert。










--------------------------------
10. 憑證與目錄的權限
--------------------------------


10.1 設定憑證與目錄的安全權限

私鑰外洩等同於憑證完全失效,務必確認以下權限設定正確。

設定個別檔案權限(初次安裝後立即執行):


私鑰:僅 root 可讀寫,其他人(含 www-data)均無權限。Debian官方建議私鑰使用 600,最小化存取面:

chmod 600 /etc/nginx/ssl/域名.com.key






完整憑證鏈:root 可讀寫,www-data 可讀(Nginx 讀取憑證鏈所需):

chmod 640 /etc/nginx/ssl/域名.com.fullchain.pem




設定擁有者:

chown root:www-data /etc/nginx/ssl/域名.com.key


chown root:www-data /etc/nginx/ssl/域名.com.fullchain.pem






以遞迴方式確保整個目錄的權限一致(建議每次新增憑證後執行),並在之後重新將私鑰收緊為 600:

find /etc/nginx/ssl -type f -exec chmod 640 {} \;


find /etc/nginx/ssl -type d -exec chmod 750 {} \;


chown -R root:www-data /etc/nginx/ssl



重新收緊私鑰權限(find 的 640 覆蓋了 .key,需再次設為 600)
chmod 600 /etc/nginx/ssl/域名.com.key





安全說明:

項目:.key 私鑰
正確權限:600 root:www-data
說明:僅 root 可讀寫。600 比 640 更嚴謹;Nginx master process 以 root 啟動,可直接讀取,不需 www-data group 讀取權。
絕對不可設為 644(任何人可讀)


項目:.fullchain.pem
正確權限:640 root:www-data
說明:root 可讀寫,www-data 可讀。憑證鏈為公開資訊,640 已足夠


項目:/etc/nginx/ssl/ 目錄
正確權限:750 root:www-data
說明:防止其他用戶列出目錄內容


若私鑰外洩,攻擊者可偽造你的伺服器身份,需立即向 CA 申請憑證撤銷(revoke)並重新申請。




權限更改後,再次確認:
ls -lh /etc/nginx/ssl/



應看到至少兩個檔案:
-rw-r----- 1 root www-data ... 域名.com.fullchain.pem
-rw------- 1 root www-data ... 域名.com.key








10.2 驗證自動續簽的 reload 是否已正確設定

--install-cert 在執行時,會將 --reloadcmd 的內容寫入該域名的設定檔(域名.com.conf)。

這個欄位(Le_ReloadCmd)正是「自動續簽完成後,acme.sh 是否會重新載入 Nginx」的關鍵。

若這裡沒有正確設定,憑證會自動更新,但 Nginx 仍會持續使用舊憑證,直到下次手動重新載入。

這是新手與老手都容易忽略、卻在數十天後(憑證快到期時)才會爆發的問題。




安裝完成後,立即執行以下指令確認:

grep Le_ReloadCmd \
  ~/.acme.sh/域名.com_ecc/域名.com.conf



應看到

Le_ReloadCmd='__ACME_BASE64__START_L3Vzxxxxxxxxxxxxxxbmdpbng=__ACME_BASE64__END_'





使用下面指令將 Base64 解碼:

grep Le_ReloadCmd ~/.acme.sh/域名.com_ecc/域名.com.conf \
| cut -d"'" -f2 \
| sed 's/^__ACME_BASE64__START_//' \
| sed 's/__ACME_BASE64__END_$//' \
| base64 -d




應看到

/usr/sbin/nginx -t && /usr/bin/systemctl reload nginx


若沒有看到這一行,或欄位內容為空,代表 --install-cert 並未正確執行(例如指令中斷、或誤用了 --issue 而非 --install-cert),

請重新執行本章開頭的 --install-cert 指令。


補充:為何要使用絕對路徑 /usr/sbin/nginx 與 /usr/bin/systemctl?

acme.sh 的自動續簽是透過 systemd timer(或 cron)以非互動式 shell 執行,

這類環境的 PATH 環境變數通常不包含 /usr/sbin,若 --reloadcmd 寫成 nginx -t && systemctl reload nginx(不含絕對路徑),

自動續簽時會出現 command not found,導致 reload 靜默失敗,而手動執行卻一切正常,造成排查上的混淆。











--------------------------------------------
11. 設定 Nginx HTTPS 虛擬主機
--------------------------------------------

憑證安裝完成後,需更新 Nginx 設定以啟用 HTTPS。


編輯
vi /etc/nginx/sites-available/域名.com.conf




將內容更新為完整的 HTTP + HTTPS 設定



# ============================================================
# /etc/nginx/sites-available/域名.com.conf
# Debian 13 + Nginx 1.30.x + acme.sh + Google Trust Services
# ============================================================

# HTTP:僅處理 ACME 驗證,其餘請求強制重新導向至 HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name 域名.com www.域名.com;

    # ACME HTTP-01 驗證路徑(保留,供日後續簽使用)
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/acme-challenge;
        default_type text/plain;
        try_files $uri =404;
    }

    # 其餘所有 HTTP 請求重新導向至 HTTPS(301 永久重新導向)
    # 使用 $host 而非 $server_name,避免 server_name 有多個值時導向至第一個值
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS:主要服務設定
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name 域名.com www.域名.com;

    # 隱藏 Nginx 版本號(安全強化)
    # 防止攻擊者透過 Server 標頭得知 Nginx 版本,降低版本指紋洩露風險
    # 注意:server_tokens 亦需在 nginx.conf 的 http {} 區塊中設定
    # 此處設定可覆蓋主設定檔中的值,確保本虛擬主機強制隱藏版本
    server_tokens off;

    # 憑證設定(由 acme.sh --install-cert 安裝)
    ssl_certificate     /etc/nginx/ssl/域名.com.fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/域名.com.key;

    # TLS 協定版本(僅允許 TLS 1.2 與 TLS 1.3)
    ssl_protocols TLSv1.2 TLSv1.3;

    # 橢圓曲線(ECC 金鑰交換優先順序)
    # X25519:效能最佳,現代瀏覽器(Chrome、Firefox、Safari)均優先選用
    # secp384r1(P-384):NIST 高安全等級曲線,備援用途
    # prime256v1(P-256):相容性最廣,適用於較舊的用戶端
    # 2GB RAM VPS 完全足以支援此三曲線組合,不造成額外記憶體壓力
    ssl_ecdh_curve X25519:secp384r1:prime256v1;

    # TLS 1.3 Early Data(0-RTT):關閉
    # 0-RTT 存在重放攻擊(Replay Attack)風險
    # 論壇網站含有登入與 POST 請求,生產環境必須關閉
    ssl_early_data off;

    # 加密套件
    # TLS 1.3 套件由 OpenSSL 自動管理,無需手動指定
    # TLS 1.2 使用 ECDHE + AEAD 安全套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

    # 優先使用用戶端的加密套件順序(更適合現代用戶端自主選擇最佳套件)
    ssl_prefer_server_ciphers off;

    # SSL Session 快取(減少 TLS 握手開銷,提升效能)
    # shared:SSL:10m 可快取約 80,000 個 session(2 GB RAM 完全沒問題)
    # ssl_session_timeout 1d:session 有效期 1 天
    # ssl_session_tickets off:禁用 Session Ticket,提升前向保密性(Forward Secrecy)
    # 避免 ticket key 外洩導致歷史流量被解密
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # TLS 記錄緩衝區大小
    # 預設 16k 對高延遲連線較耗記憶體
    # 設為 8k 可降低 TTFB(Time To First Byte),減少記憶體消耗
    # 適合以 HTML 回應為主的論壇環境
    # 若有大量大型靜態檔案傳輸(附件下載、高解析圖片)可評估調回 16k
    ssl_buffer_size 8k;

    # OCSP Stapling:已停用
    # Google Trust Services 與 Let's Encrypt 均已於 2025 年停止在憑證中嵌入 OCSP URL,OCSP 回應伺服器已完全關閉。
    # 若仍啟用 ssl_stapling,Nginx 啟動時會出現
    # [warn] "ssl_stapling" ignored 警告,並可能導致啟動延遲。
    # 現代憑證驗證已改以 CRL(Certificate Revocation List)為主。
    ssl_stapling        off;
    ssl_stapling_verify off;

    # 若日後重新啟用 ssl_stapling(例如改用其他支援 OCSP 的 CA),
    # 請將以下三行取消註解:
    # ssl_trusted_certificate /etc/nginx/ssl/域名.com.fullchain.pem;
    # resolver 1.1.1.1 1.0.0.1 8.8.8.8 valid=300s ipv6=off;
    # resolver_timeout 5s;

    # 連線保持時間
    # 預設 75s 在高併發論壇環境下可能佔用過多 worker connection
    # 設為 30s 可加速連線釋放,降低並發下的資源浪費
    keepalive_timeout 30;

    # 單個 keep-alive 連線允許的最大請求數
    # 預設 1000,對論壇環境已足夠;可依實際流量調整
    keepalive_requests 1000;

    # 允許的最大請求體大小(論壇附件上傳必要)
    # IPS論壇預設附件大小限制請與此值保持一致
    # 若允許更大的附件,需同步調高 PHP upload_max_filesize 與 post_max_size
    client_max_body_size 200m;

    # 日誌設定
    # 路徑須與 Fail2ban jail.local 中的 logpath 完全一致,否則 Fail2ban 無法讀取日誌進行封鎖
    # Debian 官方 Nginx 套件預設的 log format 名稱為 combined,
    # 若 nginx.conf 中沒有自訂 main 格式,請使用 combined 格式名稱
    access_log /var/log/nginx/域名.com.access.log combined;
    error_log  /var/log/nginx/域名.com.error.log warn;


    # 安全性相關 HTTP 標頭
    # 注意:X-XSS-Protection 已淘汰(現代瀏覽器忽略),不加入

    # HSTS:強制瀏覽器以 HTTPS 存取
    # 初始設為 86400(1 天)以便測試,確認 HTTPS 完全正常後
    # 逐步調高至 2592000(30 天)→ 31536000(1 年)
    # 警告:加入 preload 並提交至 HSTS preload list 後,
    # 移除需要數月時間生效,操作失誤將導致網站完全無法以 HTTP 存取
    # 請在完全確認 HTTPS 正常且無回退需求後方可加入 preload
    add_header Strict-Transport-Security "max-age=86400" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Permissions-Policy:限制瀏覽器功能存取(依需求調整)
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Content-Security-Policy:請依實際站台需求調整後再啟用
    # IPS Community Suite 使用較多 inline script,啟用前請仔細測試
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; ..." always;

    # 網站根目錄(依實際情況調整)
    root  /var/www/域名.com;
    index index.html index.htm index.php default.html default.htm default.php;

    # 主要路由(IPS Community Suite)
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # IPS 論壇附件目錄:禁止執行 PHP
    # 使用巢狀 location 而非單純 regex,避免 regex 優先權問題
    # ^~ 確保此前綴 location 優先匹配 /uploads/ 路徑,
    # 巢狀的 ~ \.php$ 再於前綴範圍內攔截所有 PHP 請求並拒絕
    location ^~ /uploads/ {
        location ~ \.php$ {
            deny all;
        }
    }

    # PHP-FPM(FastCGI)
    location ~ \.php$ {
        # 防止 Nginx 將不存在的 PHP 檔案路徑傳給 PHP-FPM
        # 避免路徑遍歷攻擊(path traversal)
        try_files $uri =404;

        include snippets/fastcgi-php.conf;

        # 請確認 socket 路徑與實際 PHP-FPM 版本一致
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;

        # 隱藏 PHP 版本資訊,降低版本指紋風險
        fastcgi_hide_header X-Powered-By;


        # 限制 PHP 可存取的目錄(open_basedir)
        # 防止 PHP 讀取 /var/www/域名.com/ 與 /tmp/ 以外的檔案
        # 強制開啟 open_basedir 可能造成:第三方外掛、圖片處理異常。
        # 建議先不啟用,確認所有插件正常後再評估開啟。
        # fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/域名.com/:/tmp/";


        # 確保 Nginx 傳遞用戶端真實IP 至 PHP
        fastcgi_param REMOTE_ADDR $remote_addr;

        # 傳遞真實IP(若前端有 CDN 或反向代理,可改為 $http_x_forwarded_for)
        fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;

        # FastCGI 超時設定(適合論壇附件上傳等長請求)
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout    300s;
        fastcgi_read_timeout    300s;
    }

    # Gzip 壓縮(降低傳輸量,提升載入速度)
    # 注意:gzip_types 中不應包含 image/jpeg、image/png 等已壓縮格式
    # 對已壓縮的二進位檔案再次壓縮不僅無益,反而浪費 CPU
    gzip on;
    gzip_comp_level   5;
    gzip_min_length   256;
    gzip_vary         on;
    gzip_proxied      any;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/rss+xml
        application/atom+xml
        text/xml
        image/svg+xml
        font/woff
        font/woff2
        application/font-woff
        application/font-woff2
        application/x-font-ttf
        image/x-icon;


    # ── 靜態資源快取 ─────────────────────────────────────────
    # 注意:此 location 區塊內存在 add_header,
    # 因此父層 server {} 中的所有 add_header 對此 location 均不生效。
    # (Nginx add_header 繼承規則:子層有 add_header 時,完全覆蓋而非合併父層)
    # 必須在此區塊內重新宣告所有安全性標頭,否則靜態資源回應將遺漏 HSTS 等標頭。
    # 若日後新增或修改安全標頭,記得同步更新此區塊。
    location ~* \.(jpg|jpeg|png|bmp|gif|ico|webp|svg|woff|woff2|ttf|css|js)$ {
        expires    7d;
        add_header Cache-Control                 "public, immutable";
        add_header Strict-Transport-Security     "max-age=86400" always;
        add_header X-Frame-Options               "SAMEORIGIN"    always;
        add_header X-Content-Type-Options        "nosniff"       always;
        add_header Referrer-Policy               "strict-origin-when-cross-origin" always;
        add_header Permissions-Policy            "geolocation=(), microphone=(), camera=()" always;
        access_log off;
    }

    # ── 禁止存取危險檔案 ─────────────────────────────────────
    # 防止意外洩露伺服器資訊
    location ~* \.(env|sql|bak|log|ini|sh|conf|key|pem)$ {
        deny all;
        access_log    off;
        log_not_found off;
    }

    # 禁止存取隱藏檔案(.htaccess、.git、.env 等)
    location ~ /\. {
        deny all;
        access_log    off;
        log_not_found off;
    }
}



儲存檔案並離開vi編輯器
按 Esc,輸入 :wq,按 Enter








11.1 隱藏 Nginx 版本號的補充說明

server_tokens off;  設定除了在 server {} 區塊中加入外,

也必須在主設定檔 /etc/nginx/nginx.conf 的 http {} 區塊中設定,才能對所有虛擬主機生效:


vi /etc/nginx/nginx.conf


確認 http {} 區塊中包含:

http {
    server_tokens off;
    # ... 其他設定 ...
}






說明:

server_tokens off 可防止 Nginx 在錯誤頁面及 Server HTTP 回應標頭中洩露版本號碼(例如 nginx/1.30.x),

降低攻擊者針對已知版本漏洞的攻擊面。

虛擬主機設定檔中的 server_tokens off; 可覆蓋主設定值,

但建議兩處皆設定,確保一致性。






11.2 Nginx 設定關鍵參數說明彙整


參數:server_tokens
建議值:off
說明:隱藏 Nginx 版本號,降低指紋洩露風險

參數:http2 on
建議值:啟用
說明:啟用 HTTP/2(Nginx 1.25.1+ 語法,取代舊版 listen 443 ssl http2;)

參數:ssl_protocols
建議值:TLSv1.2 TLSv1.3
說明:僅允許安全的 TLS 版本

參數:ssl_ecdh_curve
建議值:X25519:secp384r1:prime256v1
說明:X25519 效能最佳,secp384r1 高安全備援,prime256v1 廣泛相容

參數:ssl_early_data
建議值:off
說明:關閉 0-RTT,防止重放攻擊

參數:ssl_session_cache
建議值:shared:SSL:10m
說明:減少 TLS 握手開銷

參數:ssl_session_timeout
建議值:1d
說明:Session 有效期 1 天

參數:ssl_session_tickets
建議值:off
說明:提升前向保密性

參數:ssl_buffer_size
建議值:8k
說明:減少 TLS 記憶體消耗、降低 TTFB

參數:ssl_prefer_server_ciphers
建議值:off
說明:優先使用用戶端套件選擇(現代最佳實踐)

參數:keepalive_timeout
建議值:30
說明:避免高併發下連線資源浪費

參數:keepalive_requests
建議值:1000
說明:單一 keep-alive 連線最大請求數

參數:client_max_body_size
建議值:200m
說明:論壇附件上傳必要,需與 PHP 設定同步

參數:ssl_stapling
建議值:off
說明:Google CA 與 Let's Encrypt 已於 2025 年停用 OCSP






11.3 HSTS 設定注意事項

初始設為 max-age=86400(1 天)便於測試與回退。

確認 HTTPS 完全正常後,逐步調高:max-age=2592000(30 天)→ max-age=31536000(1 年)。

請勿在尚未確認 HTTPS 正常運作前貿然加入 preload 指令。

一旦加入並提交至 HSTS preload list,所有主流瀏覽器將強制以 HTTPS 存取你的域名,

移除需要數月時間生效,操作失誤將導致網站完全無法以 HTTP 存取。





11.4 add_header 繼承陷阱說明

Nginx 的 add_header 有一個常被忽略的繼承規則:當子層的 location {} 區塊中存在任何 add_header 指令時,

父層 server {} 中的所有 add_header 指令對該 location {} 均不生效(不是合併,而是完全覆蓋)。

本設定中的靜態資源 location ~* \.(jpg|jpeg|...)$ 區塊包含 Cache-Control 的 add_header,

因此已在該區塊內重新宣告所有安全性標頭,確保靜態資源回應中不會遺漏 HSTS、X-Frame-Options 等標頭。

若日後新增或修改安全標頭,記得同步更新靜態資源 location {} 區塊內的標頭清單。






11.5 驗證語法並重新載入


驗證語法
nginx -t



預期輸出
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful





重新載入
systemctl reload nginx







-------------------------------
12. 驗證憑證安裝結果
-------------------------------

12.1 憑證鏈完整性驗證

fullchain.pem 包含兩張憑證:第一張為終端憑證(End-Entity Certificate),第二張為中繼憑證(Intermediate Certificate)。

openssl verify 預設只讀取並驗證檔案中的第一張憑證(終端憑證),並用 -CAfile 指定的根憑證庫進行信任鏈驗證。
為了讓 openssl 能找到終端憑證的簽發者(中繼憑證),需要使用 -untrusted 參數將 fullchain.pem 中的中繼憑證提供為「不信任的中間鏈」:


openssl verify \
  -CAfile /etc/ssl/certs/ca-certificates.crt \
  -untrusted /etc/nginx/ssl/域名.com.fullchain.pem \
  /etc/nginx/ssl/域名.com.fullchain.pem




預期輸出
/etc/nginx/ssl/域名.com.fullchain.pem: OK




說明:-untrusted 參數告訴 openssl:「這個檔案中有中繼憑證可用於建構信任鏈,但不視為可信任的根憑證」。
-CAfile 才是根憑證的來源。兩個參數指向同一個 fullchain.pem 是正確且必要的用法——前者提供中繼憑證,後者提供要被驗證的終端憑證。





若出現以下錯誤:error 20 at 0 depth lookup: unable to get local issuer certificate

這通常表示 fullchain.pem 中只包含終端憑證,缺少中繼憑證(即誤用了 --cert-file 而非 --fullchain-file)。

可手動從 acme.sh 內部目錄複製完整鏈重新修正:
cp /root/.acme.sh/域名.com_ecc/fullchain.cer \
    /etc/nginx/ssl/域名.com.fullchain.pem


重設權限
chmod 640 /etc/nginx/ssl/域名.com.fullchain.pem
chown root:www-data /etc/nginx/ssl/域名.com.fullchain.pem
複製後重新執行驗證指令確認。




注意:openssl verify 的已知限制

某些 OpenSSL 版本對 fullchain.pem 的信任鏈驗證方式較嚴格。
即使搭配 -untrusted 參數,部分環境仍可能出現 error 20,但實際 TLS 服務完全正常。
因此,openssl verify 適合用於快速確認憑證檔格式是否完整,但不應作為正式環境的唯一驗證依據,必須搭配下方的 TLS 握手驗證。







正式環境推薦驗證方式:openssl s_client TLS 握手驗證

openssl s_client \
  -connect 域名.com:443 \
  -servername 域名.com \
  -showcerts </dev/null




重點確認以下輸出

Certificate chain 包含多張憑證 — 中繼憑證已正確傳遞

subject = CN = 域名.com — 憑證主體正確

issuer = ... Google Trust Services — 由 Google Public CA 簽發

Verify return code: 0 (ok) — 憑證鏈驗證通過






12.2 確認憑證主體與 SAN

openssl x509 \
  -in /etc/nginx/ssl/域名.com.fullchain.pem \
  -noout \
  -subject \
  -ext subjectAltName



應可看到 CN = 域名.com 以及 DNS:域名.com, DNS:www.域名.com





12.3 確認憑證到期日

openssl x509 \
  -in /etc/nginx/ssl/域名.com.fullchain.pem \
  -noout \
  -enddate



預期輸出
notAfter=MMM DD HH:MM:SS 2026 GMT






12.4 確認憑證簽發者(Issuer)

openssl x509 \
  -in /etc/nginx/ssl/域名.com.fullchain.pem \
  -noout \
  -issuer



應可看到
issuer= ... Google Trust Services ...,確認憑證確實由 Google Public CA 簽發。






12.5 確認私鑰與憑證匹配(常被忽略)

確保私鑰與憑證是一對,防止安裝時發生錯誤導致 Nginx 啟動失敗:


比對私鑰與憑證的公鑰指紋,兩行輸出應完全相同


openssl pkey -in /etc/nginx/ssl/域名.com.key -pubout 2>/dev/null | openssl sha256


openssl x509 -in /etc/nginx/ssl/域名.com.fullchain.pem -pubkey -noout 2>/dev/null | openssl sha256



兩行輸出的 SHA256 值必須完全一致,才代表私鑰與憑證正確匹配。

若不一致,表示憑證與私鑰來自不同的申請,需重新執行 --install-cert 或重新申請憑證。








12.6 透過 curl 確認 HTTPS 連線與回應標頭

curl -vkI https://域名.com



輸出中,重點確認以下項目:

SSL connection using TLSv1.3(或 TLSv1.2)→ 確認協定版本正確

Server certificate: 區塊中的 subject 與 issuer → 確認憑證主體與簽發者

HTTP/2 200(或其他 2xx 狀態碼)→ 確認伺服器正常回應

strict-transport-security 標頭 → 確認 HSTS 已啟用

Server: nginx(而非 nginx/版本號)→ 確認 server_tokens off 生效











12.7 驗證 TLS 1.3 可正常運作

確認 TLS 1.3 協定可正常使用,而不只是 TLS 1.2 回退:

curl --tlsv1.3 -I https://域名.com




預期輸出(節錄):
HTTP/2 200
...



若 curl 返回錯誤(如 SSL routines: no protocols available),表示 TLS 1.3 可能未正確啟用,

需確認 Nginx 的 ssl_protocols 設定包含 TLSv1.3,以及伺服器端 OpenSSL 版本是否支援(Debian 13 的 OpenSSL 3.x 完全支援)。







也可使用 openssl 明確指定 TLS 1.3 進行握手確認:

openssl s_client \
  -connect 域名.com:443 \
  -tls1_3 \
  -servername 域名.com \
  </dev/null 2>&1 | grep -E "Protocol|Cipher"



預期輸出(節錄):
Protocol  : TLSv1.3
Cipher    : TLS_AES_256_GCM_SHA384










12.8 IPv6 連線驗證

若有設定 DNS AAAA 記錄,額外確認 IPv6 HTTPS 連線正常:


確認 Nginx 監聽 IPv6 TCP 443
ss -tln | grep '\[::\]:443'



透過 IPv6 測試 HTTPS 連線(需本機支援 IPv6)
curl -6 https://域名.com -vI









------------------------------------------
13. 驗證憑證狀態與自動續簽
------------------------------------------

13.1 查看憑證清單與到期日

/root/.acme.sh/acme.sh --list



輸出範例
Main_Domain     KeyLength  SAN_Domains        Profile  CA        Created                Renew
域名.com	"ec-256"       www.域名.com       Google.com     2025-12-18        2026-02-15





各欄位說明

欄位:Main_Domain
說明:主域名

欄位:KeyLength
說明:ec-256 表示 ECC P-256

欄位:CA
說明:簽發機構,此處為 Google

欄位:Created
說明:憑證申請日期

欄位:Renew
說明:預計自動續簽日期(距到期約30天前)







13.2 本機直接查詢憑證到期日

openssl x509 \
  -in /etc/nginx/ssl/域名.com.fullchain.pem \
  -noout \
  -enddate



輸出範例
notAfter= xxxxxxxxxx 2026 GMT








13.3 透過 TLS 握手確認線上憑證

此指令從用戶端角度模擬完整 TLS 握手,確認 Nginx 實際提供的是正確且最新的憑證(而非僅讀取本機檔案):

openssl s_client \
  -connect 域名.com:443 \
  -servername 域名.com \
  </dev/null 2>/dev/null | openssl x509 -noout -dates -subject



預期顯示
notBefore=xxxxxxxxxxxxx 2026 GMT
notAfter=xxxxxxxxxxxxx 2026 GMT
subject=CN=域名.com








13.4 確認自動續簽排程

查看所有 systemd timer 的下次執行時間,確認 acme.sh timer 存在且排程正常


查看全部 timer(加 --all 避免 systemd 版本差異造成顯示結果不同)
systemctl list-timers --all


只查看 acme timer
systemctl list-timers --all | grep acme



NEXT 欄位:下次執行時間(若為空或 timer 不存在,需排查)
LAST 欄位:上次執行時間
UNIT 欄位:確認 timer 名稱






快速確認 timer 與 service 狀態


確認 timer 是否正常運行
systemctl status acme.sh.timer


確認最近一次 service 執行結果
systemctl status acme.sh.service


查看 acme.sh service 的歷史執行紀錄
journalctl -u acme.sh.service --since "7 days ago"



查看 acme.sh 記錄的下次續簽時間(Unix timestamp)
grep Le_NextRenewTime ~/.acme.sh/域名.com_ecc/域名.com.conf


預期顯示
Le_NextRenewTimeStr='2026-xx-xx xxxxxxxxxx'
Le_NextRenewTime='178xxxxxx'





轉換為人類可讀格式(Debian / GNU date 語法)

date -d @$(grep "^Le_NextRenewTime=" \
  ~/.acme.sh/域名.com_ecc/域名.com.conf \
  | cut -d= -f2 | tr -d "'") '+%Y-%m-%d %H:%M:%S %Z'



預期顯示
2026-xx-xx xxxxxxxx CST







13.5 測試自動續簽流程

在首次申請憑證後,建議立即進行一次續簽測試,確認整個自動流程正常運作:


強制續簽(實際執行,即使憑證尚未到期) 注意!Google Public CA 有速率限制,大量測試可能觸發限制,請謹慎使用

/root/.acme.sh/acme.sh --renew \
  -d 域名.com \
  -d www.域名.com \
  --ecc \
  --force



預期顯示
Renew: 域名.com
Renewing: '域名.com'
Renewing using Le_API=https://dv.acme-v02.api.pki.goog/directory
......
Reload successful





若續簽成功,確認 Nginx 也有被正確重新載入

systemctl status nginx













13.6 確認新憑證已實際載入

openssl s_client \
  -connect 域名.com:443 \
  -servername 域名.com \
  </dev/null 2>/dev/null | openssl x509 -noout -dates -subject



確認到期日是否已更新至新憑證的到期日
notBefore= xxxxxxxxxxx 2026 GMT
notAfter= xxxxxxxxxxxx 2026 GMT
subject=CN=域名.com







13.7 確認 Google Public CA 是否仍為目前使用的 CA

/root/.acme.sh/acme.sh --info --ecc -d 域名.com



預期顯示

Le_API=https://dv.acme-v02.api.pki.goog/directory








13.8 使用 SSL Labs 進行線上評測

申請完成後,可使用 SSL Labs 的免費工具對憑證配置進行評分:

https://www.ssllabs.com/ssltest/analyze.html?d=域名.com


依本教學設定,評級應達到 A 或 A+




提醒:

SSL Labs 測試會對外發起真實的 TLS 握手,結果可供公開查閱。

若不希望掃描結果被 SSL Labs 公開列出,可在測試頁面勾選「Do not show the results on the boards」。









--------------------------------
14. 備份與還原
--------------------------------

為何備份如此重要?

/root/.acme.sh/ 目錄中包含 ACME Account Key、憑證私鑰與所有域名的設定記錄。

若VPS故障或重建,若無備份需重新完成 EAB 金鑰申請、帳號註冊、憑證申請的完整流程,且所有歷史記錄均會遺失。



備份策略概述

目錄:/root/.acme.sh/
內容:ACME 帳號金鑰、域名設定、acme.sh 腳本
敏感程度:高

目錄:/etc/nginx/ssl/
內容:部署用私鑰、完整憑證鏈
敏感程度:高

目錄:/etc/nginx/
內容:Nginx 設定檔(虛擬主機、SSL 設定)
敏感程度:中

目錄:/etc/systemd/system/acme.sh.*
內容:systemd unit 檔案
敏感程度:低


備份原則:

高敏感資料(含私鑰)必須加密後方可離機存放。

備份本身若未加密,其安全價值等同於資料已外洩。





14.1 建立備份

備份 ACME 帳號與憑證管理資料:
tar -czf /root/acme-backup-$(date +%Y%m%d).tar.gz \
  /root/.acme.sh/



備份 TLS 憑證:
tar -czf /root/ssl-backup-$(date +%Y%m%d).tar.gz \
  /etc/nginx/ssl/



備份 Nginx 設定:
tar -czf /root/nginx-config-backup-$(date +%Y%m%d).tar.gz \
  /etc/nginx/sites-available/ \
  /etc/nginx/sites-enabled/ \
  /etc/nginx/nginx.conf \
  /etc/nginx/conf.d/



備份 systemd unit 檔案:
tar -czf /root/systemd-acme-backup-$(date +%Y%m%d).tar.gz \
  /etc/systemd/system/acme.sh.service \
  /etc/systemd/system/acme.sh.timer





執行 tar 備份時可能出現以下訊息,均屬正常:

訊息:tar: Removing leading '/' from member names
說明:tar 的預設安全機制,自動移除絕對路徑前導斜線,避免解壓縮時直接覆蓋系統根目錄下的檔案。正常行為,可忽略

訊息:tar: /root/.acme.sh/...: file changed as we read it
說明:備份過程中,某檔案正在被其他程序寫入(例如 acme.sh 正在執行)。
可加上 --warning=no-file-changed 抑制警告,但建議在 acme.sh 未運行時執行備份以確保完整性





上面指令產生的 4 個備份檔 .tar.gz,都存放在 /root 目錄。

acme-backup-日期.tar.gz

nginx-config-backup-日期.tar.gz

ssl-backup-日期.tar.gz

systemd-acme-backup-日期.tar.gz








14.2 備份前驗證

建立備份後,務必立即驗證備份內容是否完整,避免「備份能做、還原不能用」的情況:


驗證 ACME 備份完整性(列出 tar 內容,不解壓縮)
tar tzf /root/acme-backup-$(date +%Y%m%d).tar.gz



驗證 SSL 備份完整性
tar tzf /root/ssl-backup-$(date +%Y%m%d).tar.gz



驗證 Nginx 設定備份完整性
tar tzf /root/nginx-config-backup-$(date +%Y%m%d).tar.gz



驗證 systemd unit 備份完整性
tar tzf /root/systemd-acme-backup-$(date +%Y%m%d).tar.gz





確認輸出中包含預期的目錄與檔案路徑,例如:
root/.acme.sh/ca/dv.acme-v02.api.pki.goog/    (帳號資訊)
root/.acme.sh/域名.com_ecc/    (域名設定與憑證)
etc/nginx/ssl/域名.com.key    (私鑰)
etc/nginx/ssl/域名.com.fullchain.pem     (憑證鏈)


注意:tar tzf 僅列出內容,不進行解壓縮,對伺服器幾乎無影響,建議納入每月維運檢查清單。








14.3 加密備份(強烈建議)

/root/.acme.sh/ 目錄中包含 ACME Account Key 與憑證私鑰,屬高敏感資料。

備份後應立即以 GPG 對稱加密,防止備份檔案落入他人手中,避免離線備份直接保存私鑰明文。



安裝 GnuPG(備份加密前請先確認已安裝)
apt update && apt install -y gnupg





加密 ACME 備份(執行後 GPG 會要求輸入並確認密碼)

gpg --cipher-algo AES256 -c /root/acme-backup-$(date +%Y%m%d).tar.gz




執行後 GPG 會以互動式對話框要求設定密碼

Enter passphrase
Passphrase: ___________________
Repeat: ________________________
<OK>                                   <Cancel>





說明:--cipher-algo AES256 明確指定使用 AES-256 對稱加密,比預設演算法更安全且具明確可稽核性。

若省略此參數,不同版本的 GPG 預設演算法可能不同(GPG 2.1+ 預設為 AES-128,GPG 2.2+ 為 AES-256),建議明確指定以確保一致性。

補充:Debian 13 搭配的 GnuPG 2.4.x 在互動式模式下預設透過 pinentry-curses 輸入密碼。

若在純文字環境中出現 gpg: problem with the agent: No pinentry 錯誤,

可加上 --pinentry-mode loopback 改為從終端機直接讀取密碼:gpg --cipher-algo AES256 --pinentry-mode loopback -c 檔案.tar.gz













加密 TLS 憑證備份(執行後 GPG 會要求輸入並確認密碼)

gpg --cipher-algo AES256 -c /root/ssl-backup-$(date +%Y%m%d).tar.gz




執行後 GPG 會以互動式對話框要求設定密碼

Enter passphrase
Passphrase: _______________________
Repeat: ____________________________
<OK>                                   <Cancel>





以上  未加密 .tar.gz 備份檔、已加密的 .tar.gz.gpg 備份檔,都存在放 /root 目錄








加密完成後,刪除原始未加密的 tar.gz(保留 .gpg 檔案)

rm /root/acme-backup-$(date +%Y%m%d).tar.gz


rm /root/ssl-backup-$(date +%Y%m%d).tar.gz



使用 SFTP 將備份檔下載至異地備份。


安全建議:

將加密後的 .gpg 檔案儲存至異地(如另一台機器、離線硬碟或加密雲端儲存)

密碼請記錄於密碼管理器,切勿遺失,否則備份無法解密還原

特別是在 VPS 重建前,請務必先完成備份

建議每月至少執行一次完整備份,並驗證備份檔案可正常解密









14.4 還原 ACME 帳號

使用SFTP將備份檔上傳至VPS的 /root 目錄後,若有加密先解密:

cd /root

ls -l



解密 GPG 加密的備份(會要求輸入密碼)
gpg -d /root/acme-backup-*.tar.gz.gpg > /root/acme-backup.tar.gz



GPG 解密時會要求輸入密碼
Please enter the passphrase for decryption.
Passphrase: ________________________________________ 
<OK>                              <Cancel>




得到解密後的檔案 acme-backup.tar.gz 後,解壓縮還原


解壓縮還原(-C / 確保路徑正確還原至 /root/.acme.sh/)
tar xzf /root/acme-backup.tar.gz -C /




重新載入 Shell 環境(確保 PATH 包含 acme.sh)
grep -q 'acme.sh' ~/.profile || \
  echo 'export PATH="$HOME/.acme.sh:$PATH"' >> ~/.profile

source ~/.profile





驗證

/root/.acme.sh/acme.sh --version


/root/.acme.sh/acme.sh --list



還原後的好處:

VPS 故障可快速還原,無需重新安裝 acme.sh

不需重新綁定 EAB 金鑰

憑證歷史記錄完整保留




常被忽略的步驟:還原後確認 --install-cert 設定

還原 acme.sh 帳號後,acme.sh --list 雖可看到域名記錄,

但 --install-cert 中設定的 --reloadcmd 以及 --key-file、--fullchain-file 安裝路徑,也儲存於各域名的 .conf 檔案中,應一併確認:


cat /root/.acme.sh/域名.com_ecc/域名.com.conf | grep -E "Le_ReloadCmd|Le_RealCertPath|Le_RealKeyPath"



若以上欄位為空或路徑不正確,建議重新執行一次 --install-cert 指令,

確保 Hook 與路徑設定完全同步,否則自動續簽後 Nginx 將不會收到正確的憑證或不會被重新載入:



特別提醒:即使還原至全新 VPS,也建議重新執行一次 --install-cert。

這可確保 Hook(reloadcmd)與憑證路徑設定完全與當前環境同步,避免新舊路徑不一致導致靜默失敗。







14.5 還原 TLS 憑證

確保目錄存在:
mkdir -p /etc/nginx/ssl


解密(若有加密):
gpg -d /root/ssl-backup-*.tar.gz.gpg > /root/ssl-backup.tar.gz



GPG 解密時會要求輸入密碼
Please enter the passphrase for decryption.
Passphrase: ________________________________________ 
<OK>                              <Cancel>




解壓縮還原至根目錄(-C / 確保路徑正確還原至 /etc/nginx/ssl/):
tar xzf /root/ssl-backup.tar.gz -C /



還原後重新確認權限(還原後 uid/gid 可能改變,務必重設):

find /etc/nginx/ssl -type f -exec chmod 640 {} \;


find /etc/nginx/ssl -type d -exec chmod 750 {} \;


chown -R root:www-data /etc/nginx/ssl


重新收緊私鑰為 600
chmod 600 /etc/nginx/ssl/域名.com.key



常被忽略:tar 還原後,uid/gid 是以數字儲存的。
若新 VPS 上 www-data 的 GID 與備份時不同,chown -R root:www-data 仍是必要的重設步驟,不可跳過。


確認 www-data 群組存在:
getent group www-data


若不存在(例如尚未安裝 Nginx),先安裝 Nginx 後再進行還原。














14.6 還原後驗證(完整驗證鏈)

還原完成後,必須依序執行以下驗證步驟,確認整個服務鏈正常運作。不可跳過任何一步,因為每步驗證的面向不同:


步驟一:驗證 Nginx 設定語法

nginx -t


預期輸出:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful


語法驗證失敗代表設定檔有問題(路徑錯誤、語法錯誤),必須先修正才能繼續。



步驟二:重新載入 Nginx(語法正確後方可執行)

systemctl reload nginx


systemctl status nginx




步驟三:驗證憑證鏈完整性(本機 openssl verify)

此步驟確認憑證檔案本身是完整的憑證鏈(含中繼憑證),而非僅有終端憑證:

openssl verify \
  -CAfile /etc/ssl/certs/ca-certificates.crt \
  -untrusted /etc/nginx/ssl/域名.com.fullchain.pem \
  /etc/nginx/ssl/域名.com.fullchain.pem


預期輸出:
/etc/nginx/ssl/域名.com.fullchain.pem: OK



若出現 error 20: unable to get local issuer certificate,代表 fullchain.pem 缺少中繼憑證,

需重新執行 --install-cert 或手動從 acme.sh 內部目錄複製 fullchain.cer。





步驟四:確認憑證到期日

openssl x509 \
  -in /etc/nginx/ssl/域名.com.fullchain.pem \
  -noout -enddate



確認還原的憑證到期日合理。若距到期不足 30 天,立即執行強制續簽:
/root/.acme.sh/acme.sh --renew --ecc -d 域名.com -d www.域名.com --force








步驟五:透過 TLS 握手確認 Nginx 實際提供正確憑證


此步驟與步驟三的差異在於:它是從外部模擬真實 TLS 連線,確認 Nginx 在執行時確實提供了正確且已更新的憑證,而非僅確認磁碟上的檔案:

openssl s_client \
  -connect 域名.com:443 \
  -servername 域名.com \
  </dev/null 2>/dev/null | openssl x509 -noout -dates -subject


確認輸出的 notAfter 與 subject 符合預期。




步驟六:驗證 TLS 1.3 可正常運作

curl --tlsv1.3 -I https://域名.com


預期:返回 HTTP/2 200(或其他正常狀態碼)而不是 SSL 錯誤。








步驟七:確認 acme.sh 能正確識別已還原的憑證

/root/.acme.sh/acme.sh --list





步驟八:確認自動續簽排程已恢復

加 --all 避免 systemd 版本差異造成顯示結果不同

systemctl list-timers --all | grep acme


確認 timer 狀態為 active,且 NEXT 欄位有顯示下次執行時間。





步驟九:線上 TLS 掃描(建議執行,評估整體設定品質)

還原完成後,使用 SSL Labs 進行一次線上掃描,確認 TLS 設定評級正常(應達 A 或 A+):

https://www.ssllabs.com/ssltest/analyze.html?d=域名.com



本機的 openssl verify 與 openssl s_client 僅驗證憑證本身的有效性,

無法評估 TLS 協定版本、加密套件組合、HSTS 設定、中繼憑證傳遞是否正確等整體安全配置。

SSL Labs 掃描能一次涵蓋所有面向,讓你確認還原後的服務品質與原本一致。






14.7 還原 Nginx 設定

解壓縮還原 Nginx 設定(-C / 確保還原至正確路徑,會覆蓋現有設定,請確認後再執行)
tar xzf /root/nginx-config-backup-*.tar.gz -C /



解壓縮還原 systemd unit 檔案(-C / 確保還原至正確路徑,會覆蓋現有設定,請確認後再執行)
tar xzf /root/systemd-acme-backup-*.tar.gz -C /



重新載入 systemd 設定並啟用 timer

systemctl daemon-reload

systemctl enable --now acme.sh.timer



確認 timer 已實際載入並排程正常(不可只靠 enable,必須確認 active 狀態):


確認 timer 處於 active 狀態
systemctl status acme.sh.timer




確認 timer 已列入排程清單並顯示下次執行時間
systemctl list-timers --all | grep acme


NEXT 欄位有顯示具體時間,表示 timer 正常運作。

若 NEXT 欄位為空或 timer 不在清單中,需重新排查 unit 檔案是否正確還原。




驗證 Nginx 設定語法
nginx -t



若語法正確,重新載入
systemctl reload nginx














------------------------------
特殊環境注意事項
------------------------------

CDN 使用者(Cloudflare 等)

若站點啟用 Cloudflare Proxy(橘色雲):HTTP-01 驗證若失敗,暫時切換為 DNS Only(灰色雲),等完成後再切回 Proxy 模式。








IPv6 使用者

若 DNS 存在 AAAA 記錄,Google CA 可能優先驗證 IPv6。

因此 curl -6 http://域名.com/.well-known/acme-challenge/test-file 必須正常,否則 HTTP-01 驗證將失敗。



確認 Nginx 正在監聽 IPv6 TCP 80
ss -tln | grep '\[::\]:80'



確認 Nginx 正在監聽 IPv6 TCP 443
ss -tln | grep '\[::\]:443'



應分別看到 [::]:80 與 [::]:443 的監聽條目。

若沒有出現,即使 Nginx 設定了 listen [::]:80;,也可能因 /etc/nginx/nginx.conf 主設定檔的限制未正確套用,需確認並排查。





-----------------------------
15. 常見問題與排錯
-----------------------------

Q1:申請憑證時出現 Could not connect to server

可能原因:

TCP 80 port 未對外開放(被 OS 防火牆或雲端安全群組封鎖)

Nginx 未在監聽 TCP 80 port

域名 DNS 尚未生效,或指向錯誤 IP


排查步驟:

確認 Nginx 監聽狀態
ss -tlnp | grep ':80'


確認防火牆規則
nft list ruleset


確認域名解析結果(A 記錄)
dig +short A 域名.com



確認域名解析結果(AAAA 記錄,若有使用 IPv6)
dig +short AAAA 域名.com





Q2:acme.sh 安裝完後指令找不到(command not found)

安裝 acme.sh 時會自動在 ~/.bashrc 或 ~/.profile 中加入 PATH,但當前 shell 尚未重新載入:

grep -q 'acme.sh' ~/.profile || \
  echo 'export PATH="$HOME/.acme.sh:$PATH"' >> ~/.profile

source ~/.profile



驗證
/root/.acme.sh/acme.sh --version



建議始終使用絕對路徑 /root/.acme.sh/acme.sh 執行指令,避免PATH環境問題,這在 cron 和 systemd 環境中尤其重要。







Q3:EAB 金鑰已超過有效期

需返回 Google Cloud Console 重新建立 EAB 金鑰,然後重新執行帳號註冊指令。





Q4:Nginx 語法測試通過但重新載入後 HTTPS 無法連線

排查步驟:

確認 Nginx 狀態
systemctl status nginx


查看 Nginx 錯誤日誌(最後 50 行)
tail -50 /var/log/nginx/error.log


確認憑證檔案確實存在且路徑正確
ls -lh /etc/nginx/ssl/


確認 TCP 443 port 正在監聽
ss -tlnp | grep ':443'


確認防火牆允許 TCP 443 入站
nft list ruleset | grep 443





Q5:自動續簽成功但 Nginx 未使用新憑證

確認 --install-cert 中的 --reloadcmd 設定正確:


查看 acme.sh 內部記錄的 reloadcmd
grep ReloadCmd /root/.acme.sh/域名.com_ecc/域名.com.conf


若 ReloadCmd 為空或錯誤,重新執行 --install-cert 指令以覆寫設定。





Q6:憑證申請失敗,日誌顯示 Verify error: urn:ietf:params:acme:error:connection

這表示 Google CA 的驗證伺服器無法從外部存取 /.well-known/acme-challenge/ 路徑。

排查步驟:

手動建立測試檔案
echo "acme-test" > /var/www/acme-challenge/.well-known/acme-challenge/test



從 VPS 本機測試(搭配 Host 標頭確認 Nginx 路由正確)
curl -sv http://127.0.0.1/.well-known/acme-challenge/test \
  -H "Host: 域名.com"



若本機 OK,再從外部(另一台電腦或手機)測試
curl http://域名.com/.well-known/acme-challenge/test



清理測試檔案
rm /var/www/acme-challenge/.well-known/acme-challenge/test


若本機可正常存取但外部無法,問題在防火牆層面;若本機也無法存取,問題在 Nginx 設定或目錄權限。






Q7:憑證有效但瀏覽器顯示「不安全」或憑證錯誤

可能原因:Nginx 引用了 .cer(單一憑證)而非 .fullchain.pem(完整憑證鏈),導致中繼憑證缺失,部分舊版瀏覽器或 API 客戶端無法驗證。


檢查 Nginx 實際載入的憑證鏈:

openssl s_client \
  -connect 域名.com:443 \
  -showcerts \
  -servername 域名.com \
  </dev/null 2>/dev/null | grep -E "^(subject|issuer)"


輸出應顯示你的域名憑證與 Google Trust Services 的中繼憑證。







Q8:systemd timer 存在但 acme.sh 未被執行


確認 acme.sh.timer 是否啟用且狀態正常
systemctl status acme.sh.timer



若狀態顯示 inactive,手動啟用:
systemctl enable --now acme.sh.timer



查看 service 執行日誌,確認有無錯誤:
journalctl -u acme.sh.service -n 50





Q9:ssl_stapling_verify on 導致 Nginx 啟動時出現警告

自 2025 年起,Google Trust Services 與 Let's Encrypt 均已停止在憑證中嵌入 OCSP URL,並已關閉 OCSP 回應伺服器。

若仍啟用 ssl_stapling,Nginx 啟動時將出現 "ssl_stapling" ignored 警告,且可能導致伺服器啟動延遲。應將 ssl_stapling 相關設定全部停用:

ssl_stapling        off;
ssl_stapling_verify off;







Q10:curl 測試 HTTPS 時顯示 SSL_ERROR_RX_RECORD_TOO_LONG

這通常代表 Nginx 在 TCP 443 port 上回應的是 HTTP 而非 HTTPS。

常見原因是 listen 443 ssl; 設定未正確套用,或設定檔語法有誤但 nginx -t 未捕捉到。


確認正在使用哪個設定檔
nginx -T | grep -A5 "listen 443"


重新測試語法
nginx -t





Q11:acme.sh --upgrade 後 acme.sh 指令失效

升級後若 alias 失效,重新載入即可:

grep -q 'acme.sh' ~/.profile || \
echo 'export PATH="$HOME/.acme.sh:$PATH"' >> ~/.profile

source ~/.profile






驗證
/root/.acme.sh/acme.sh --version

ls -lh ~/.acme.sh/acme.sh










Q12:憑證申請速率限制(too many certificates already issued)

Google Public CA 對同一域名每週有申請次數限制。

若頻繁測試導致觸發限制,需等待限制解除(通常 7 天)後再申請。









Q13:--install-cert 後 Nginx 未重新載入(Reload failed)

若 --reloadcmd 執行失敗,acme.sh 會顯示 Reload failed。常見原因是 root 的 PATH 中找不到 systemctl。


排查方式:

手動測試 reloadcmd 是否可執行

/usr/sbin/nginx -t && /usr/bin/systemctl reload nginx
echo $?



若返回非 0,表示 Nginx 設定有語法錯誤,先執行 nginx -t 確認後再重新載入。


補漏提醒:這也是為何 --reloadcmd 應使用絕對路徑的原因,

cron 與 systemd 環境下 PATH 往往不包含 /usr/sbin,直接使用 nginx -t 可能找不到指令。



Q14:ssl_ecdh_curve 設定後 Nginx 啟動時出現 unknown curve 警告

Debian 13 的 OpenSSL 版本(3.x)支援 X25519、secp384r1 與 prime256v1,正常情況下不會出現警告。若出現,先確認 Nginx 版本與 OpenSSL 版本:

nginx -V 2>&1 | grep -E "version|OpenSSL"



Nginx 1.25.1+ 搭配 OpenSSL 3.x 完整支援 X25519:secp384r1:prime256v1 三曲線組合。若確認不支援(例如舊版 Nginx 或 LibreSSL),可退而保留兩曲線:

ssl_ecdh_curve X25519:prime256v1;









Q15:ssl_early_data off 設定後是否影響效能?

ssl_early_data off 關閉的是 TLS 1.3 的 0-RTT 功能,對已建立連線的效能幾乎沒有影響。

0-RTT 主要針對重複連線的首次請求,而論壇網站的安全考量(登入狀態、POST 請求)遠重於此效能優化,生產環境保持關閉是正確選擇。





Q16:IPv6 DNS AAAA 記錄設定後 Nginx 沒監聽 IPv6

即使 Nginx 設定檔中有 listen [::]:80;  或  listen [::]:443 ssl;

也可能因為系統層面的 IPv6 未啟用,

或 Nginx 主設定檔(nginx.conf)的影響而未實際監聽。



排查步驟:


確認 Nginx 是否監聽 IPv6 TCP 80
ss -tln | grep '\[::\]:80'


確認 Nginx 是否監聽 IPv6 TCP 443
ss -tln | grep '\[::\]:443'


確認系統核心是否停用了 IPv6
sysctl net.ipv6.conf.all.disable_ipv6


若值為 1,表示 IPv6 已被系統停用,需先啟用。

若確認系統支援 IPv6 但 Nginx 仍未監聽,檢查是否有 default_server 衝突:

nginx -T | grep -E "listen.*\[::\]"








Q17:server_tokens off 設定後 Server 標頭仍顯示版本號

server_tokens off 必須在 /etc/nginx/nginx.conf 的 http {} 區塊中設定,才能對所有虛擬主機生效。

僅在 server {} 區塊設定只影響該虛擬主機。


確認主設定檔中是否已設定
grep server_tokens /etc/nginx/nginx.conf



測試後重新載入
nginx -t && systemctl reload nginx


驗證標頭
curl -fsSLI https://域名.com | grep -i server


預期輸出:server: nginx(無版本號)






Q18:申請憑證時出現 badNonce 或 invalid JWS 錯誤

這類錯誤通常是系統時間不同步造成的。

ACME 協定的請求包含時間戳記,若伺服器時間偏差過大,Google CA 會拒絕請求。


立即確認系統時間狀態
timedatectl status



確認 NTP 同步是否正常
systemctl status systemd-timesyncd


若未同步,強制啟用 NTP
timedatectl set-ntp true


等待約 30 秒後再確認
timedatectl status


修正時間後再重新申請憑證。






-------------------------------
16. acme.sh 常用指令速查
-------------------------------

查看所有已管理的憑證
acme.sh --list



手動強制續簽(測試用,即使憑證尚未到期)

注意:Google Public CA 有速率限制,勿頻繁執行

/root/.acme.sh/acme.sh --renew --ecc -d 域名.com -d www.域名.com --force



查看 acme.sh 版本
/root/.acme.sh/acme.sh --version



更新 acme.sh 本身
/root/.acme.sh/acme.sh --upgrade


確認 cron 排程(預設)
crontab -l | grep acme


確認 systemd timer 排程(加 --all 避免版本差異)
systemctl list-timers --all | grep acme




查看所有 timer(確認排程整體狀態)
systemctl list-timers



查看 acme.sh service 執行紀錄(systemd timer 方式)
journalctl -u acme.sh.service --since "7 days ago"



查看某域名的完整設定
cat /root/.acme.sh/域名.com_ecc/域名.com.conf


確認下次續簽時間(顯示 Unix timestamp)
grep Le_NextRenewTime ~/.acme.sh/域名.com_ecc/域名.com.conf


轉換為人類可讀格式(Debian / GNU date 語法)
date -d @$(grep "^Le_NextRenewTime=" \
  ~/.acme.sh/域名.com_ecc/域名.com.conf \
  | cut -d= -f2 | tr -d "'") '+%Y-%m-%d %H:%M:%S %Z'



確認目前使用的 CA
/root/.acme.sh/acme.sh --info --ecc -d 域名.com



撤銷憑證(通常在私鑰外洩時使用)
/root/.acme.sh/acme.sh --revoke --ecc -d 域名.com -d www.域名.com



移除某域名的憑證(停止管理)
/root/.acme.sh/acme.sh --remove --ecc -d 域名.com -d www.域名.com



查看線上憑證到期日(TLS 握手方式)
openssl s_client \
  -connect 域名.com:443 \
  -servername 域名.com \
  </dev/null 2>/dev/null | openssl x509 -noout -dates -subject



查看本機憑證檔案到期日
openssl x509 \
  -in /etc/nginx/ssl/域名.com.fullchain.pem \
  -noout \
  -enddate



透過 curl 確認 HTTPS 連線與憑證資訊
curl -fsSLI https://域名.com



詳細 TLS 協商資訊(需要查看憑證 issuer 等詳細資訊時使用)
curl -vkI https://域名.com








--------------------------------
附錄 A:憑證檔案路徑彙整
--------------------------------

用途:acme.sh 內部工作目錄(勿直接引用)
路徑:/root/.acme.sh/域名.com_ecc/


用途:Nginx 私鑰
路徑:/etc/nginx/ssl/域名.com.key


用途:Nginx 完整憑證鏈
路徑:/etc/nginx/ssl/域名.com.fullchain.pem



用途:ACME 帳號資訊
路徑:/root/.acme.sh/ca/dv.acme-v02.api.pki.goog/


用途:ACME webroot 目錄
路徑:/var/www/acme-challenge/


用途:systemd service unit
路徑:/etc/systemd/system/acme.sh.service


用途:systemd timer unit
路徑:/etc/systemd/system/acme.sh.timer




-------------------------------------------
附錄 B:Nginx 設定關鍵參數說明彙整
-------------------------------------------

參數:server_tokens
建議值:off
說明:隱藏 Nginx 版本號,降低指紋洩露風險

參數:http2 on
建議值:啟用
說明:啟用 HTTP/2(Nginx 1.25.1+ 語法)

參數:ssl_protocols
建議值:TLSv1.2 TLSv1.3
說明:僅允許安全的 TLS 版本

參數:ssl_ecdh_curve
建議值:X25519:secp384r1:prime256v1
說明:X25519 效能最佳;secp384r1 高安全備援;prime256v1 廣泛相容;2 GB RAM 完全沒問題

參數:ssl_early_data
建議值:off
說明:關閉 0-RTT,防止重放攻擊

參數:ssl_session_cache
建議值:shared:SSL:10m
說明:減少 TLS 握手開銷

參數:ssl_session_timeout
建議值:1d
說明:Session 有效期 1 天

參數:ssl_session_tickets
建議值:off
說明:提升前向保密性

參數:ssl_buffer_size
建議值:8k
說明:減少 TLS 記憶體消耗

參數:ssl_prefer_server_ciphers
建議值:off
說明:優先用戶端套件選擇(現代最佳實踐)

參數:keepalive_timeout
建議值:30
說明:避免高併發下連線資源浪費

參數:keepalive_requests
建議值:1000
說明:單一連線最大請求數

參數:client_max_body_size
建議值:200m
說明:論壇附件上傳必要

參數:ssl_stapling
建議值:off
說明:Google CA 與 Let's Encrypt 已於 2025 年停用 OCSP





-------------------------------
附錄 C:日常維運檢查清單
-------------------------------

建議定期執行以下指令,確認 TLS 憑證與自動續簽機制均正常運作:


1. 確認憑證到期日(距到期應有 30 天以上)
openssl x509 -in /etc/nginx/ssl/域名.com.fullchain.pem -noout -enddate


2. 確認自動續簽排程正常(加 --all 避免版本差異)
systemctl list-timers --all | grep acme



3. 快速確認 timer 與 service 運作狀況

systemctl status acme.sh.timer


systemctl status acme.sh.service



4. 確認 acme.sh service 近期執行無異常
journalctl -u acme.sh.service --since "7 days ago"



5. 確認 Nginx 狀態正常
systemctl status nginx



6. 確認 Nginx 監聽 IPv4/IPv6(80 與 443)


ss -tln | grep -E ':80|:443'


ss -tln | grep '\[::\]:80'


ss -tln | grep '\[::\]:443'




7. 確認備份檔案存在(建議每月備份一次)

ls -lh /root/acme-backup-*.tar.gz.gpg


ls -lh /root/ssl-backup-*.tar.gz.gpg




8. 驗證備份內容完整性(列出不解壓縮,適用於未加密或已解密後的備份)

tar tzf /root/acme-backup-$(date +%Y%m%d).tar.gz | head -20


tar tzf /root/ssl-backup-$(date +%Y%m%d).tar.gz




9. 確認憑證權限正確

ls -lh /etc/nginx/ssl/


預期看到:
-rw-r----- 1 root www-data  ... 域名.com.fullchain.pem    (640 root:www-data)
-rw------- 1 root www-data  ... 域名.com.key               (600 root:www-data)


10. 確認 Nginx Server 標頭不洩露版本號

curl -fsSLI https://域名.com | grep -i server

預期:server: nginx(無版本號)



11. 確認 TLS 1.3 可正常運作

curl --tlsv1.3 -I https://域名.com



12. 確認系統時間同步正常
timedatectl status



13. 確認排程機制唯一性(正式環境應只有 systemd timer,cron 應為空)

預期顯示:無任何輸出
crontab -l 2>/dev/null | grep acme


預期顯示:active
systemctl is-active acme.sh.timer





14. 確認私鑰與憑證匹配(兩行輸出應相同)

openssl pkey -in /etc/nginx/ssl/域名.com.key -pubout 2>/dev/null | openssl sha256


openssl x509 -in /etc/nginx/ssl/域名.com.fullchain.pem -pubkey -noout 2>/dev/null | openssl sha256








-----------------------------------------
附錄 D:VPS 重建後完整還原流程
-----------------------------------------

若需要在新 VPS 上還原整套環境,請依以下順序執行:

1. 安裝基礎環境(Nginx、PHP-FPM、MariaDB)


2. 確認 www-data 群組存在
getent group www-data


3. 安裝前置相依套件(curl、socat、GnuPG 備份解密需要)
apt update

apt install -y curl socat gnupg



4. 確認系統時間同步正常
timedatectl status


若未同步先執行:
timedatectl set-ntp true


5. 從備份還原 Nginx 設定(/etc/nginx/)

tar xzf /root/nginx-config-backup-*.tar.gz -C /



6. 建立憑證目錄並設定正確擁有者
mkdir -p /etc/nginx/ssl && chown root:www-data /etc/nginx/ssl



7. 從備份還原 TLS 憑證
gpg -d /root/ssl-backup-*.tar.gz.gpg > /root/ssl-backup.tar.gz

tar xzf /root/ssl-backup.tar.gz -C /




8. 重設憑證目錄與檔案權限

find /etc/nginx/ssl -type f -exec chmod 640 {} \;


find /etc/nginx/ssl -type d -exec chmod 750 {} \;


chown -R root:www-data /etc/nginx/ssl


chmod 600 /etc/nginx/ssl/域名.com.key




9. 從備份還原 acme.sh

gpg -d /root/acme-backup-*.tar.gz.gpg > /root/acme-backup.tar.gz


tar xzf /root/acme-backup.tar.gz -C /




10. 重新載入 Shell 環境並驗證 acme.sh 版本

grep -q 'acme.sh' ~/.profile || echo 'export PATH="$HOME/.acme.sh:$PATH"' >> ~/.profile
source ~/.profile
/root/.acme.sh/acme.sh --version




11. 確認並重新執行 --install-cert(強烈建議)

先確認設定是否完整:
cat /root/.acme.sh/域名.com_ecc/域名.com.conf | grep -E "Le_ReloadCmd|Le_RealCertPath|Le_RealKeyPath"


無論欄位是否有值,建議重新執行一次 --install-cert,確保 Hook 與路徑設定完全與當前新環境同步:





12. 從備份還原 systemd unit 並啟用 timer(確認移除 cron job 後再啟用)

tar xzf /root/systemd-acme-backup-*.tar.gz -C /

systemctl daemon-reload


先確認 cron job 已移除(預期無輸出)
crontab -l 2>/dev/null | grep acme



確認無輸出後,啟用 timer
systemctl enable --now acme.sh.timer



13. 執行完整還原後驗證(依序執行)

nginx -t


systemctl reload nginx


openssl verify \
  -CAfile /etc/ssl/certs/ca-certificates.crt \
  -untrusted /etc/nginx/ssl/域名.com.fullchain.pem \
  /etc/nginx/ssl/域名.com.fullchain.pem



openssl x509 -in /etc/nginx/ssl/域名.com.fullchain.pem -noout -enddate


openssl s_client -connect 域名.com:443 -servername 域名.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject


驗證 TLS 1.3
curl --tlsv1.3 -I https://域名.com


systemctl list-timers --all | grep acme


systemctl status acme.sh.timer


systemctl status acme.sh.service




線上掃描:
https://www.ssllabs.com/ssltest/analyze.html?d=域名.com



14. 確認排程機制唯一性

預期:無輸出
crontab -l 2>/dev/null | grep acme



預期:active
systemctl is-active acme.sh.timer



提醒:
還原帳號後無需重新執行 EAB 金鑰綁定與帳號註冊,acme.sh 的帳號資訊已含於備份中。
但若還原失敗或帳號損毀,則需重新執行建立 EAB 金鑰與帳號註冊步驟。

若還原的憑證距到期日不足 30 天,立即執行一次強制續簽:

/root/.acme.sh/acme.sh --renew --ecc -d 域名.com -d www.域名.com --force






--------------------------------
附錄 E:部署驗收清單
--------------------------------

本清單用於首次部署完成後的一次性驗收,

確認整套 acme.sh + Google Public CA + Nginx 自動化憑證機制已正確落地。

建議將每一項實際執行結果記錄下來(截圖或文字紀錄),作為交付文件的一部分;日常維運請改用附錄 C 的檢查清單。



[ ] DNS A / AAAA 正確

  dig +short A 域名.com

  dig +short AAAA 域名.com

  getent ahosts 域名.com

  確認回應的IP與VPS公網 IP 一致,且 dig 與 getent ahosts 結果相符。




[ ] TCP 80、443 對外可達

  ss -tlnp | grep -E ':80|:443'

  nft list ruleset | grep -E '"80"|"443"'

  並從外部主機(非本機)以 curl -v http://域名.com/ 與 curl -v https://域名.com/ 確認可連線。





 [ ] timedatectl status 顯示同步正常

  timedatectl status

  確認 System clock synchronized: yes 且 NTP service: active。




[ ] acme.sh --list 可看到憑證

  /root/.acme.sh/acme.sh --list

  確認 Main_Domain、CA(應為 Google)、Created、Renew 欄位皆正確。



[ ] systemctl list-timers --all | grep acme 正常

  systemctl list-timers --all | grep acme

  systemctl is-active acme.sh.timer

  crontab -l 2>/dev/null | grep acme

  確認 timer 為 active、NEXT 欄位有時間,且 crontab -l 無 acme 相關輸出(cron 與 systemd timer 不可同時存在)。




[ ] openssl s_client Verify return code = 0

  openssl s_client -connect 域名.com:443 -servername 域名.com -showcerts </dev/null

  確認結尾出現 Verify return code: 0 (ok)




[ ] curl --tlsv1.3 -I https://域名.com 正常

  curl --tlsv1.3 -I https://域名.com

  確認回應為 HTTP/2 200(或其他正常狀態碼),而非 SSL 錯誤。



[ ] Server: nginx 不顯示版本號

  curl -fsSLI https://域名.com | grep -i server

  確認輸出為 server: nginx,不含版本號數字。

  並確認 server_tokens off; 同時存在於 nginx.conf 的 http {} 區塊與該虛擬主機。



[ ] Strict-Transport-Security 標頭存在

  curl -fsSLI https://域名.com | grep -i strict-transport-security

  確認主要路由與靜態資源 location(含 add_header 的區塊)都有此標頭,避免因 Nginx add_header 繼承規則而遺漏。



[ ] /etc/nginx/ssl/*.key 權限為 600

  ls -lh /etc/nginx/ssl/

  確認 .key 檔案權限為 600、擁有者為 root:www-data

  fullchain.pem 權限為 640



[ ] 憑證鏈完整性(含中繼憑證)

  openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
    -untrusted /etc/nginx/ssl/域名.com.fullchain.pem \
    /etc/nginx/ssl/域名.com.fullchain.pem

  預期輸出 : OK。

  若出現 error 20,請參考第 12.1 節排查。




[ ] 私鑰與憑證指紋匹配

  openssl pkey -in /etc/nginx/ssl/域名.com.key -pubout 2>/dev/null | openssl sha256

  openssl x509 -in /etc/nginx/ssl/域名.com.fullchain.pem -pubkey -noout 2>/dev/null | openssl sha256

  兩行 SHA256 必須完全相同。




[ ] 自動續簽 reload 設定正確(Le_ReloadCmd)

  grep Le_ReloadCmd ~/.acme.sh/域名.com_ecc/域名.com.conf

  應看到:
  Le_ReloadCmd='/usr/sbin/nginx -t && /usr/bin/systemctl reload nginx'

  若沒有,代表未正確執行 --install-cert,需重新執行。





[ ] 憑證簽發者與部署資訊一致

  openssl s_client -connect 域名.com:443 \
    -servername 域名.com </dev/null | \
    openssl x509 -noout -issuer -subject -dates

  確認 issuer 含 Google Trust Services,subject 的 CN 為你的域名,且 notAfter 為合理的未來日期(約90天)。





[ ] ssl_stapling 已關閉

  grep -E "ssl_stapling" /etc/nginx/sites-enabled/域名.com.conf

  確認 ssl_stapling off; 與 ssl_stapling_verify off; 均存在,

  避免因 2025 年 OCSP 服務停止而出現 [warn] "ssl_stapling" ignored。




[ ] IPv6 監聽(若有 AAAA 記錄)

  ss -tln | grep '\[::\]:80'

  ss -tln | grep '\[::\]:443'

  curl -6 -I https://域名.com





[ ] 首次強制續簽測試成功

  /root/.acme.sh/acme.sh --renew --ecc -d 域名.com -d www.域名.com --force

  systemctl status nginx

  確認續簽流程與 reload 機制在實際環境中可正常運作(注意 Google Public CA 速率限制,僅需測試一次)。





[ ] 備份與加密已建立並驗證

  ls -lh /root/*.tar.gz.gpg

  tar tzf /root/acme-backup-$(date +%Y%m%d).tar.gz | head -5

  確認 ACME 帳號、TLS 憑證、Nginx 設定、systemd unit 四份備份均已加密並可正常列出內容。




[ ] SSL Labs 線上評測達 A 或 A+

  https://www.ssllabs.com/ssltest/analyze.html?d=域名.com

  涵蓋 TLS 協定版本、加密套件、憑證鏈傳遞、HSTS 等綜合面向,是交付前的最後一道把關。




建議將 附錄 C 的日常維運檢查清單排入每月例行檢查,

並在每次重大變更(如 Nginx 大版本升級、VPS 遷移)後重新執行 附錄 E 的部署驗收清單。

本帖最后于,由Jack编辑

创建帐户或登录后发表意见

帐户

导航

搜索

搜索

配置浏览器推送通知

Chrome (安卓)
  1. 轻敲地址栏旁的锁形图标。
  2. 轻敲权限 → 通知。
  3. 调整你的偏好。
Chrome (台式电脑)
  1. 点击地址栏中的挂锁图标。
  2. 选择网站设置。
  3. 找到通知选项并调整你的偏好。