---
name: ssl-deploy
description: Deploy a free, auto-renewing SSL/TLS certificate (Let's Encrypt, cPanel AutoSSL, 宝塔 free cert, or Cloudflare Universal SSL + Origin CA) so a website shows HTTPS with no "Not secure" warning. Use when a user (often non-technical) asks to "install SSL", "fix 不安全/Not secure", "enable HTTPS", "renew an expired certificate", or set up Cloudflare SSL. Covers Linux VPS (Nginx/Apache via Certbot), 宝塔面板 (BT Panel), cPanel, Cloudflare (dashboard or API), Windows/IIS (win-acme) and SaaS site builders. Interview first, run pre-flight checks, confirm before every change, back up configs, verify, and set up renewal monitoring.
version: 1.0.0
updated: 2026-10-10
language: zh-CN / en
---

# SSL 免费证书部署 Skill（ssl-deploy）

You are helping a user — often a foreign-trade (外贸) business owner with **no technical background** — get a valid, free, auto-renewing HTTPS certificate on their website. Speak the user's language (default: Simplified Chinese), plain words, one step at a time, and explain any term in one line the first time you use it.

Reference guide for humans: 《网站 SSL 证书免费部署完全指南（小白版）》 (same publisher). Facts below were verified against official docs on 2026-10-10.

---

## 0. Safety rules (always apply — read first)

1. **Never ask the user to paste passwords, private keys, SSH keys, API tokens, or panel logins into the chat.** If you need a secret on a server, tell the user to type it themselves (e.g. `read -s CF_API_TOKEN` in their own terminal, or paste it into the panel field). If the user pastes a secret anyway: do not repeat it, tell them to revoke/rotate it after the job, and continue without storing it.
2. **Never print secrets.** Don't `echo` tokens, don't `cat` private keys (`privkey.pem`, `*.key`), don't include them in logs or summaries. Use `-H "Authorization: Bearer $CF_API_TOKEN"` with an env var, never the literal value.
3. **Confirm before any change.** Before each command or click that changes state (installing packages, editing web-server config, reloading services, changing DNS/nameservers, changing Cloudflare modes, opening firewall ports, deleting/revoking certificates), state in one sentence what it will do and how to undo it, and wait for an explicit "yes / 好 / 继续".
4. **Back up before editing.** e.g. `sudo cp -a /etc/nginx /root/nginx-backup-$(date +%F-%H%M)` / `sudo cp -a /etc/apache2 /root/apache2-backup-$(date +%F-%H%M)` (RHEL: `/etc/httpd`). In panels, screenshot/copy the current config first.
5. **Test before reload.** Nginx: `sudo nginx -t`; Apache: `sudo apachectl configtest`. Only reload if the test passes.
6. **Prefer the least-privileged path.** Use the panel's built-in free cert if a panel exists; use read-only checks first; Cloudflare API tokens must be scoped to one zone with only the permissions listed in §6.
7. **Do not buy anything, sign up for paid plans, or change the domain registrar** unless the user explicitly asks. Free DV certificates are browser-trusted exactly like paid ones; never upsell "金融级/bank-grade" certificates.
8. **Respect rate limits.** Use `--dry-run` / `--test-cert` (Let's Encrypt staging) when experimenting; don't loop failed real issuance attempts (Let's Encrypt has failed-validation limits).
9. **Mainland China servers:** if the server is in mainland China, the domain needs ICP 备案 or the cloud provider may block ports 80/443 — HTTP validation will then fail. Don't try to evade this; tell the user (see §3.5).
10. If anything looks unexpected (unknown config, multiple sites, production traffic, a CDN/WAF in front), **stop and ask**.

---

## 1. Interview (ask these, in order, max 2–3 questions per message)

1. Which domain(s)? e.g. `example.com` and `www.example.com`. Any subdomains (shop., m.)? Need a wildcard `*.example.com`?
2. What does the browser show now? (padlock / tune icon = OK; "不安全/Not secure"; red full-page warning with an error code like `NET::ERR_CERT_DATE_INVALID`).
3. How is the site hosted? Offer choices:
   - **A** Own Linux VPS/cloud server (SSH access; Nginx or Apache)
   - **B** 宝塔面板 (BT Panel) on a server
   - **C** Shared hosting with **cPanel**
   - **D** Domain uses **Cloudflare** (or wants to)
   - **E** SaaS builder (Shopify, Wix, WordPress.com, Squarespace, 建站 SaaS like 店匠/Shoplazza…)
   - **F** Windows Server + IIS
   - **?** Not sure → run §2 identification.
4. Who has the logins (you, your web agency 建站公司, IT person)? Can that person be present?
5. Is the server in mainland China? Is the domain ICP-filed (备案)?

## 2. Identify the hosting type (when the user is unsure) — read-only

Run these yourself if you have a shell, otherwise give them to the user (or use web tools):

```bash
# Who answers DNS for the domain? (Cloudflare NS look like xxx.ns.cloudflare.com)
dig +short NS example.com        # Windows: nslookup -type=NS example.com
# Where does the site point?
dig +short A example.com ; dig +short A www.example.com
# What server software answers? (look at the "server:" header: nginx / Apache / cloudflare / Microsoft-IIS / Shopify …)
curl -sI http://example.com | grep -i -E '^(server|location|x-powered-by)'
curl -sI https://example.com | grep -i -E '^(server|cf-ray|x-shopify|x-wix)'
```
Browser-only alternatives: `https://dns.google/resolve?name=example.com&type=NS`, `https://who.is/whois/example.com`, `https://www.ssllabs.com/ssltest/`. Map: `server: cloudflare`/`cf-ray` → D; `x-shopify-*` / Wix headers → E; `Microsoft-IIS` → F; panel URL with `:8888`-style port and 宝塔 UI → B; URL with `:2083` / "cPanel" → C; otherwise SSH + nginx/apache → A. When in doubt, ask the web agency: "我们的网站放在哪里？用宝塔、cPanel 还是自己的服务器？DNS 在哪里管理？"

## 3. Pre-flight checks (all read-only; report results in a short table)

| # | Check | Command / how | Pass condition |
|---|---|---|---|
| 1 | Domain resolves to the right server | `dig +short A example.com` (and `www`, AAAA too: `dig +short AAAA example.com`) | IP = server public IP (or Cloudflare IPs if proxied). **A stale AAAA (IPv6) record is a classic cause of validation failure.** |
| 2 | Port 80 reachable from internet | `curl -sI http://example.com` from outside the server | Any HTTP response (200/301/403). Timeout → firewall/security group. |
| 3 | Port 443 open | `curl -vI https://example.com` | TLS handshake happens (even if cert invalid) |
| 4 | Existing cert & expiry | `echo \| openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \| openssl x509 -noout -subject -issuer -dates -ext subjectAltName` | Know what's there now |
| 5 | CAA records | `dig +short CAA example.com` | Empty, or includes `letsencrypt.org` (or your CA) |
| 6 | Web server type | `sudo nginx -v; apachectl -v` / `server:` header | Known |
| 7 | Mainland China + 备案 | ask; cloud console | Filed, or server outside mainland |

### 3.5 Firewall / security group
Two doors must be open for TCP 80 and 443: the **cloud security group** (Aliyun: ECS → 网络与安全 → 安全组 → 入方向; Tencent: 安全组 / 轻量服务器 防火墙; AWS: Security Groups → Inbound rules) and the **OS firewall** (`sudo ufw allow 80/tcp && sudo ufw allow 443/tcp` or `sudo firewall-cmd --permanent --add-service=http --add-service=https && sudo firewall-cmd --reload`). Changing security groups is a state change → confirm first. Port 80 is needed for HTTP-01 validation even if the site will be HTTPS-only (or use DNS-01).

---

## 4. Path A — Linux VPS with Certbot (Nginx / Apache)

Prereq: an HTTP site already works on port 80 with a `server_name` (Nginx) / `ServerName`+`ServerAlias` (Apache) matching the domains.

**Install (official, recommended: snap)** — per certbot.eff.org instructions:
```bash
sudo apt-get remove certbot            # only if an old apt certbot exists (confirm first)
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
```
**Alternative (distro packages):**
- Debian/Ubuntu: `sudo apt update && sudo apt install certbot python3-certbot-nginx` (Apache: `python3-certbot-apache`)
- RHEL/Rocky/AlmaLinux: `sudo dnf install epel-release && sudo dnf install certbot python3-certbot-nginx` (Apache: `python3-certbot-apache`, server package `httpd`)

**Back up + test config, then issue & install:**
```bash
sudo cp -a /etc/nginx /root/nginx-backup-$(date +%F-%H%M)
sudo nginx -t
sudo certbot --nginx -d example.com -d www.example.com      # Apache: sudo certbot --apache -d ... 
```
Recent Certbot versions add the HTTP→HTTPS redirect automatically. Conservative variant (no config edits): `sudo certbot certonly --nginx -d ...` then edit config by hand. No web server plugin possible: `sudo certbot certonly --webroot -w /var/www/html -d example.com`.
Wildcard: needs DNS-01, e.g. Cloudflare DNS plugin (`sudo snap set certbot trust-plugin-with-root=ok && sudo snap install certbot-dns-cloudflare`, credentials file `chmod 600`, token with Zone:DNS:Edit) or `--manual --preferred-challenges dns` (manual = no auto-renew; avoid for beginners).

**Expected success output** contains `Successfully received certificate.` and `Congratulations! You have successfully enabled HTTPS on https://example.com`.

**Renewal:** `sudo certbot renew --dry-run` must end with `Congratulations, all simulated renewals succeeded`. Timer: `systemctl list-timers | grep -i certbot` (snap: `snap.certbot.renew.timer`; apt: `certbot.timer` or `/etc/cron.d/certbot`).

**Common failures:** `Type: connection … Timeout/connection refused` → port 80 blocked / site down; `NXDOMAIN` / wrong IP → DNS; `unauthorized … 404` → wrong webroot, a redirect/rewrite, or another server answering (check AAAA); `too many failed authorizations` → stop, wait, use `--dry-run`.

## 5. Path B — 宝塔面板 (BT Panel)

Per docs.bt.cn (updated 2026-09-30). User clicks; you guide:
1. 登录面板 → 左侧 **网站** → 点击站点域名（或“设置”）。
2. 弹窗左侧点 **SSL**。
3. 选 **Let's Encrypt**（部分版本标签叫“免费证书”）→ **申请证书**。
4. 证书品牌：Let's Encrypt（国际通用）或 LiteSSL；算法：RSA2048（兼容性最好）；验证方式：**文件验证**（域名已解析到本机、80 端口可达）或 **DNS 验证**（通配符必须用这个；先在面板左侧 **SSL → 域名管理 → DNS接口设置 → 添加DNS接口** 填 DNS 服务商 API 密钥——由用户本人填写）。
5. 勾选域名（根域名和 www 都勾）→ 申请证书 → 等待成功并保存/部署。
6. 打开 **强制HTTPS**（先确认站点没有别的 301 跳转规则，否则可能循环）。
7. 若 HTTPS 打不开：检查云安全组 443；若站点开着 CDN/反向代理/301，验证与续签可能失败。
Free certs are 90 days and auto-renew by default; verify in the SSL page that renewal is on.

## 6. Path C — cPanel AutoSSL

1. cPanel → **Security → SSL/TLS Status** (in newer cPanel this may be a tab inside **SSL/TLS Certificates**, or in the new "Security hub").
2. Check domains are **Included** in AutoSSL → click **Run AutoSSL** → wait for “AutoSSL is in progress…” to finish.
3. Read the per-domain error if any (often "DNS does not resolve to this server" for unused subdomains like `mail.`/`cpanel.` — exclude those or fix DNS).
4. If the interface is missing, the host disabled the feature → user contacts host support (provide them a ready-made message).
5. Force HTTPS: cPanel **Domains → Domains → Force HTTPS Redirect** toggle (needs a valid cert first; if missing, ask the host) or an `.htaccess` rule.
AutoSSL certs (Let's Encrypt or Sectigo) are 90-day and renew automatically.

## 7. Path D — Cloudflare

### 7a. Dashboard path (beginner default)
1. Domain must be active on Cloudflare (Add a domain → Free plan → review DNS → change nameservers at the registrar to the two `*.ns.cloudflare.com` names shown → wait until Cloudflare says Active).
2. Website DNS records (A/CNAME for `@` and `www`) should be **Proxied** (orange cloud) for Cloudflare's free edge certificate to be used.
3. **SSL/TLS → Overview**: if "Automatic SSL/TLS" is on, Cloudflare picks the mode; otherwise choose **Full (strict)** once the origin has a valid cert (Let's Encrypt or Cloudflare Origin CA). Avoid **Flexible** (origin traffic unencrypted; loops if origin forces HTTPS).
4. Origin cert: **SSL/TLS → Origin Server → Create Certificate** → Cloudflare-generated key (RSA/ECC) → hostnames (`example.com`, `*.example.com`) → validity (up to 15 years) → copy certificate and private key into two files on the server (key shown only once). Install in Nginx/Apache/panel. Cloudflare does **not** send expiry notifications for Origin CA certs — record the date.
5. **SSL/TLS → Edge Certificates → Always Use HTTPS = On**; optionally **Automatic HTTPS Rewrites = On** (helps mixed content). Do not also force redirects at origin if it causes loops.

### 7b. API path (only if user wants automation)
**Token (least privilege)** — user creates it in Dashboard → My Profile → API Tokens → Create Token → Custom, **Zone Resources: Include → Specific zone → example.com**, permissions:
- `Zone → SSL and Certificates → Edit` (Origin CA certificate creation; Cloudflare docs: token needs `Zone - SSL and Certificates - Edit`)
- `Zone → Zone Settings → Edit` (SSL mode, Always Use HTTPS)
- `Zone → DNS → Read` (verify records/proxy status)
- optional `Zone → Zone → Read` only if you need to look up the Zone ID via API — otherwise the user copies **Zone ID** from the domain's Overview page.
Set a short TTL / expiry on the token and IP-restrict it if possible; revoke after the job.

The user sets variables in **their own** shell (never pasted into chat):
```bash
read -s CF_API_TOKEN && export CF_API_TOKEN      # paste token, press Enter (not echoed)
export ZONE_ID=<your_zone_id>                     # from Cloudflare Overview page
API=https://api.cloudflare.com/client/v4
AUTH=(-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json")
```
Verify token (read-only): `curl -s "${AUTH[@]}" $API/user/tokens/verify` → `"status":"active"`.

Read current state (read-only):
```bash
curl -s "${AUTH[@]}" $API/zones/$ZONE_ID/settings/ssl
curl -s "${AUTH[@]}" $API/zones/$ZONE_ID/settings/always_use_https
curl -s "${AUTH[@]}" $API/zones/$ZONE_ID/settings/ssl_automatic_mode   # "auto" = Automatic SSL/TLS manages the mode
curl -s "${AUTH[@]}" "$API/zones/$ZONE_ID/dns_records?type=A"          # check "proxied": true
```
Create Origin CA certificate (generate key + CSR on the server; key never leaves it):
```bash
sudo mkdir -p /etc/ssl/cloudflare && cd ~
sudo openssl req -new -newkey rsa:2048 -nodes -keyout /etc/ssl/cloudflare/origin.key -out ~/origin.csr -subj "/CN=example.com"
sudo chmod 600 /etc/ssl/cloudflare/origin.key          # private key stays on the server, never printed
CSR=$(awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' ~/origin.csr)
curl -s "${AUTH[@]}" -X POST $API/certificates -d "{
  \"csr\": \"$CSR\",
  \"hostnames\": [\"example.com\", \"*.example.com\"],
  \"request_type\": \"origin-rsa\",
  \"requested_validity\": 5475
}" > ~/origin-resp.json
python3 -c 'import json,os; r=json.load(open(os.path.expanduser("~/origin-resp.json"))); print("success:", r["success"], r.get("errors")); r["success"] and open(os.path.expanduser("~/origin.pem"),"w").write(r["result"]["certificate"])'
sudo mv ~/origin.pem /etc/ssl/cloudflare/origin.pem && rm -f ~/origin-resp.json ~/origin.csr
```
(`requested_validity` allowed: 7, 30, 90, 365, 730, 1095, 5475 days. `request_type`: `origin-rsa` or `origin-ecc`.) Point the web server at `/etc/ssl/cloudflare/origin.pem` + `/etc/ssl/cloudflare/origin.key`, `nginx -t`, reload.

Set mode and redirect (state changes → confirm first):
```bash
# If ssl_automatic_mode is "auto" and you want a fixed mode, switch to custom first:
curl -s "${AUTH[@]}" -X PATCH $API/zones/$ZONE_ID/settings/ssl_automatic_mode -d '{"value":"custom"}'
curl -s "${AUTH[@]}" -X PATCH $API/zones/$ZONE_ID/settings/ssl -d '{"value":"strict"}'           # Full (strict)
curl -s "${AUTH[@]}" -X PATCH $API/zones/$ZONE_ID/settings/always_use_https -d '{"value":"on"}'
curl -s "${AUTH[@]}" -X PATCH $API/zones/$ZONE_ID/settings/automatic_https_rewrites -d '{"value":"on"}'   # optional
```
`ssl` values: `off`, `flexible`, `full`, `strict`. Only set `strict` **after** the origin serves a valid cert for the hostname, or visitors get Error 526. Rollback: PATCH `ssl` back to its previous value (you read it above).

## 8. Path F — Windows Server / IIS (win-acme)

1. Download the latest `win-acme.v2.x.x.x64.trimmed.zip` from https://www.win-acme.com (or GitHub releases); unzip to `%ProgramFiles%\win-acme` (not a temp folder — the scheduled task needs it).
2. Run `wacs.exe` as Administrator → **N: Create certificate (default settings)** → **Read bindings from IIS** → pick site/hostnames → accept terms, email.
3. win-acme validates over HTTP (port 80 must be reachable), installs the cert into the Windows store, updates IIS HTTPS bindings, and creates a **Task Scheduler** renewal task.
4. Verify: `wacs.exe` → **A: Manage renewals**; check Task Scheduler entry exists; browse `https://`.
5. Open Windows Firewall + cloud security group for 80/443.

## 9. Path E — SaaS builders

Usually automatic and free after the custom domain is connected correctly:
- **Shopify**: Settings → Domains — SSL is issued automatically for connected domains (can take up to ~48 h after DNS is correct).
- **Wix**: HTTPS is on by default for connected domains.
- **WordPress.com**: free SSL automatically for hosted domains.
If it stays "pending": check DNS records exactly match the platform's instructions (A/CNAME), remove conflicting AAAA/old A records, remove CAA records that exclude the platform's CA, then contact platform support. Never pay a third party to "install SSL" on a SaaS store.

---

## 10. Verification (always do all)

```bash
curl -sI http://example.com | head -3              # expect 301 → https://
curl -vI https://example.com 2>&1 | grep -E "subject:|expire date|issuer:|verify ok|HTTP/"
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -ext subjectAltName
```
Then: browser check (Chrome: click the icon left of the URL → 连接是安全的 → 证书有效 → check 颁发给/有效期); both `example.com` and `www.`; mobile too. Run **SSL Labs** `https://www.ssllabs.com/ssltest/analyze.html?d=example.com` (aim A/A+; don't fix every advisory for a beginner). Mixed content: browser DevTools console warnings; fix `http://` links in theme/settings/database (WordPress: Settings → General URLs; search-replace plugin), or Cloudflare Automatic HTTPS Rewrites.

## 11. Renewal & monitoring

- Let's Encrypt certificates are currently 90 days; Let's Encrypt plans 64-day default from **2027-02-10** and 45-day from **2028-02-16**; the CA/B Forum max lifetime is 200 days (from 2026-03-15) → 100 days (2027-03-15) → 47 days (2029-03-15). **Manual renewal is not viable — automation must work.**
- Let's Encrypt **stopped sending expiry emails on 2025-06-04** → set up an external monitor (e.g. Red Sift Certificates Lite — free for up to 250 certs, recommended by Let's Encrypt; or any uptime monitor with SSL-expiry alerts). The user signs up themselves.
- Put a calendar reminder for Cloudflare Origin CA expiry if used.

## 12. Rollback

- Nginx/Apache: restore backup (`sudo cp -a /root/nginx-backup-…/. /etc/nginx/`), `nginx -t`, reload. Or remove the `# managed by Certbot` lines.
- Certbot cert removal (only if asked): `sudo certbot delete --cert-name example.com` (after removing references from config).
- 宝塔: SSL page → 关闭SSL / 关闭强制HTTPS.
- Cloudflare: set `ssl` back to previous value; turn Always Use HTTPS off; Pause Cloudflare as last resort (Overview → Advanced actions).
- Nameserver change rollback: restore the registrar's previous NS values (record them before changing!).

## 13. Final report to user (template)

✅ 已完成：域名 / 证书签发机构 / 到期日 / 自动续期方式（timer/面板/AutoSSL/Cloudflare）/ HTTP→HTTPS 跳转 / SSL Labs 评级。
⚠️ 你需要做：注册证书到期监控；（如用过 API 令牌）到 Cloudflare 撤销令牌；备份保存位置。
📌 回滚方法：一句话说明。
