The core issue is that your script copies PEM files to disk and then calls ReloadTlsCertificates, but Stalwart’s manual certificate mode does not simply read from arbitrary paths on disk. Certificates are stored as Certificate objects in Stalwart’s database, and file-path references in those objects are only useful if the Certificate record’s certificate/privateKey fields were configured to point to those specific file paths.
Here is what is actually happening and how to fix it:
How manual certificates work in Stalwart:
- Each certificate is a
Certificate object stored in the database (managed via the WebUI under Settings › TLS › Certificates, or via stalwart-cli).
- The
certificate and privateKey fields accept either inline PEM text, an environment variable reference, or a file path (@type: "File").
ReloadTlsCertificates tells the server to re-read those Certificate objects from the store (and, if the fields are file references, to re-read the files at those paths).
Two valid approaches for a Certbot renewal hook:
Option A - Use file references (simplest): Configure the Certificate object so its certificate and privateKey fields point directly to your Certbot lineage paths (e.g. /etc/letsencrypt/live/mail.example.com/fullchain.pem). Then your renewal hook only needs to call ReloadTlsCertificates and does not need to copy files at all.
Option B - Update the Certificate object via the CLI: After copying the files, use stalwart-cli to update the Certificate record’s contents directly (pushing the new PEM data inline or updating the file path). Then call ReloadTlsCertificates.
With your current script, if the Certificate objects in the database still point to the old inline PEM data (not to the /etc/stalwart/certs/... paths you are copying to), the ReloadTlsCertificates action will re-read the old data and nothing changes.
The recommended fix is Option A: edit the Certificate object in the WebUI or via stalwart-cli and set:
{
"certificate": { "@type": "File", "filePath": "/etc/letsencrypt/live/mail.example.com/fullchain.pem" },
"privateKey": { "@type": "File", "filePath": "/etc/letsencrypt/live/mail.example.com/privkey.pem" }
}
Then your Certbot hook only needs the ReloadTlsCertificates call, with no file copying.
Relevant docs: Manual certificate management and Certificate object reference.
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.