PhoenixDKIM Documentation

Man pages | Guides | Configuration examples

Man pages

Guides

In-depth how-tos. They complement the man pages and the examples below.

Configuration examples

These examples cover common setups from the simplest to the more involved. They are not a substitute for the phoenixdkim.conf(5) man page, which documents every option.

1. Verify only | 2. Verify and sign | 3. Dual signing: Ed25519 and RSA | 4. Hardening | 5. Vault-backed keys and zero-downtime rotation

1. Verify only

The minimum configuration to get started: check DKIM signatures on incoming mail without signing anything. Useful while you generate and publish your key, or if you simply do not send mail through this filter.

Syslog                  yes
Socket                  local:/run/phoenixdkim/phoenixdkim.sock
UserID                  phoenixdkim:phoenixdkim

Mode                    v

2. Verify and sign

Generate an RSA signing key with phoenixdkim-genkey(8):

phoenixdkim-genkey -b 2048 -d example.com -s mail

This creates two files: mail.private (the private key) and mail.txt (the DNS TXT record to publish). Copy mail.private to /etc/phoenixdkim/keys/ and publish the record from mail.txt as mail._domainkey.example.com in your domain's DNS.

Syslog                  yes
Socket                  local:/run/phoenixdkim/phoenixdkim.sock
UserID                  phoenixdkim:phoenixdkim

Mode                    sv
Canonicalization        relaxed/relaxed
Domain                  example.com
Selector                mail
KeyFile                 /etc/phoenixdkim/keys/mail.private

Mode sv enables both signing and verification. If you sign for more than one domain, use KeyTable and SigningTable instead of Domain, Selector, and KeyFile; see the phoenixdkim.conf(5) man page.

3. Dual signing: Ed25519 and RSA

Ed25519 is a modern elliptic-curve signature algorithm standardised for DKIM in RFC 8463. Compared to RSA, Ed25519 signatures are smaller, faster to generate, and harder to attack. The downside is that some older or poorly maintained mail infrastructure does not yet support verifying them, and a few milters are known to misbehave — or crash — when they encounter an Ed25519 signature.

One way to handle this is to sign each message with both an Ed25519 key and an RSA key. A verifier that supports Ed25519 uses that signature; one that does not falls back to the RSA signature.

Do consider, though, that until Ed25519 signatures are supported by all major mail services, they are not more effecient because you have to dual-sign with an RSA key for backup. It is also possible that not all mail servers will handle Ed25519 gracefully, even if you have an RSA backup signature.

If you do want to proceed, generate both keys:

phoenixdkim-genkey -b 2048 -d example.com -s mail-rsa
phoenixdkim-genkey -t ed25519 -d example.com -s mail-ed25519

Copy both .private files to /etc/phoenixdkim/keys/example.com/ and publish both DNS records. It is good practice to keep each domain's keys in its own subdirectory under keys/.

The format of KeyTable and SigningTable is described in the phoenixdkim.conf(5) man page.

Create /etc/phoenixdkim/keytable:

mail-rsa      example.com:mail-rsa:/etc/phoenixdkim/keys/example.com/mail-rsa.private
mail-ed25519  example.com:mail-ed25519:/etc/phoenixdkim/keys/example.com/mail-ed25519.private

Create /etc/phoenixdkim/signingtable:

*@example.com  mail-rsa
*@example.com  mail-ed25519
Mode                    sv
Canonicalization        relaxed/relaxed
KeyTable                /etc/phoenixdkim/keytable
SigningTable            refile:/etc/phoenixdkim/signingtable
MultipleSignatures      yes

The refile: prefix on SigningTable enables regular expression matching. With MultipleSignatures yes, all matching entries are applied, so each message receives both signatures.

4. Hardening

By default, PhoenixDKIM signs only the minimum set of headers required by the DKIM specification. Extending this — and oversigning certain headers — significantly raises the bar for forgery and header injection.

SignHeaders extends signing beyond the minimum. Any listed header that is present in a message will be covered by the signature; if it is later altered in transit, the signature will fail. The following covers the headers that matter most for a typical mail sender:

SignHeaders             From,Sender,Reply-To,Subject,Date,Message-Id,To,Cc,Mime-Version,Content-Type,Content-Transfer-Encoding,In-Reply-To,References,List-Id,List-Help,List-Owner,List-Unsubscribe,List-Unsubscribe-Post,List-Subscribe,List-Post,Resent-To,Resent-Cc,Resent-From,Resent-Sender,Resent-Message-Id,Openpgp,Autocrypt

OversignHeaders protects against header injection. When a header is oversigned, the signature includes a commitment to the absence of an extra instance of that header. If an attacker later prepends a new header of the same name — a classic technique for spoofing the apparent From — the signature is immediately invalidated:

OversignHeaders         From,Reply-To,Subject,To,Cc,Date,Message-Id,Mime-Version,Content-Type,Content-Transfer-Encoding,In-Reply-To,References,Sender,Openpgp,Autocrypt

Note that OversignHeaders does not need to mirror every entry in SignHeaders. Headers from the List-* and Resent-* families may legitimately appear multiple times (on mailing lists, for example), so oversigning them can break valid traffic. Focus oversigning on the headers that identify and address the message.

5. Vault-backed keys and zero-downtime rotation

PhoenixDKIM can read signing keys at runtime from HashiCorp Vault instead of from files on disk, using the vault: data set backend. Point KeyTable at a Vault KV v1 or KV v2 path and the filter fetches the key over HTTPS for each lookup:

KeyTable      vault://vault.example.com:8200/v1/secret/data/dkim/{d}
SigningTable  refile:/etc/phoenixdkim/signingtable
VaultToken    hvs.CAES...your-token...

The {d} token is replaced with the signing domain, so the secret at secret/data/dkim/example.com supplies the key for example.com. (VaultToken may instead be supplied through the VAULT_TOKEN environment variable.) In its simplest form the secret holds a single private_key field and produces one signature, exactly like a file-based key.

The interesting part is rotation. Replacing a DKIM key is normally nerve-wracking: you publish the new public key in DNS, but you cannot know when every resolver and forwarder worldwide has seen it. Sign with the new key too early and mail signed against the not-yet-propagated record fails verification. The robust answer is to sign with the old and the new key at the same time for the duration of a grace window, so a verifier passes against whichever record it currently holds. PhoenixDKIM does this directly from Vault: a secret may carry a selectors array, and the filter emits one signature for every selector whose validity window contains the current time.

Write a secret like this (KV v2 shown) at secret/data/dkim/example.com:

{
  "selectors": [
    { "selector": "2025a", "key": "-----BEGIN PRIVATE KEY-----\n...old RSA...\n-----END PRIVATE KEY-----",
      "valid_end": 1739000000 },
    { "selector": "2025b", "key": "-----BEGIN PRIVATE KEY-----\n...new RSA...\n-----END PRIVATE KEY-----",
      "valid_start": 1736000000 }
  ]
}

During the overlap — after 2025b's valid_start but before 2025a's valid_end — every message is signed with both selectors. Once the window passes, 2025a drops out automatically and only 2025b remains. No filter restart, no config edit: you change the timestamps in Vault and the signing follows. Each window bound is a Unix timestamp; valid_start is inclusive and valid_end is exclusive, and an absent bound simply means “unbounded”.

The same mechanism gives you algorithm diversity for free: list an RSA selector and an Ed25519 selector as both valid, and each message gets an RSA signature and an Ed25519 signature. The algorithm is taken from the key material itself, so you never declare it twice. Combining the two ideas, a secret can rotate RSA and Ed25519 keys at once, emitting four signatures (old and new of each) across the grace window — the safest possible cutover.

A selector that is malformed or outside its window is skipped and logged, never fatal, so one bad entry cannot stop a domain's mail. The array field name (VaultSelectorsField, default selectors), the single-key field name (VaultField), and the token (VaultToken) are all documented in phoenixdkim.conf(5). The secret schema matches rspamd's Vault layout, so the same secret can be managed with rspamd's tooling and signed by PhoenixDKIM.

The vault: backend is a thin convenience wrapper over the more general http:/https: backend. If your keys live in SQL, LDAP, or any other store, point KeyTable at a small HTTP bridge of your own (in any language) that returns the key for a given domain and selector; PhoenixDKIM treats it identically. All of this requires a build with libcurl (-DWITH_CURL=ON).