Knowledge base

PMTA knowledge base›Block <domain>›Directive§ 3.3.10.26

dkim-headers

<domain>

Which message headers are covered by the signature
in the configuration this is a linedkim-headersinside the block <domain>
What the signature protects and what can be changed with impunity

The signature covers the message body and the listed headers. A header not on the list can be changed along the way, and the signature will stay valid: the recipient will verify it and notice nothing.

The mandatory set cannot be changed. The manual lists about thirty headers that are always signed if present in the message: From, Sender, Subject, To, Cc, Date, Message-ID, Reply-To, MIME-Version, the whole Content-* family, the whole List-* family, the whole Resent-* family. This line adds to the set rather than replacing it.

Seven headers must not be signed under the mail standard: Bcc, Received, Return-Path, DKIM-Signature, Keywords, Comments, Resent-Bcc. They change along the way by the design of the protocol, and a signature over them would always break.

The main trap is signing a header that may be absent from the message. The signature lists names in the h= field, and if a header is named but missing from the message, the receiver considers it removed along the way — and the check fails. Only headers that are always present in your mailing are worth adding to the list.
What the Hub does. It does not write it — the mandatory set is enough. It already covers everything used to tamper with a message: the sender, the subject, the recipient, the content type and the unsubscribe family. Adding makes sense only for your own service headers, if you get any.
Recommended values

Add only permanent headers. A named but missing header breaks the check — it reads as removed.

An expression in slashes catches a family at once. This is how your own headers with a shared prefix are signed without listing each.

An empty string does not mean “sign nothing.” It means “keep the default set”; the mandatory headers cannot be canceled at all.

The “only permanent headers” rule is ours. From the manual here come the full mandatory set, the seven prohibited by the standard, the syntax of names and expressions and the meaning of an empty string.
Example configuration
# we add our own service headers to the mandatory set
<domain *>
    dkim-headers X-Campaign-Id, /^X-Kendali-/</domain>
The first value is an exact name, the second an expression: it covers all your own headers with this prefix without listing them.
yellow — — directive names, blue — blocks. They are highlighted the same way in the text of the card.
▲

Common mistakes and problems

what people get wrong and how it ends2 mistakes▶

Signing a header that is not in every message

our experience

What it costsThe receiver sees its name in the h= field, does not find it in the message and considers it removed along the way. The check fails — for some of the messages, and which ones cannot be determined from the configuration.
How to spot itSignature check failures for part of the mailing with a correct key.
How to do it rightKeep only headers that are always present on the list.

Trying to narrow the set

§ 3.3.10.26

What it costsThe mandatory headers are signed regardless of the value, and an empty string means “leave it as is.” The set cannot be narrowed with this line with any value.
How to spot itA value is set, but the h= field in the signature is as long as before.
How to do it rightNothing: this is how the engine works. The line only adds.
Compatibility
PowerMTA 6.0
described in the manual of this version · platform: any
The vendor does not state the first version for every directive — there is no note here. The name also appears in the 5.0 edition of the manual.
Reference data
Syntax
dkim-headers <header-list>
Values
comma-separated header names and/or expressions in slashes; an empty string means “keep the default set”
Applying
reload, no restart needed
Queue
will not touch what is already queued
Where to write it
<domain>
How many times
once in the block
Everything here comes from the manual, with the section.
Inheritance
The value merges across <domain> blocks from general to specific: the best-matching block wins, not the last one in the file. The mandatory set is signed regardless of the value — the line adds to it. It is picked up by pmta reload; for queued messages it takes effect from the next attempt: the signature is applied at sending.
How to check on the node
The effective value is printed by the queue settings command; what is actually signed is listed in the h= field of the signature itself:
pmta show settings gmail.com/vmta1 | grep -i dkim-headers
grep -i 'DKIM-Signature' /var/log/pmta/pmta.log | tail -3
In the signature header the names of the signed headers are in the h= field, separated by colons — that is the definitive answer. The Hub does not write this line.
In the Hub
Cost of a mistake
reversible: harms only while it is set
Scenarios
Message signing and authentication