# SSL Certificate Issuance — Procedure & Troubleshooting
## Background
All Kaburu client sites are proxied through Cloudflare. This breaks the standard Let's Encrypt HTTP-01 challenge method. Every site on this server must use DNS-01 validation via the Cloudflare API instead.
This procedure was written after discovering (2026-05-31) that every single site on kaburusvr had been issued staging Let's Encrypt certificates since the server was built. Sites appeared to work because Cloudflare was handling SSL termination at the edge — the broken origin certs were never visible to end users. Sites not proxied through Cloudflare showed SSL errors directly.
—
## Why HTTP-01 Fails Here
When Let's Encrypt validates via HTTP-01, it makes a request to: ``` http://<domain>/.well-known/acme-challenge/<token> ``` Cloudflare intercepts this before it reaches the origin and returns 404. Validation fails silently. CyberPanel's “Issue SSL” button appeared to do something but never actually worked for this reason.
## Why DNS-01 Works
Let's Encrypt looks for a TXT record at `_acme-challenge.<domain>`. acme.sh creates this via the Cloudflare API, LE validates it, cert is issued, acme.sh cleans up the TXT record automatically. Cloudflare proxying is irrelevant to DNS validation.
—
## Normal Operation — New Site
For any new site that is already in Cloudflare:
1. Create the site in CyberPanel as normal 2. Click Issue SSL in CyberPanel 3. Done — CyberPanel detects the domain is in Cloudflare, uses dns_cf automatically,
installs the cert to `/etc/letsencrypt/live/<domain>/`, reloads LiteSpeed
No manual steps required. Auto-renewal also uses DNS-01 and runs unattended.
—
## Manual Issuance (if CyberPanel button fails)
```bash export CF_Token='<token from /root/.acme.sh/account.conf>'
/root/.acme.sh/acme.sh –issue \
/root/.acme.sh/acme.sh –install-cert -d <domain> –ecc \
```
—
## Troubleshooting Checklist
### CyberPanel Issue SSL does nothing / fails silently
Check CyberPanel can find the Cloudflare key: ```bash grep -E “SAVED_CF_Key|SAVED_CF_Email|SAVED_CF_Token” /root/.acme.sh/account.conf ``` All three must be present and non-empty. If missing: ```bash echo “SAVED_CF_Key='<token>'” » /root/.acme.sh/account.conf echo “SAVED_CF_Email='[email protected]'” » /root/.acme.sh/account.conf ``` CyberPanel checks `SAVED_CF_Key` + `SAVED_CF_Email` (legacy format). acme.sh uses `SAVED_CF_Token` which takes priority for the actual DNS API call.
### Cloudflare token invalid / 401
Test the token: ```bash curl -s https://api.cloudflare.com/client/v4/user/tokens/verify \
``` If invalid: generate a new token in Cloudflare dashboard → API Tokens → Create Token → Edit zone DNS template. Then update in three places: - `/root/.acme.sh/account.conf` — SAVED_CF_Token, SAVED_CF_Key - `/usr/local/bin/kaburu-cloudflare-mcp-run.sh` — CF_TOKEN export
### Cert issued but LiteSpeed still serving old cert
acme.sh `–install-cert` uses `systemctl reload lsws` which may not flush the cert cache. If the old cert is still being served after install: ```bash systemctl restart lsws ``` If still wrong, check what LiteSpeed is actually reading: ```bash grep -E “certFile|keyFile” /usr/local/lsws/conf/vhosts/<domain>/vhost.conf ``` The vhost uses `fullchain.pem` as certFile. Verify it's not staging: ```bash openssl x509 -issuer -enddate -noout -in /etc/letsencrypt/live/<domain>/fullchain.pem ``` If it shows `(STAGING)` in the issuer, the install wrote the wrong file. Copy directly from acme.sh source: ```bash acme_dir=“/root/.acme.sh/<domain>_ecc” dest=“/etc/letsencrypt/live/<domain>” cp “$acme_dir/<domain>.cer” “$dest/cert.pem” cp “$acme_dir/<domain>.key” “$dest/privkey.pem” cp “$acme_dir/fullchain.cer” “$dest/fullchain.pem” systemctl restart lsws ```
### Staging certs on origin (Cloudflare 526 error)
526 = Cloudflare cannot validate the origin cert (SSL mode is Full Strict). Check what the origin is actually serving: ```bash echo | openssl s_client -connect 49.13.202.144:443 -servername <domain> 2>/dev/null \
| openssl x509 -noout -issuer
``` If it shows `(STAGING) Let's Encrypt` — reissue as above.
Do not upgrade Cloudflare SSL mode to Full Strict until all origin certs are confirmed production. Check all sites first: ```bash for domain in /etc/letsencrypt/live/*/; do
d=$(basename $domain) issuer=$(openssl x509 -issuer -noout -in "$domain/fullchain.pem" 2>/dev/null) echo "$issuer" | grep -qi "staging" && echo "STAGING: $d" || echo "OK: $d"
done ```
### acme.sh issues cert but fullchain.cer not updated
If running multiple domains in parallel, nonce collisions can cause some to partially succeed — cert issued but chain not fetched, leaving stale `fullchain.cer` from a previous run. Fix: delete the entire acme dir and reissue from scratch: ```bash rm -rf /root/.acme.sh/<domain>_ecc/ /root/.acme.sh/acme.sh –issue -d <domain> -d www.<domain> –dns dns_cf \
``` Run problematic domains one at a time with a few seconds between them, not in parallel.
—
## Key File Locations
| File | Purpose |
| —— | ——— |
| `/root/.acme.sh/account.conf` | acme.sh config — CF token, default CA |
| `/root/.acme.sh/<domain>_ecc/` | acme.sh cert store per domain |
| `/etc/letsencrypt/live/<domain>/` | Installed certs — what LiteSpeed reads |
| `/usr/local/lsws/conf/vhosts/<domain>/vhost.conf` | LiteSpeed vhost — certFile/keyFile paths |
| `/usr/local/CyberCP/plogical/sslv2.py` | CyberPanel SSL issuance logic |
## Cloudflare SSL Settings
All zones should be set to Full (Strict). This is safe because all origins have valid production certs. Do not use Flexible (sends HTTP to origin, no encryption) or Full without Strict (doesn't validate origin cert).
Check/set via API: ```bash curl -s -X PATCH “https://api.cloudflare.com/client/v4/zones/<zone_id>/settings/ssl” \
```