This is the hardest kind of TLS failure to notice, because today everything is fine. The certificate is valid, the browser is happy, monitoring is green. The problem surfaces on the day the renewal cron runs — and on that day the whole site throws a security warning.
acme.sh keeps a per-domain configuration file at ~/.acme.sh/<domain>/<domain>.conf. The Le_API value in it decides which certificate authority that domain renews from, and it is fixed at the moment of the first issuance. If the certificate was ever issued with --staging, or the default CA was set to staging, every subsequent renewal comes from staging. Staging certificates are trusted by no browser. As long as the currently installed production certificate is still valid the problem is invisible; on renewal day the site becomes unreachable. Check with grep -r acme-staging ~/.acme.sh/*/*.conf.
Let's Encrypt runs two separate environments. Production signs with roots that browsers trust. Staging speaks the same protocol and runs the same flow, but signs with a deliberately fake root — and says so in the name: "Pretend Pear", "Artificial Apricot".
Staging exists so you can rehearse automation without burning through strict rate limits. The trouble starts when a rehearsal becomes a permanent setting. acme.sh writes the domain configuration at first issuance and never asks again:
# ~/.acme.sh/example.com/example.com.conf
Le_API='https://acme-staging-v02.api.letsencrypt.org/directory'
As long as that line stands, every run of acme.sh --cron goes to staging. And if the certificate currently installed is a valid production one issued by hand, there is no symptom at all. This is a genuinely time-delayed fault: its trigger is not a code change but the calendar.
One line scans every domain on the server at once:
grep -r 'acme-staging' ~/.acme.sh/*/*.conf
Empty output means you are clean. Every matching line is a domain that will break at its next renewal.
You can also ask the live server who actually signed the certificate it is serving:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -dates
If the issuer line contains "(STAGING)" or "Pretend", the problem is already live today. If it does not, but grep found a match, the problem is scheduled.
One more trap: a single server may host dozens of domains and only one of them may point at staging. Run the check across the whole directory, not per domain.
Editing the configuration file by hand is not enough, because acme.sh can regenerate it. The correct route is to reissue the certificate with the production server named explicitly:
acme.sh --issue -d example.com -d www.example.com \
--server letsencrypt \
--webroot /usr/local/lsws/Example/html \
--force
--force is required because acme.sh would otherwise skip the job: the existing certificate has not expired yet. --server letsencrypt pins Le_API back to production.
So the same mistake does not repeat on new domains, set the default CA once:
acme.sh --set-default-ca --server letsencrypt
Do not forget the install step — obtaining a certificate is not the same as deploying it. Define the target files and the reload command with --install-cert so renewals complete end to end:
acme.sh --install-cert -d example.com \
--key-file /etc/letsencrypt/live/example.com/privkey.pem \
--fullchain-file /etc/letsencrypt/live/example.com/fullchain.pem \
--reloadcmd "/usr/local/lsws/bin/lswsctrl reload"
After the fix, check three things in order:
# 1) Does the configuration point at production now?
grep Le_API ~/.acme.sh/example.com/example.com.conf
# 2) Who signed what the server is actually serving?
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -enddate
# 3) Does a renewal dry run complete cleanly?
acme.sh --cron --home ~/.acme.sh --dry-run
The third step is the valuable one: instead of waiting for renewal day, you see today what will happen on it.
What faults in this class have in common is that they stay invisible until you go looking. A simple scheduled check removes the surprise: read the certificate's expiry and issuer once a day, and alert when fewer than 21 days remain or the issuer is not what you expect.
Run the check from outside, over a TLS connection, not against the file on disk. The file can be correct while the web server is still serving something else; what matters is the certificate a visitor is handed.
Staging environments exist for testing, and test settings have a habit of leaking into permanent configuration. In acme.sh that leak hides in a single line and stays quiet for weeks. When you inherit a server, or whenever you are investigating certificate trouble, make grep -r 'acme-staging' ~/.acme.sh/*/*.conf the first command you type — it costs three seconds and saves a day later.
Le_API is fixed at first issuance; once staging, always staginggrep -r 'acme-staging' ~/.acme.sh/*/*.confopenssl x509 -noout -issuer--server letsencrypt --forceacme.sh --set-default-ca --server letsencryptPossibly yes. If the certificate currently installed is a valid production one issued by hand, no browser will complain. But if the acme.sh configuration points at staging, the renewal cron will install an untrusted certificate over it on the day it runs. Checking today is far cheaper than waiting for renewal day.
Not reliably. acme.sh can regenerate that file during its own operations and your manual edit may disappear. The correct approach is to reissue with --server letsencrypt --force, which both writes the file correctly and installs a production certificate immediately.
By the issuer field. Staging certificates are signed with openly fake names such as (STAGING) Pretend Pear or (STAGING) Artificial Apricot. Pipe openssl s_client output into openssl x509 -noout -issuer and the name appears directly.
No. The problem is per domain; on a server with ten domains only one may point at staging while the rest look healthy, and that one will break on renewal day. Always run the check across the whole ~/.acme.sh directory.