2026.08 / DKIM signing 050
Docker Mailserver Not Signing Outgoing Email with DKIM?
A message sent through Docker Mailserver reaches an external mailbox, but its raw source has no DKIM-Signature header and the receiver reports dkim=none. Publishing another TXT record at random will not make the signer run. The missing signature has to be traced across four separate boundaries: was the message eligible for signing, did the active signer select a domain and private key, was the signature preserved in transit, and could the receiver retrieve the matching public key?
This guide uses example.com, mail.example.com, and selector mail as placeholders. Keep real addresses, account names, server addresses, complete configuration output, and private-key contents out of public troubleshooting transcripts.
dkim=noneanddkim=failare different incidents. First prove whether a signature exists in the complete received message; only then debug signing or verification.
Start with the receiver’s complete raw message
Download “Show original” or “View source” from a newly delivered test message. Search the untouched source for every DKIM-Signature: header and the final receiver’s Authentication-Results:. Do not inspect a forwarded copy or only the webmail summary.
| Evidence | Meaning | Next boundary |
|---|---|---|
No DKIM-Signature; dkim=none | No signature reached the verifier | Eligibility, signer, key selection, or a relay that removed it |
Signature exists; no key | The signer ran, but selector DNS is unavailable or wrong | Query s= + d= publicly |
Signature exists; dkim=fail | The key or signed message did not verify | Read the exact failure reason |
dkim=pass; dmarc=fail | Cryptography worked, but the signing domain may not align with From | Compare header.d and visible From domain |
Give the test a unique subject token. Record its timestamp and Docker Mailserver queue ID privately so every later log line belongs to the same message.
1. Confirm the image and active signing implementation
Current Docker Mailserver documentation integrates DKIM signing with Rspamd. Older pinned releases and migrated installations may still contain OpenDKIM-era configuration. Identify what is actually running before copying a fix:
docker compose images mailserver
docker compose ps mailserver
docker compose exec mailserver sh -lc \
'pgrep -a rspamd || true; pgrep -a opendkim || true'
docker compose logs --since 20m mailserver | \
grep -Ei 'dkim|rspamd|opendkim|milter|sign|queue'
Do not dump the complete environment or print configuration files that may contain credentials and infrastructure details. If the logs show neither an active signer nor a signing attempt, check the security features enabled for the exact image tag and compare them with that version’s Docker Mailserver documentation.
2. Prove the message was submitted through a signable path
The clean baseline is a mail account authenticated on submission port 587 with STARTTLS (or the documented implicit-TLS submission service), using a full mailbox identity such as [email protected]. Send one plain-text message that way before debugging an application container.
Rspamd’s signing policy can select authenticated messages and messages from explicitly recognised local networks. A web application that opens port 25 anonymously from an arbitrary Docker network is not equivalent to authenticated submission. Do not “fix” that by trusting a broad private address range: any compromised container in that range could then submit mail under a local identity. Prefer SMTP AUTH with a dedicated least-privilege mailbox, TLS, and normal submission policy.
Correlate the test in the logs:
docker compose logs --since 20m mailserver | grep 'UNIQUE_SUBJECT_TOKEN'
# Then use the discovered queue ID for the full path
docker compose logs --since 20m mailserver | grep 'QUEUE_ID'
If authenticated client mail is signed but application mail is not, the key and DNS are probably healthy. The fault is the application’s submission route or identity, not DKIM generation.
3. Check that a persisted key exists for the From domain
Docker Mailserver’s supported helper generates DKIM material with good defaults. Run the help from your pinned image before generating or replacing anything:
docker compose exec mailserver setup config dkim help
# Inventory names and permissions only; never print private keys
docker compose exec mailserver sh -lc \
'find /tmp/docker-mailserver -type f \
\( -path "*dkim*" -o -path "*opendkim*" \) \
-printf "%p %m\n" 2>/dev/null'
If no key exists for a newly added mail domain, follow the current official setup config dkim procedure and restart Docker Mailserver as documented. Generate keys only after at least one mail account exists, preserve a private backup, and publish only the generated public DNS value. Never paste a private key into DNS, Git, an issue, or a chat.
If keys exist on the host but disappear after container recreation, the configuration directory is not mounted or persisted as intended. Repair the Compose mount rather than generating a new key on every start. If the signer reports permission denied, restore the ownership and restrictive mode expected by the image; do not make signing keys world-readable.
4. Match the signing domain to the visible From domain
Multi-domain installations expose selection mistakes quickly. A mailbox may authenticate as one domain while an application supplies a different visible From:. The signer needs a private key and supported domain mapping for the identity it selects. DMARC additionally requires the successful DKIM d= domain to align with the visible From domain.
For one test message, record these identities separately:
- SMTP authenticated username;
- envelope sender / Return-Path domain;
- visible RFC5322
From:domain; - expected DKIM
d=domain ands=selector.
Do not let a contact form impersonate a visitor’s external address in From:. Use a local address you control in From, put the visitor in Reply-To:, and preserve safe input validation. That provides a stable local signing identity and avoids DMARC alignment failures for someone else’s domain.
5. Validate Rspamd configuration before restarting
Rspamd supports policy controls such as signing authenticated or local messages, domain-based key selection, selector choice, and key paths. Docker Mailserver supplies an integrated configuration, so custom overrides should be minimal and live in the documented persisted override directory—not in generated files inside the running container.
# Validate active Rspamd configuration without displaying private keys
docker compose exec mailserver rspamadm configtest
# Read recent signer decisions around the matching queue ID
docker compose logs --since 20m mailserver | \
grep -Ei 'QUEUE_ID|dkim_signing|cannot load|private key|selector|domain'
A config test catches syntax and nesting errors, but it does not prove the policy selects your message. Logs may reveal that the message was not authenticated or local, the domain was absent, the selector could not be resolved to a key, or the key was unreadable. Change the narrowest responsible setting and retain a rollback copy.
6. Restart safely and verify that the signature is added
After generating a missing persisted key or changing supported configuration, restart the mail service deliberately:
docker compose restart mailserver
docker compose ps mailserver
docker compose logs --since 5m mailserver
Do not use docker compose down -v; missing DKIM is not a reason to delete mail or state volumes. Send a completely new authenticated message. Old queued messages may retain their previous processing result, so they are poor proof of a repaired signer.
If local logs say the message was signed but the external raw message contains no signature, inspect every downstream relay, smarthost, and gateway. A later system may rebuild the message or remove headers. Compare direct delivery with the relay route using separate harmless test messages.
7. Query the exact selector in public DNS
Once the new message contains DKIM-Signature, copy only its non-secret d= and s= tags. The verifier queries selector._domainkey.domain:
dig +short TXT mail._domainkey.example.com @1.1.1.1
dig +short TXT mail._domainkey.example.com @8.8.8.8
The response should be one coherent DKIM public-key record. A Cloudflare-hosted DKIM TXT record is DNS-only by nature; there is no HTTP proxy switch for TXT records. Long TXT values may appear as several quoted strings and are concatenated by DNS clients. Avoid publishing two conflicting records at the same selector.
DNS cannot explain a genuinely absent signature, but it determines whether a newly present signature verifies. If you are replacing a key, use a new selector and a DNS-first overlap rather than changing key material under a cached selector; the DKIM rotation runbook covers that sequence.
8. Require end-to-end authentication evidence
The repair is complete only when the raw source of a fresh externally received message shows:
DKIM-Signature: ... d=example.com; s=mail; ...
Authentication-Results: receiver.example;
dkim=pass header.d=example.com header.s=mail;
dmarc=pass header.from=example.com
Also require a matching remote 250 delivery response in the sender log and repeat one test for every hosted From domain. Inbox placement is a later deliverability question; a message can pass authentication and still be filtered for reputation or content reasons.
Common wrong turns
- Adding DNS before proving a signature exists: a public key cannot activate a local signer.
- Regenerating keys immediately: this can overwrite a working identity and create a DNS cache mismatch.
- Trusting an entire Docker subnet: broadens relay or signing trust beyond the application that needs it.
- Testing only the inbox badge: preserve the complete raw message and exact authentication result.
- Editing the running container: the change disappears on recreation; use supported persisted overrides.
- Assuming one domain proves all domains: every signing domain needs the correct key, mapping, DNS, and alignment.
- Publishing private material: DNS contains only the public key; private keys stay restricted and backed up.
- Weakening DMARC: policy changes do not make Docker Mailserver add a signature.
Compact acceptance checklist
- A complete fresh received message—not a forwarded copy—shows whether DKIM is absent or failed.
- The test is matched to one queue ID and a narrow private log window.
- The pinned Docker Mailserver image and active Rspamd or legacy signer are identified.
- Authenticated submission mail is tested separately from application mail.
- A persisted, restricted key exists for the intended signing domain.
rspamadm configtestpasses and logs show a successful signing decision.- The fresh message contains the expected
d=domain ands=selector. - The exact selector’s public TXT record resolves through independent DNS resolvers.
- The recipient reports
dkim=passand aligneddmarc=pass. - Every hosted sending domain passes its own controlled test.
Authoritative references: Docker Mailserver’s official DKIM, DMARC, and SPF guide documents key generation, DNS publication, restarts, and message verification. Its Rspamd integration guide covers the current filtering and signing stack. Rspamd’s official DKIM signing module reference explains message eligibility, domain selection, selectors, keys, and signing policy. Use the pages matching your pinned versions.