Authenticating Your Mailserver With SPF and DKIM

| ~8 minute read


I recently did a video on setting up an email server, with Postfix for SMTP and Dovecot for IMAP, which you can check out here. Now this much isn't enough since you want to make sure no one can spoof your domain and/or tamper with the content of your E-mails. This is where SPF (Sender Policy Framework) and DKIM (DomainKeys Identified Mail) come to the rescue.

What are SPF and DKIM?

SPF is used to limit which servers are allowed to send emails from your domain. It protects your domain against domain spoofing. Even if you don't use E-mail on your domain, I recommend using SPF to specify that you won't be sending any emails from this particular domain, so no one can spoof your domain and pretend to be you while sending emails.

DKIM can be used to sign the emails with a key pair (where the public key is added to your domain's TXT records) so your E-mails' contents cannot be tampered with. OpenDKIM is an open source implementation of DKIM, we will be setting up OpenDKIM in this tutorial (because I have a natural urge to use anything which has "open" in its name)

Setting up SPF for outgoing emails on your domain

You need to add a TXT record to your domain containing the SPF syntax. the SPF record should always start with the SPF version number you are using. For me it is v=spf1. Then, you can add all the SPF "mechanisms" that need to be evaluated in order to identify the E-mail.

Most of the time you just need one or two mechanisms, which are mx or mx:your-domain.tld and ip4:your_servers_ipv4 and ip6:your_servers_ipv6 (you don't need all of them, it's kinda overkill)

You can prepend any of these with - to reject emails which meet this condition (by default it accepts the ones passing this condition); You can use -all to make sure all the other unauthorised emails don't pass the SPF test.

So this is what a valid SPF record would look like:

v=spf1 mx -all

Now I did say using all of these at ones is pretty overkill, but here is what mine looks like:

v=spf1 mx ip4:172.105.59.60 ip6:2400:8904::f03c:93ff:feac:ca67 mx:m.vidhukant.xyz -all

I don't think using many mechanisms is a bad thing, so you can just copy mine and replace the IP4 and IP6 addresses with your server's IP4 and IP6 addresses. Which is enough for most usecases. To apply this SPF record just add a TXT record to your root domain. It should look like this:

SPF Record Screenshot

NOTE: If you are using multiple domains for a single mail server, you should use the exact same SPF record for all the domains. If you don't plan to use E-mail with any of your domains, set the SPF record to v=spf1 -all. ALWAYS add this to your root domain since it tells every receiving server that this domain doesn't send E-mails and if you receive one that means it's probably malicious.

This website has all of the other info about structuring an SPF record.

Setting up DKIM (OpenDKIM)

Install the necessary packages

apt install opendkim opendkim-tools 

Configure OpenDKIM

The OpenDKIM config file located at /etc/opendkim.conf should look like this:

Syslog	       yes
SyslogSuccess	 yes
Canonicalization	 relaxed/simple
Mode			 sv
SubDomains		 no
OversignHeaders    From
Socket             local:/var/spool/postfix/opendkim/opendkim.sock
PidFile            /var/run/opendkim/opendkim.pid
AutoRestart        yes
AutoRestartRate    10/1M
Background         yes
DNSTimeout         5
SignatureAlgorithm rsa-sha256
UserID		 opendkim
UMask			 002
KeyTable           /etc/opendkim/key.table
SigningTable       refile:/etc/opendkim/signing.table
ExternalIgnoreList /etc/opendkim/trusted.hosts
InternalHosts      /etc/opendkim/trusted.hosts
TrustAnchorFile	 /usr/share/dns/root.key

This is what works for me on Debian 11. The data files for OpenDKIM will be stored in /etc/opendkim/. If you want to store them anywhere else, edit the KeyTable, SigningTable, ExternalIgnoreList, InternalHosts parameters in /etc/opendkim.conf

Setting up the Signing Table

Make a list of all the domains you want to handle E-mails for, and add them to /etc/opendkim/signing.table like this:

*@example.tld mail._domainkey.example.tld

Replace both occurances of example.tld to your domain name, and mail to whichever subdomain sends emails. mail._domainkey.example.tld would act as the DKIM selector for example.tld

If you have any other subdomain set up for the mail server (for example I have m.vidhukant.xyz while the standard would be mail.vidhukant.xyz), replace "mail" with that. You can add as many domains you want, But don't add domains which won't be sending emails.

Setting up the Key Table

Now edit /etc/opendkim/key.table and add lines for each domain like this:

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

Add one line like this for all the domains added to the signing.table file, replace mail and example.tld respectively. Decide a short name for the private key for this domain, /etc/opendkim/keys/example.private in this example holds the private key for example.tld
You should decide different names for each domain you're adding and remember that name, you will need that later.

Setting up the Trusted Hosts

Edit the /etc/opendkim/trusted.hosts file and add these contents:

127.0.0.1
::1
localhost
hostname
hostname.example.tld
*.example.tld

Replace hostname with your server's hostname (defined in /etc/hostname), and example.tld with your domain. If you have more than one domain, you can append those to the file prepended with a *., just like in *.example.tld. No need to add another entry for hostname.example.tld, just append *.yourdomain.tld to the file as many times (for as many domains) you want.

Set file permissions

Make sure opendkim user has access to the required files/directories:

chmod u=rw,go=r /etc/opendkim.conf
chown -R opendkim:opendkim /etc/opendkim
chmod -R go-rwx /etc/opendkim/keys

Generating the keys

Run this command, replacing example.tld with your domain name, and mail with the subdomain for email:

opendkim-genkey -b 2048 -h rsa-sha256 -r -s mail -d example.tld -v

mv /etc/opendkim/mail.private /etc/opendkim/keys/example.private
mv /etc/opendkim/mail.txt /etc/opendkim/keys/example.txt

Replace "example" in the example.private and example.txt files with the short name you entered for the domain in the key.table file.
Repeat this step for all the domains if you have multiple of them!

NOTE: Never share the contents of the generated ".private" files!

Start OpenDKIM

systemctl restart opendkim

Add the TXT Records

Read the contents of the TXT file generated by opendkim-genkey:

cat /etc/opendkim/keys/example.txt

The output would be something like this:

mail._domainkey	IN	TXT	( "v=DKIM1; h=sha256; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzNTZydyFiNljq/Md1cXNEqemKDk9CKhZGHSzEH6x0zxtdcv5ROzaytJ4OsatDOdk+Pygkj6Qq9PiLCc3HlWPTcvMEs+M8YvRergTATFNoAmXLXvpbi+DD0oXAsbz2dM/klObY9OSNlJqFpzmGjgRbtSnvCbot8Smg5LreCjmkuHo/sxyynRHGwRHUM6jokm2YGIGATZBIVqtS"
	  "jM418Gtxx9MZUbwcQTlchk1hSQgbXlAAl5tagle3bq/2GwrwrdaghRH750qLjnBQhzdFnH+GjHTmRl2drQ/2zG1L0GlufipZ1UkWulidox2RtIykv2VxDlBYb77G4PAiiJsSar+wIDAQAB" )  ; ----- DKIM key mail for example.tld

Carefully delete everything outside the parenthesis (including the parenthesis) and join all three lines into one. Now, remove all the quotes and also the whitespaces between line 2 and 3, so both lines get merged into one, without any space between them.

Update (04 May 2024): Use this command to easily turn this output into a valid txt record:

cut -d'"' -f2 example.txt | tr -d '\n'

The result should look like this:

v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxzNTZydyFiNljq/Md1cXNEqemKDk9CKhZGHSzEH6x0zxtdcv5ROzaytJ4OsatDOdk+Pygkj6Qq9PiLCc3HlWPTcvMEs+M8YvRergTATFNoAmXLXvpbi+DD0oXAsbz2dM/klObY9OSNlJqFpzmGjgRbtSnvCbot8Smg5LreCjmkuHo/sxyynRHGwRHUM6jokm2YGIGATZBIVqtSjM418Gtxx9MZUbwcQTlchk1hSQgbXlAAl5tagle3bq/2GwrwrdaghRH750qLjnBQhzdFnH+GjHTmRl2drQ/2zG1L0GlufipZ1UkWulidox2RtIykv2VxDlBYb77G4PAiiJsSar+wIDAQAB

This is your public key for DKIM.

Now go to your DNS editor, click on Add TXT Record, the hostname should be mail._domainkey or whatever the first few characters the original example.txt showed. In the value field copy and paste the public key. Make sure there are no errors.

Configuring postfix to use OpenDKIM

Set up postfix to process outgoing E-mails with OpenDKIM

postconf -e "milter_default_action = accept"
postconf -e "milter_protocol = 6"
postconf -e "smtpd_milters = local:opendkim/opendkim.sock"
postconf -e "non_smtpd_milters = local:opendkim/opendkim.sock"

Add this to your /etc/default/opendkim file to set the socket for postfix

SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"

And then create the socket directory

mkdir /var/spool/postfix/opendkim
chown opendkim:postfix /var/spool/postfix/opendkim

Testing if DKIM is working

Wait for the DNS records to propagate (shouldn't take long!) and send an email with your SMTP server, on the receiving end, your email client should have an option to "view source" or "show email headers", open that, you should see a DKIM-Signature: field there.

You can now send a test email to a GMail account, and on the GMail web app you can view the email's source. It will show detailed information about your DKIM and SPF setup. GMail is by far the best test to verify that your authentication mechanisms (SPF, DKIM, etc) are working properly.

Use DMARC to tell receiving servers how to handle your email

Domain-based Message Authentication, Reporting and Conformance, short for DMARC can be used to specify a set of instructions for the receiving email servers on how to handle the email. You can use it to specify which authentication mechanism (SPF, DKIM or both) is in place, what to do with emails which fail authentication (you can reject, send, or quarantine them), how to check the From: field of the email. It can also be used by receiving servers to report back to your own server, in case any of the checks fail.

All DMARC records start with v=DMARC1, and the p= field would specify what to do with emails that fail the SPF or the DKIM test. p= can have these three values:

It is best to use p=none while testing your SPF and DKIM records, after that you can choose between p=reject or p=quarantine.

You can also optionally use rua=mailto:your_email to get DMARC fail reports to your email. Just replace your_email with your E-mail. You can specify multiple E-mail addresses seperated with a comma.

Here's what an example DMARC record should look like:

v=DMARC1; p=quarantine; rua=mailto:user@example.com

Add this to your domain's TXT records and set the hostname to _dmarc and you should be good to go!

Use this DKIM test to test your SPF, DKIM and DMARC configuration.