With this script, I tried to update the domains (manual cert management), but although

Your question

With this script, I tried to update the domains (manual cert management), but although the script runs without errors, it doesn’t seem that the certs are actually beeing upgrade. is there a different working way, how this can be achieved? Reading the certs from disc?

#!/bin/bash
set -euo pipefail

MY_DOMAINS=(“mail.example.com”)

for RENEWED_DOMAIN in $RENEWED_DOMAINS; do
for MY_DOMAIN in “${MY_DOMAINS[@]}”; do
if [ “$RENEWED_DOMAIN” = “$MY_DOMAIN” ]; then
CERTIFICATE_DIR=“/etc/stalwart/certs/$RENEWED_DOMAIN”
mkdir -p “$CERTIFICATE_DIR”
cp “$RENEWED_LINEAGE/fullchain.pem” “$CERTIFICATE_DIR/fullchain.pem”
cp “$RENEWED_LINEAGE/privkey.pem” “$CERTIFICATE_DIR/privkey.pem”
chown stalwart:stalwart “$CERTIFICATE_DIR/fullchain.pem” “$CERTIFICATE_DIR/privkey.pem”
chmod 640 “$CERTIFICATE_DIR/privkey.pem”
fi
done
done

Trigger Stalwart to re-read all cert files (no restart needed)

stalwart-cli --url https://mail.hagelloch.de --api-key API_AAAAAQ create action/ReloadTlsCertificates

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

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.