# DKIM Record Publishing — Cloudflare ## Problem OpenDKIM on Hetzner was signing outbound mail for all 21 client domains correctly (private keys generated, milter running, signatures added to every message) — but the **public keys were never published to DNS**. This caused Gmail/other recipients to fail DKIM verification, drop the spam score, and quarantine the mail. **Symptom:** Wordfence alerts (and other transactional email from Kaburu sites) consistently flagged as spam in Gmail. ## Root Cause CyberPanel's email installer generates DKIM keys locally and configures OpenDKIM, but does **not** publish the public key TXT records to Cloudflare. The records at `default._domainkey.` were either missing or contained the wrong (empty/stale) value. ## Fix Applied (2026-07-30) Published `default._domainkey.` TXT records for all 21 Kaburu domains: - 24hrresponse.com - aberdeenrda.co.uk - bankhousecatering.co.uk - claybusters.scot - dm-ph.com - doctorfin.co.uk - friendscic.org - ggsgenerators.co.uk - jafricasafari.com - kaburu.cc, kaburu.co, kaburu.co.uk - kaburusvr.uk - l8waterhygiene.co.uk - longniddrybowlingclub.com, .co.uk - myretonmarquees.co.uk - pilates-edinburgh.co.uk - thechippyvan.co.uk - touchwell.co.uk - womenunlimited.africa ## Verification ```bash dig TXT default._domainkey.24hrresponse.com +short # Should return: "v=DKIM1; h=sha256; k=rsa; p=MIIBIjA..." ssh root@kaburusvr 'tail -20 /var/log/mail.log | grep opendkim' # Should show: "DKIM-Signature field added (s=default, d=24hrresponse.com)" ``` ## Re-running the Publish Script Located at: `/root/publish-dkim.sh` on kaburusvr (Hetzner) What it does: 1. Reads CF token from `/root/.secrets/cloudflare` 2. Lists all zones in the Cloudflare account 3. For each domain in `/etc/opendkim/keys/`: - Extracts the public key from `default.txt` - Looks up the zone ID - Creates or updates the `default._domainkey.` TXT record When to re-run: - A new domain is added to the Hetzner server - DKIM keys are regenerated (e.g. by CyberPanel reinstall) - A new site is migrated to the server - DNS records need rotation (every 6-12 months for hygiene) ## Gotchas - **`grep -F "^domain|"` doesn't work** — `^` with `-F` treats `|` as literal. Use `awk -F'|' '$1 == d'` instead. Caught on first run. - The script lives on Hetzner because it needs the CF token from `/root/.secrets/cloudflare` and the OpenDKIM keys from `/etc/opendkim/keys/`. Don't move it elsewhere. - DNS propagation: Cloudflare typically serves new records within 1-5 minutes. No need to wait for TTL. - DNSSEC warning ("key not secure") from `opendkim-testkey` is informational only, doesn't block signing. ## Related - [[opnsense]] — OPNsense firewall (separate email chain, Brevo SMTP fallback) - Wiki: `wiki/infrastructure/opnsense.md` — Unbound/DNS notes - Wiki: `wiki/web/cyberpanel.md` — CyberPanel-specific email setup ## Verification log (2026-07-30) Before fix: ``` $ dig TXT default._domainkey.24hrresponse.com +short # (empty) ``` After fix: ``` $ dig TXT default._domainkey.24hrresponse.com +short "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi6SuVjoBZaUmtPVD+p6Nel23K5gFpJHfHR5hlM8r73AVjmC7toVZKblrIob0RFhe8xcT1IYS3vE0IECxV8oOlBSEFgKGVPkBvKt9gh3AqvG+LB8yeVOho7lypFScRZiENBE3GO+/Rj3wCjPq19BUn87TsNfB3EHO9IPjUykFACO91nUns0QwJkL..." "W5hu71HjOcExh+qNOumDpE1" ``` Op en DKIM log confirmed signing active throughout: ``` 2026-07-30T18:36:28 opendkim[1538]: 7AE90C5D26: DKIM-Signature field added (s=default, d=24hrresponse.com) ... (5 more 24hrresponse.com entries from active brute-force block alerts) ```