CyberPanel's SSL Auto-Renewal Can Silently Fail — Here's the Fix
Last week I was doing a routine check on a CyberPanel server and noticed something that didn't add up. The panel's SSL status showed a clean "SSL Issued Successfully". Nothing to see, apparently. Except the certificate expiration date hadn't moved.
The browser was still serving a certificate that was about to expire.
That's the kind of mismatch that's easy to miss if you trust the panel's UI at face value — the message says success, the log (if you even check it) looks uneventful, and you move on. I didn't move on.
Checking what's actually served
The panel tells you what it thinks happened. What matters is what the server actually hands out to a browser. That's a one-liner away:
openssl s_client -connect domain.com:443 -servername domain.com /dev/null | openssl x509 -noout -dates
This outputs something like:
notBefore=Jul 31 22:59:26 2026 GMT
notAfter=Oct 29 22:59:26 2026 GMT
Wait — that date range looked fine on its own. The real tell was comparing it against what the panel had just claimed to renew: the dates hadn't changed since before the "successful" renewal. Whatever acme.sh had just done, it hadn't produced a new certificate that OpenLiteSpeed was actually serving.
Checking what acme.sh actually did
CyberPanel doesn't implement ACME itself — it delegates to acme.sh under the hood. So the next step was checking what acme.sh had registered:
acme.sh --list
That's where the mismatch became clearer. The certificate authority listed for this domain wasn't Let's Encrypt production — it was the staging CA. Staging certificates are useful for testing, but they're not production certificates trusted by browsers. In this case, the important part wasn't just that staging was configured — it was that the renewal workflow no longer matched the certificate actually served by OpenLiteSpeed. CyberPanel reported success because, from its point of view, acme.sh had returned successfully. It just wasn't returning the certificate the site needed.
I can't say for certain how this CyberPanel installation's acme.sh configuration ended up pointed at the staging CA — only that it had. Worth keeping in mind if you're chasing a similar mismatch: the fix doesn't require knowing the origin, just confirming the current state.
The fix
Once the mismatch was clear, the fix was mechanical:
- Force acme.sh back onto the Let's Encrypt production CA.
- Drop the existing (staging) registration for the domain.
- Reissue the certificate from scratch, this time against production.
- Install it where CyberPanel and OpenLiteSpeed expect it:
/etc/letsencrypt/live//. - Reload OpenLiteSpeed so it picks up the new files.
None of this is exotic — it's exactly what acme.sh is designed to do. The problem was never the tooling, it was that CyberPanel's reported status wasn't a reliable signal of what state the certificate was actually in.
Turning it into something reusable
Fixing one server by hand is fine once. But when the same class of drift keeps showing up across CyberPanel releases, it stops being a one-off and starts being something worth scripting around. I wrote two tools:
-
renew-ssl.sh— routine renewal, bypassing CyberPanel's scheduler entirely. It supports an auto mode (renews anything expiring within a configurable threshold, default 10 days), a single-domain mode, and a--checkmode that simulates the run and just reports what would happen — useful for confirming a diagnosis like this one before touching anything. -
fix-ssl.sh— the heavier tool, for when a certificate is actually broken or stuck in staging. Given that the whole premise of this post is "don't trust the reported status, verify it," the script holds itself to the same standard on both ends:- Before touching anything, it writes a test file into the HTTP-01 challenge path and fetches it over plain HTTP, the same way Let's Encrypt will. If that fails, it stops before issuing anything — no point calling acme.sh against a webroot that can't serve the challenge.
- It backs up the existing certificate and acme.sh registration to a timestamped archive before removing anything, and refuses to proceed if the backup itself fails.
- It forces the production CA, clears the old registration, reissues via webroot, and installs the new certificate.
- After reloading OpenLiteSpeed, it doesn't just assume the reload worked — it fetches the certificate actually served over HTTPS and compares its SHA256 fingerprint against the one just installed. A mismatch is reported as an error, with the exact restore command from the backup printed alongside it.
Both are plain Bash, no dependencies beyond acme.sh and openssl (fix-ssl.sh also uses curl for the webroot check, though it degrades gracefully — with a warning — if curl isn't available). Both are meant to be run as root since they touch /etc/letsencrypt/, /root/.acme.sh/, and the OpenLiteSpeed service.
Source, with usage examples and a debugging section covering both commands above:
CyberPanel SSL Renewal Scripts
Why this is worth checking on your own servers
If you're running CyberPanel and haven't looked recently, it's worth running the openssl s_client check against your actual domains — not because CyberPanel is broken in general, but because "the panel says success" and "the certificate is actually valid and current" are two different claims, and only one of them is verifiable from outside the panel.
Have you seen similar ACME renewal drift on CyberPanel, OpenLiteSpeed, or other hosting panels? I'd be interested in comparing root causes — certificate automation failures are often harder to diagnose than certificate issuance itself.

