Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stalwart + Caddy + Cloudflare: Certificate Sync

Keeps Stalwart Mail Server's TLS certs (and optionally its DANE/TLSA record in Cloudflare) in sync with certs issued by Caddy.

The set of scripts consists of:

  • stal-lib-cert-copy.sh - shared library, sourced by the other two scripts (not run directly)
  • stal-copy-caddy-certs.sh - manually copy a Caddy cert+key into Stalwart's certs dir
  • stal-tlsa-update.sh - unattended (cron/systemd): copies the cert, restarts Stalwart, updates the Cloudflare TLSA record if the cert changed
  • install.sh - installs all scripts and (optionally) the systemd timer

All scripts must stay in the same directory.

Requirements

sudo apt update && sudo apt install openssl dnsutils curl jq

Docker is assumed to be installed already, since that's how Stalwart runs.

Install

sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/stackfusion/stalwart-caddy-certs/HEAD/install.sh)"

Or, from a local clone:

sudo ./install.sh

Either way, this copies the scripts to /usr/local/lib/stalwart-caddy-certs/, symlinks them into /usr/local/bin/, and (interactively) offers to install + enable the systemd timer (runs every 5 minutes). For non-interactive installs, set SETUP_TIMER=y to enable the timer automatically:

sudo SETUP_TIMER=y bash -c "$(curl -fsSL https://raw.githubusercontent.com/stackfusion/stalwart-caddy-certs/HEAD/install.sh)"

You'll still need to fill in your Cloudflare credentials in /etc/stal-tlsa-update.env afterward — see below.

Manual cert copy

sudo stal-copy-caddy-certs.sh -d mx.example.com -s ./stalwart-etc/certs/

Copies the cert/key with correct ownership (uid/gid 2000:2000, mode 640) and atomic rename, so Stalwart never sees a partial write.

stal-tlsa-update.sh

On each run: compares the local cert's public-key hash to Cloudflare's published TLSA record. If unchanged, exits. If changed: copies the cert into place, restarts Stalwart, and updates the TLSA record (DANE-EE, 3 1 1).

Warns if the Caddy cert is missing the intermediate (leaf-only), which can break DANE validation for clients without Let's Encrypt's intermediate cached.

Uses flock (/var/lock/stal-tlsa-update.lock) so only one run happens at a time.

Configuration

All variables below have defaults and can be overridden via environment:

Variable Default
DOMAIN example.com
SUBDOMAIN mx
PORT 25
CADDY_CERT_PATH derived from DOMAIN/SUBDOMAIN
STALWART_CERT_DIR /opt/stalwart/certs
STALWART_CONTAINER stalwart
CLOUDFLARE_NAMESERVER @anastasia.ns.cloudflare.com
LOCK_FILE /var/lock/stal-tlsa-update.lock

CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID are required, with no default.

Cloudflare credentials

  1. Create a token at https://dash.cloudflare.com/profile/api-tokens with Zone → DNS → Edit for your zone (optionally restrict by server IP).

  2. Get your Zone ID:

    curl https://api.cloudflare.com/client/v4/zones \
      -H "Authorization: Bearer YOUR_API_TOKEN" | jq -r '.result[] | {name, id}'
  3. Store both in a root-only env file:

    sudo install -m 600 -o root -g root /dev/null /etc/stal-tlsa-update.env
    sudo tee /etc/stal-tlsa-update.env >/dev/null <<'ENV'
    CLOUDFLARE_API_TOKEN=your-token-here
    CLOUDFLARE_ZONE_ID=your-zone-id-here
    ENV

For a one-off manual run instead:

export CLOUDFLARE_API_TOKEN="your-token-here"
export CLOUDFLARE_ZONE_ID="your-zone-id-here"
sudo -E stal-tlsa-update.sh

Caution

If a real token ends up in a chat, doc, ticket, or repo, roll it immediately at https://dash.cloudflare.com/profile/api-tokens (⋯ → Roll/Delete), then update /etc/stal-tlsa-update.env.

systemd

install.sh can set this up for you. Manually, it's:

  • /etc/systemd/system/stal-tlsa-update.service - loads /etc/stal-tlsa-update.env, runs stal-tlsa-update.sh
  • /etc/systemd/system/stal-tlsa-update.timer - runs every 5 minutes, 2 min after boot
sudo systemctl daemon-reload
sudo systemctl enable --now stal-tlsa-update.timer
sudo systemctl status stal-tlsa-update.timer
sudo journalctl -u stal-tlsa-update.service -f

cron (alternative)

*/5 * * * * . /etc/stal-tlsa-update.env && /usr/local/bin/stal-tlsa-update.sh >> /var/log/stal-tlsa-update.log 2>&1

Troubleshooting

Full chain check (should print 2):

grep -c "BEGIN CERTIFICATE" /path/to/caddy/cert.crt

Verify Cloudflare token:

curl https://api.cloudflare.com/client/v4/user/tokens/verify \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

Current TLSA record:

dig +short TLSA _25._tcp.mail.example.org

Query Cloudflare's authoritative DNS directly:

dig +short @nova.ns.cloudflare.com TLSA _25._tcp.mail.example.org

Check outbound IP version:

curl -4 https://api.cloudflare.com/cdn-cgi/trace
curl -6 https://api.cloudflare.com/cdn-cgi/trace

About

Keeps Stalwart Mail Server's TLS certs (and its DANE/TLSA record in Cloudflare) in sync with certs issued by Caddy

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages