Top.Mail.Ru

DKIM and DMARC on a VPS: How to Set Them Up So Your Emails Don’t End Up in Spam

2
DKIM and DMARC on a VPS: How to Set Them Up So Your Emails Don’t End Up in Spam

Why you need DKIM and DMARC if SPF is already set up

SPF answers one question: which IP addresses are allowed to send mail on behalf of a domain. That is only part of the protection. A receiving server can check SPF and accept a message, but it has no way to confirm that the content was not altered on the way, and no way to be told what to do when the checks fail. DKIM and DMARC cover exactly that.

DKIM signs a message with a digital signature tied to the sender’s domain and the message body. If the message is modified in transit (for example, an intermediate spam filter appends an advertising line), the signature no longer matches and the recipient can see it. DMARC goes one step further: it is a policy that tells the receiving server what to do with a message when SPF and DKIM do not line up: deliver it, send it to spam, or reject it outright. Without a DMARC record every mail service decides on its own, using internal algorithms, and the result is hard to predict.

This is not a theoretical recommendation but a rule that is already in force. Gmail and Yahoo officially require SPF and DKIM for delivery and strongly recommend DMARC (details below). If you run a mail server on a VPS and send to Gmail, Yahoo, Outlook or any other large provider, some of your messages will either land in spam or be rejected during the SMTP session when DKIM and DMARC are missing.

If SPF is not configured yet

Start with SPF: DKIM and DMARC work on top of it, not instead of it. SPF is a TXT record on the root of your domain. A minimal example for a server that sends mail from its own IP address and is also your MX host:

example.com.  IN  TXT  "v=spf1 mx ip4:203.0.113.10 -all"

Here mx allows the servers listed in your MX records, ip4: allows a specific address, and -all tells receivers to treat everything else as unauthorized. The rest of this article assumes SPF is already published and working.

Installing DKIM on a VPS

We will use Ubuntu 24.04 with Postfix as the mail server. The opendkim package signs outgoing messages on the fly, before Postfix delivers them further.

Install the packages:

apt-get install -y opendkim opendkim-tools

Create a directory for the domain’s keys and move into it:

mkdir -p /etc/opendkim/keys/example.com
cd /etc/opendkim/keys/example.com

Generate a 2048-bit key pair:

opendkim-genkey -b 2048 -d example.com -s mail -v

The -s mail flag sets the selector: a short arbitrary word that becomes part of the DNS record name. You can use “mail”, “default” or the key’s creation date (for example “202609”). The value itself does not matter, but the same word must later appear in the opendkim configuration. The command prints:

opendkim-genkey: generating private key
opendkim-genkey: private key written to mail.private
opendkim-genkey: extracting public key
opendkim-genkey: DNS TXT record written to mail.txt

You now have two files. mail.private is the private key. It must belong to the opendkim user and have mode 600 so nobody except the daemon can read it. opendkim-genkey creates the file with mode 600, but the owner will be whoever ran the command (usually root), so chown is still required. We set the permissions explicitly as well:

chown opendkim:opendkim mail.private
chmod 600 mail.private

mail.txt contains the ready-made TXT record you need to add to your DNS zone.

The DKIM DNS record: why the key is split into several strings

When you open mail.txt you will see something like this (the key is shortened for the example; yours will be longer):

mail._domainkey	IN	TXT	( "v=DKIM1; h=sha256; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwT8fVn28RxUu"
	  "Kq3ZbLxV9mYpQe7Dc4Ht1Nw0oKzXjFvS2bGcRr5eYxL8CnQaU7WdMhPz3T"
	  "kV9BvGxLqYcRJ8mNtDx2FhZkP4uWnCbEaXVsY6rTgQ1iSzKoM3JdHwLmIQIDAQAB" )  ; ----- DKIM key mail for example.com

The split into several quoted pieces is neither a typo nor a generator bug. A single character-string inside a TXT record is limited to 255 bytes (RFC 1035), and the base64 form of a 2048-bit RSA public key is longer than that. opendkim-genkey therefore cuts the key into segments, and a DNS server joins them back into one value when the record is read.

How you add the record depends on where you add it. If you edit a BIND zone file directly, copy the block from mail.txt exactly as it is, with quotes and line breaks. If you use a registrar’s or DNS provider’s control panel, there is usually a single text field for the TXT value. Paste only the joined text between the quotes, without the record name, IN TXT, or the quote characters themselves. In other words, literally v=DKIM1; h=sha256; k=rsa; p=<the base64 key joined without spaces> on one line. Pasting the file as is is a common reason for a record to be published broken (see the next section).

The record name is <selector>._domainkey.<domain> and the type is TXT. This is defined in RFC 6376 (sections 3.6.1 to 3.6.2.1). For the example above it is mail._domainkey.example.com.

Connecting DKIM to Postfix

The opendkim settings live in /etc/opendkim.conf. The package installs it with working defaults (including PidFile and UserID, which decide whether the daemon starts at all), so do not replace the file. Append these lines to the end instead:

Domain			example.com
KeyFile			/etc/opendkim/keys/example.com/mail.private
Selector		mail
Mode			sv
KeyTable		/etc/opendkim/KeyTable
SigningTable		/etc/opendkim/SigningTable
ExternalIgnoreList	/etc/opendkim/TrustedHosts
InternalHosts		/etc/opendkim/TrustedHosts
Socket			inet:8891@localhost

The Domain, KeyFile and Selector lines are redundant when KeyTable and SigningTable are present. They are kept here for clarity and do no harm, but rely on the KeyTable and SigningTable files.

KeyTable links a selector to the private key file:

mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private

SigningTable says which messages are signed with that key:

*@example.com mail._domainkey.example.com

TrustedHosts lists hosts opendkim trusts without further checks (the local machine is usually enough):

127.0.0.1
localhost

Next, tell Postfix to pass mail through opendkim as a milter, an external filter that signs the message before it is sent. Apply the settings with postconf:

postconf -e "milter_default_action = accept"
postconf -e "milter_protocol = 6"
postconf -e "smtpd_milters = inet:localhost:8891"
postconf -e "non_smtpd_milters = inet:localhost:8891"

Restart both services:

systemctl restart opendkim
systemctl restart postfix

To check that opendkim is listening, run ss -tlnp: the output should contain a line with 127.0.0.1:8891 in the LISTEN state. postfix check should report no errors.

Checking DKIM

After the TXT record is published, check the key with:

opendkim-testkey -d example.com -s mail -k mail.private -vvv

This command makes a real DNS query for mail._domainkey.example.com and compares what is published with the local private key. It works only after the record has actually appeared in DNS and propagated to resolvers, which can take anywhere from a few minutes to an hour depending on the zone’s TTL and how quickly your DNS provider picks the change up.

Two different messages are easy to confuse:

  • “record not found” means the DNS record is not visible to your server yet: either it has not propagated (usually minutes to an hour, depending on TTL) or it was not added at all. This is expected right after publishing. Wait and check again.
  • “Revoked key” means the record has already been found in DNS, but its p= value is empty. Under RFC 6376 an empty p= formally means “key revoked”. Waiting will not help here: the record is published but broken, and you need to re-check and re-upload its contents (a common cause is a DNS panel that accepted only the first quoted segment of a long record and dropped the rest). Large services publish empty keys deliberately, too: several old Gmail DKIM selectors, such as 20161025._domainkey.gmail.com, currently have an empty p=, which is how Google retires rotated keys.

There is also a “key not secure” message. It only means the record has no DNSSEC confirmation and it appears even when the key check succeeds, so it is not a reason to worry on its own.

You can also check the DNS record itself without relying on the private key on the server:

dig TXT mail._domainkey.example.com

The answer should contain a string like v=DKIM1; k=rsa; p=... with a non-empty p= value.

DMARC: syntax and policies

A DMARC record is published as a TXT record on the _dmarc subdomain. A minimal working version:

v=DMARC1; p=none; rua=mailto:postmaster@example.com

p= is the policy that tells the receiving server what to do with a message that fails SPF and DKIM. Strictly speaking it applies when domain alignment fails, but for a basic setup it is enough to read it as “SPF and DKIM did not match”.

Value of p= What happens to the message
none No explicit requirement for the receiver, reports only. The message will most likely be delivered as usual, although the recipient’s own spam filters can still trigger independently of DMARC.
quarantine The receiving server should treat the message as suspicious where possible, which usually means the spam folder.
reject The receiving server should reject the message during the SMTP session, before it reaches the mailbox. This is a protocol recommendation (RFC 7489 and RFC 9989 use SHOULD), not a hard guarantee for 100% of receivers.

rua= is the address (or several, comma-separated) that receives aggregate reports in XML: who sent mail on behalf of your domain, from which IP addresses, and whether it passed SPF and DKIM. ruf= is an optional address for forensic reports about individual failed messages. Not every mail provider supports these, so not every receiver sends them.

How to roll out DMARC: not straight to reject

Setting p=reject right after DKIM is a bad idea, even if everything seems to work. If SPF or DKIM is not perfect (for example, mail also goes out through a third-party newsletter service you forgot to include in SPF), your domain will start blocking its own legitimate mail, and you will find out not immediately but when customers complain they are not receiving anything.

The accepted industry practice looks like this:

  1. Set up and test SPF and DKIM separately.
  2. Publish DMARC with p=none and a rua address. This changes nothing about delivery but starts report collection.
  3. For a few weeks, analyze the reports: who really sends mail for the domain, whether all sources are legitimate, and whether they pass SPF and DKIM.
  4. If the picture is clean, tighten the policy gradually: first quarantine, then, once you are fully confident, reject.

Reports arrive as XML files that are hard to read in volume, which is why DMARC analytics services exist. Large companies use them as well. Look at Yahoo’s DMARC record and you will see that its reports go to a third-party service rather than to its own domain:

dig TXT _dmarc.yahoo.com
"v=DMARC1; p=reject; pct=100; rua=mailto:d@rua.agari.com; ruf=mailto:d@ruf.agari.com;"

For comparison, Google’s record:

dig TXT _dmarc.google.com
"v=DMARC1; p=reject; rua=mailto:mailauth-reports@google.com"

Both approaches work. In rua you can point to your own mailbox or to a specialized service that parses the reports and shows them in a readable form.

Current Gmail and Yahoo requirements for senders

These are not abstract rules. Google and Yahoo tightened their email authentication requirements in February 2024, and since November 2025 Google has warned that traffic that does not comply receives temporary and permanent delivery failures. Today this is reality, not an announcement.

Requirement Details
“Bulk sender” threshold About 5,000+ messages to personal Gmail accounts within 24 hours
SPF or DKIM for all senders At least one is mandatory regardless of sending volume
SPF and DKIM for bulk senders Both are required, and the domain in the From: header must align with at least one of them
DMARC for bulk senders Required, at least with policy p=none
TLS connection Gmail requires mail to be transmitted over TLS, which is the ordinary STARTTLS that Postfix enables out of the box
Valid PTR record The sending server’s IP must have a reverse (PTR) record that resolves to a hostname. For a VPS this is a separate question for the provider, especially if the provider assigns a generic PTR such as “1-2-3-4.example-provider.net”
Spam rate Keep it below 0.1% (recommendation). At 0.3% and above, deliverability suffers noticeably. Yahoo states this threshold directly as a requirement
One-click unsubscribe Mandatory since June 1, 2024 for marketing messages: List-Unsubscribe and List-Unsubscribe-Post headers, and unsubscribes must be processed within 48 hours

The last item mostly concerns newsletters and marketing mail rather than ordinary transactional or personal correspondence. For setting up a mail server on a VPS, SPF, DKIM and DMARC are enough in most cases. But if bulk mailings also go out from your VPS, messages without one-click unsubscribe will lose deliverability too.

How to check everything at once

Once DKIM and DMARC are configured and the records have propagated, check the result in several ways.

With dig, make sure the records are published and readable:

dig TXT mail._domainkey.example.com
dig TXT _dmarc.example.com

With public online tools, if you want to see the result from the receiver’s side and not only formally in DNS:

  • mail-tester.com generates a one-time address. Send a test message to it from your server, and the site shows a score and a detailed breakdown of how SPF, DKIM and DMARC passed.
  • mxtoolbox.com offers separate checks for a domain or IP: SPF Record Check, DKIM Check, DMARC Check, and a Blacklist Check.

Both tools are free for one-off checks and need no setup: enter a domain or send a message.

Conclusion

SPF, DKIM and DMARC solve different problems and only work together. SPF says who may send mail for the domain, DKIM confirms the message was not changed in transit, and DMARC tells the receiving server what to do if the first two fail. On a VPS the whole DKIM setup comes down to generating a key, publishing a TXT record in DNS, and connecting opendkim to Postfix as a milter. DMARC is a separate record and is rolled out gradually, from monitoring (p=none) to strict rejection (p=reject), never the other way round. Since both Gmail and Yahoo have required this combination since 2024, there is no reason to put the setup off: without it, part of your domain’s mail will either end up in spam or never arrive.

Related

All articles