Fixing DKIM Signing Issues with OpenDKIM and Postfix

Fixing DKIM Signing Issues with OpenDKIM and Postfix

If your mail server sends messages but external tools report "Your message is not signed with DKIM", the issue is usually related to a missing configuration in OpenDKIM. A common situation is when the trusted.hosts file does not exist or when OpenDKIM does not know which domains and keys it should use for signing outgoing mail.

Below is a structured guide to correctly configure OpenDKIM together with Postfix and ensure that your outgoing email is properly signed.


1. Create the trusted.hosts File

This file defines which hosts are allowed to send mail that OpenDKIM should sign.

sudo nano /etc/opendkim/trusted.hosts

Add the following content:

127.0.0.1
localhost
::1
vindazo.nl
*.vindazo.nl

Save and exit the file.


2. Complete the OpenDKIM Configuration

Open the main configuration file:

sudo nano /etc/opendkim.conf

Ensure the following parameters are present:

Syslog                  yes
LogWhy                  yes

Canonicalization        relaxed/simple
Mode                    sv
SubDomains              no
Socket                  inet:8891@localhost

KeyTable                /etc/opendkim/key.table
SigningTable            refile:/etc/opendkim/signing.table
InternalHosts           /etc/opendkim/trusted.hosts
ExternalIgnoreList      /etc/opendkim/trusted.hosts

These settings tell OpenDKIM:

  • Where to find the DKIM keys
  • Which domains should be signed
  • Which hosts are trusted to send mail
  • How Postfix communicates with OpenDKIM

3. Verify the Default Socket Configuration

Ubuntu may override the socket configuration through the default settings file.

sudo nano /etc/default/opendkim

Make sure this line is active:

SOCKET="inet:8891@localhost"

Other socket definitions should remain commented out.


4. Verify DKIM Private Key Permissions

OpenDKIM must be able to read the private key used for signing.

ls -l /etc/opendkim/keys/vindazo.nl/mail.private

If necessary, correct the permissions:

sudo chown opendkim:opendkim /etc/opendkim/keys/vindazo.nl/mail.private
sudo chmod 600 /etc/opendkim/keys/vindazo.nl/mail.private

5. Restart the Services

After updating the configuration, restart both services.

sudo systemctl restart opendkim
sudo systemctl restart postfix

Then confirm OpenDKIM is listening on port 8891:

systemctl status opendkim
ss -lntp | grep 8891

6. Check Logs While Sending a Test Email

Open a live log viewer:

tail -f /var/log/mail.log | grep -i opendkim

Send a test email. If everything works correctly, you should see messages indicating that a DKIM signature has been added.


7. Example Working Configuration

/etc/opendkim/key.table

mail._domainkey.vindazo.nl vindazo.nl:mail:/etc/opendkim/keys/vindazo.nl/mail.private

/etc/opendkim/signing.table

*@vindazo.nl mail._domainkey.vindazo.nl

/etc/opendkim/trusted.hosts

127.0.0.1
localhost
::1
vindazo.nl
*.vindazo.nl

/etc/postfix/main.cf

smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891
milter_default_action = accept
milter_protocol = 2

/etc/opendkim.conf

Syslog                  yes
LogWhy                  yes
Canonicalization        relaxed/simple
Mode                    sv
SubDomains              no
Socket                  inet:8891@localhost
KeyTable                /etc/opendkim/key.table
SigningTable            refile:/etc/opendkim/signing.table
InternalHosts           /etc/opendkim/trusted.hosts
ExternalIgnoreList      /etc/opendkim/trusted.hosts

8. Testing DKIM Externally

Send a message to testing services such as:

test@mail-tester.com

or

check-auth@verifier.port25.com

If the configuration is correct, the message headers will contain a DKIM-Signature and the report will confirm:

DKIM = PASS

Additional Note

If your mail logs show errors such as:

smtp_connect_addr: bind 148.251.XX.XX: Cannot assign requested address

This indicates a separate network configuration issue related to outbound IP binding in Postfix. While it does not directly affect DKIM signing, it should still be corrected to ensure stable mail delivery.

Fixing a DKIM Signature Verification Failure Caused by a Key Mismatch

While configuring DKIM signing with Postfix and OpenDKIM, the mail server was successfully adding a DKIM signature to outgoing messages, but external validation tools such as Mail-Tester and Gmail were returning:

DKIM_INVALID
signature verification failed

At first glance, everything appeared to be configured correctly:

  • OpenDKIM was running
  • Postfix was connected to the DKIM milter
  • DNS contained a DKIM TXT record
  • The server was adding a DKIM-Signature header

However, the cryptographic verification still failed.


Root Cause

The actual issue turned out to be a mismatch between the private DKIM key used by the server and the public key stored in DNS.

In other words:

  • The mail server was signing emails using one private key
  • The receiving mail servers were verifying the signature using a different public key from DNS

Since DKIM relies on asymmetric cryptography, both keys must belong to the same key pair. If they do not match exactly, verification will always fail.


Step 1 — Extract the Public Key From the Private DKIM Key

The first step was to derive the public key directly from the private DKIM key used by OpenDKIM:

openssl rsa -in /etc/opendkim/keys/vindazo.nl/mail.private -pubout -outform PEM > /tmp/mail.pub.pem

Then convert the key to DER format and generate a fingerprint:

openssl pkey -pubin -in /tmp/mail.pub.pem -pubout -outform DER | sha256sum

Step 2 — Extract the Public Key From DNS

Next, retrieve the DKIM TXT record from DNS and convert it into a usable public key:

dig +short TXT mail._domainkey.vindazo.nl | tr -d '"' | tr -d '\n' | sed 's/.*p=//' > /tmp/dkim_dns_key.b64

Convert it to a PEM file:

python3 - <<'PY'
from pathlib import Path
import textwrap
b64 = Path('/tmp/dkim_dns_key.b64').read_text().strip()
pem = "-----BEGIN PUBLIC KEY-----\n" + "\n".join(textwrap.wrap(b64, 64)) + "\n-----END PUBLIC KEY-----\n"
Path('/tmp/dkim-dns.pub.pem').write_text(pem)
PY

Then generate the fingerprint of the DNS key:

openssl pkey -pubin -in /tmp/dkim-dns.pub.pem -pubout -outform DER | sha256sum

Step 3 — Compare Both Keys

If the fingerprints differ, the keys do not belong to the same pair. This was exactly the issue in this case.

Once the correct public key was generated from the private key, the DNS DKIM record was replaced with the correct value.


Correct DKIM DNS Record

mail._domainkey.vindazo.nl TXT
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvU7VwDD22TW4xAT9r5cwkQNBAieW3ucRw2SMkH7bOPsjQFC2kynjTNAeJyEa0xev9Ub7tmGZRbTTAtSBrzy+kiTZKyN51OPo3eWiYRxbmnBfw...

Final Verification

After updating the DNS record and waiting for propagation, DKIM verification was successful.

Testing again with Mail-Tester produced:

DKIM = PASS

This confirmed that the private signing key and DNS public key were finally aligned.


Key Takeaways

  • Always ensure the DKIM private key on the server matches the public key in DNS
  • If DKIM signatures appear but verification fails, check for a key mismatch first
  • Comparing fingerprints of the derived public key and DNS key is the fastest diagnostic method
  • DNS TXT records may appear split across multiple quoted strings, which is normal

Once the keys match, DKIM signing becomes stable and mail authentication improves significantly.

Configuring Additional IP Addresses on a Hetzner Dedicated Server

After a server rebuild or disk replacement, the network configuration may be reset to a minimal setup that only contains the primary IP address. In this case, additional IPv4 addresses configured in the Hetzner Robot panel will not be active on the server itself, which prevents services like Postfix from binding to those IPs.

This guide describes how the issue was diagnosed and fixed by updating the correct network configuration file.


Identifying the Network Configuration Method

The first step was to determine how networking was managed on the system. Running the following command revealed that the server uses systemd-networkd through Netplan.

networkctl status

The output showed that the interface was configured using a Netplan-generated file:

Configuring with /run/systemd/network/10-netplan-enp4s0.network

This indicated that the actual configuration is defined in the Netplan directory:

/etc/netplan/

Locating the Netplan Configuration File

Listing the directory revealed the active configuration file:

ls /etc/netplan
01-netcfg.yaml

Opening the file showed the original configuration which only contained the primary IP address:

network:
  version: 2
  renderer: networkd
  ethernets:
    enp4s0:
      addresses:
        - 148.251.89.72/32
        - 2a01:4f8:202:6447::2/64
      routes:
        - on-link: true
          to: 0.0.0.0/0
          via: 148.251.89.65
        - to: default
          via: fe80::1

Adding Additional IPv4 Addresses

Hetzner uses routed additional IP addresses with a /32 subnet mask. This means extra addresses can simply be added to the same interface.

The configuration was updated to include all assigned IPv4 addresses:

network:
  version: 2
  renderer: networkd
  ethernets:
    enp4s0:
      addresses:
        - 148.251.89.72/32
        - 148.251.89.86/32
        - 148.251.89.87/32
        - 2a01:4f8:202:6447::2/64
      routes:
        - on-link: true
          to: 0.0.0.0/0
          via: 148.251.89.65
        - to: default
          via: fe80::1
      nameservers:
        addresses:
          - 185.12.64.2
          - 2a01:4ff:ff00::add:1
          - 185.12.64.1
          - 2a01:4ff:ff00::add:2

Applying the Configuration

After editing the Netplan file, the configuration was applied with:

netplan apply

The new addresses became active immediately.


Verification

Finally, the interface was inspected to confirm that all IP addresses were correctly attached:

ip addr show enp4s0

The output confirmed that all three IPv4 addresses were now active:

148.251.89.72/32
148.251.89.86/32
148.251.89.87/32

With this configuration in place, services such as Postfix can now bind to different IP addresses for outbound connections.


Conclusion

The issue was caused by a missing network configuration after the server rebuild. By locating the correct Netplan configuration file and adding the additional IP addresses with a /32 mask, the server was restored to its intended multi-IP setup.

This configuration allows applications to bind specific services to different IP addresses, which is particularly useful for mail servers and other network services that require multiple outbound identities.

Comments