← Back home

2026.08 / DKIM permissions 051

Docker Mailserver Rspamd Cannot Load DKIM Key? Fix Permissions

Docker Mailserver accepts and delivers an outbound message, but Rspamd logs permission denied, cannot load DKIM key, or an unreadable private-key path. The tempting fix is chmod 777. Do not do that: it turns a signing identity into a secret readable or replaceable by unrelated processes and may still leave the real mount or path problem untouched.

This runbook finds the narrowest fault: the wrong signing stack, an unexpected effective key path, a missing bind mount, a directory that the Rspamd process cannot traverse, mismatched numeric ownership, or an invalid key. Examples use example.com and selector mail. Never print or paste a private key, complete environment, real server address, or credential-bearing configuration into a public ticket.

A private key must be readable by the signer and not broadly readable. Both halves matter. Prove access as the actual Rspamd user instead of weakening permissions until the error disappears.

First prove this is a key-access failure

Send one new message through authenticated submission and give it a unique harmless subject. Keep its timestamp and queue ID private. Then inspect a narrow log window:

docker compose logs --since 15m mailserver | \
  grep -Ei 'QUEUE_ID|dkim|rspamd|permission|cannot load|private key|sign'

Separate these incidents before changing files:

EvidenceLikely boundary
Permission denied for a named key pathDirectory traversal, file ownership/mode, mount labelling, or signer identity
No such fileWrong effective path, missing persisted mount, wrong domain/selector, or absent key
PEM/parser errorEmpty, damaged, wrong-format, or accidentally public key—not primarily permissions
No signing attempt at allMessage eligibility, active stack, domain selection, or disabled signing
Signature exists but receiver says no keyPublic selector DNS, not private-key file access

1. Identify the active signer and pinned image

Current Docker Mailserver documentation uses Rspamd for DKIM when its security stack is enabled. Older releases and migrated configurations may use OpenDKIM. A permission recipe for the wrong daemon can change irrelevant files.

docker compose images mailserver

docker compose exec mailserver sh -lc \
  'pgrep -a rspamd || true; pgrep -a opendkim || true; id _rspamd 2>/dev/null || true'

Use documentation for the image tag you actually run. Do not enable both signers merely to test a permissions theory; duplicate signing and conflicting key paths make the evidence harder to interpret.

2. Discover the effective path without exposing the key

Rspamd can choose a key through a per-domain block, a selector map, or a path template containing variables such as domain and selector. Docker Mailserver supplies an integrated layout, so the path copied from a generic Rspamd tutorial may not be the path used by your container.

docker compose exec mailserver rspamadm configtest

docker compose exec mailserver sh -lc \
  'rspamadm configdump dkim_signing 2>/dev/null | \
   grep -Ei "enabled|path|selector|domain|sign_authenticated|sign_local"'

Review the output privately before sharing it: custom paths can reveal domain names or internal layout. Never run a command that prints the key file. Match the logged path with the expected From domain and selector. If the log names a different domain, fix domain selection first; changing ownership on the key you expected will not help the key Rspamd actually opened.

3. Compare host and container mounts

A host key can have perfect permissions and still be absent from the container. Inspect mount destinations without dumping environment variables:

docker inspect mailserver \
  --format '{{range .Mounts}}{{println .Type .Destination .RW}}{{end}}'

# Inside the container: names, types, owners and modes only
# Replace the placeholder with the logged parent directory, not the key contents.
docker compose exec mailserver sh -lc \
  'ls -ld /path /path/to /path/to/dkim; \
   find /path/to/dkim -maxdepth 2 -type f -printf "%p %u:%g %m\n"'

If the key was generated only inside a running container, recreation can remove it. If it exists only on the host, repair the documented configuration mount. Keep signing material in the persisted Docker Mailserver configuration location rather than adding an ad hoc second mount over an image-managed directory.

4. Test traversal and read access as Rspamd

Reading a file requires more than a readable final file. The process needs execute (traverse) permission on every parent directory. A restrictive parent owned by an unrelated host UID commonly causes EACCES.

# Show every path component and its numeric ownership/mode
# Run only against the exact private key path found in the log.
docker compose exec mailserver sh -lc \
  'namei -l /path/to/dkim/example.com/mail.private'

# Test readability as the daemon user without printing the file.
docker compose exec --user _rspamd mailserver sh -lc \
  'test -r /path/to/dkim/example.com/mail.private'

echo $?

An exit status of zero proves the process can read that path at that moment. It does not validate the key or prove signing policy. A non-zero result confirms access is broken without disclosing a byte of private material.

5. Repair ownership and modes at the persisted source

Back up the private key securely before changing metadata. Determine the numeric UID and GID used by _rspamd inside the running image, and compare them with numeric ownership on the mounted files. User names shown on the host and in the container are not reliable if their IDs differ.

docker compose exec mailserver id _rspamd

# Inspect numeric metadata; do not display file contents.
docker compose exec mailserver sh -lc \
  'stat -c "%n uid=%u gid=%g mode=%a type=%F" \
   /path/to/dkim /path/to/dkim/example.com \
   /path/to/dkim/example.com/mail.private'

Use Docker Mailserver’s supported DKIM setup/helper for your version when generating or repairing its managed key layout. If manual metadata correction is genuinely required, apply it to the persisted host source with the exact signer UID/GID and least privilege appropriate to that layout. A common end state is:

Those are principles, not a blind command. Bind-mounted UID mapping, Kubernetes secrets, rootless Docker, and image versions can require a different exact owner. Do not recursively chown the whole Docker Mailserver configuration tree: mail accounts, TLS files, Postfix maps, and other services can require different ownership.

6. Check read-only mounts and mandatory access control

A read-only mount is usually compatible with signing because Rspamd only needs to read an existing private key. It becomes relevant if a helper is trying to generate or rotate a key in place. Generate through the supported workflow and persist the result; do not make a secret directory broadly writable.

If Unix mode and ownership are correct but the daemon still receives EACCES, inspect the host’s security layer privately:

# Host-specific examples; use only the tool your system provides.
journalctl --since '15 minutes ago' | grep -Ei 'apparmor|avc:|selinux|denied'

docker inspect mailserver \
  --format '{{json .HostConfig.SecurityOpt}}'

On SELinux hosts, a bind mount can need the correct container label. Do not disable SELinux globally or run the mail container privileged. Adjust only the affected persisted mount using the platform’s documented container-labelling mechanism, then retest as _rspamd.

7. Validate the key without printing it

Once the access test passes, distinguish readable from valid. First rerun Rspamd’s configuration test and watch for a key-load error on a new message. If your pinned tooling provides a DKIM signing or key test, pass it the exact domain, selector, and path—but direct any generated signed output to a private temporary file and delete it afterward.

Do not use cat, shell tracing, CI artifact uploads, or broad debug dumps around private keys. If the parser says the file is empty or malformed, restore the matching key from a trusted backup or generate a new selector and follow a DNS-first DKIM rotation. Never overwrite the old selector and assume cached public DNS updates instantly.

8. Restart narrowly and prove a fresh signature

docker compose restart mailserver
docker compose ps mailserver
docker compose logs --since 5m mailserver | \
  grep -Ei 'rspamd|dkim|permission|cannot load|error'

Do not run docker compose down -v; a DKIM permission fault is not a reason to remove mail or state volumes. Send a completely new authenticated message. At the receiver, inspect the untouched raw source and require:

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

Confirm the local log shows the same test’s successful signing decision and remote delivery. Then recreate the container once during a planned window and repeat the readability test. That final recreation proves the repair lives in persisted configuration instead of the old container layer.

Common wrong turns

Compact acceptance checklist

  1. The active Docker Mailserver image and Rspamd process are identified.
  2. One queue ID produces an explicit key path and access error.
  3. The effective domain, selector, and key path are confirmed without exposing the key.
  4. The persisted host source appears at the expected container destination.
  5. Every parent directory is traversable by the actual Rspamd UID/GID.
  6. The private key is least-privilege readable—not world-readable or broadly writable.
  7. rspamadm configtest passes and a fresh message logs successful signing.
  8. The external raw message contains the expected signature and reports dkim=pass.
  9. DKIM aligns with the visible From domain so DMARC passes.
  10. The repair survives a controlled container recreation.

Authoritative references: Docker Mailserver’s official DKIM, DMARC, and SPF guide documents its supported key-generation and verification workflow; its Rspamd integration guide explains the current security stack. Rspamd’s official DKIM signing module reference describes key paths, selectors, maps, and signing eligibility, while the DKIM signing guide covers private-key ownership and restrictive permissions. Match every command to your pinned versions.