Email remains a cornerstone of communication, ensuring the authenticity and integrity of messages is paramount. To combat the ever-present threats of spoofing and phishing, three technologies are a foundation for protecting email channels: Sender Policy Framework (SPF), DomainKeys Identified Mail (DKIM), and Domain-based Message Authentication, Reporting & Conformance (DMARC). Together these protocols empower domain owners to define legitimate sending sources, cryptographically sign their emails, and establish policies for handling unauthenticated messages, collectively building a more trustworthy and secure email ecosystem for senders and recipients.
I run my mail services on a small Fedora Linux server running Postfix, which is integrated with a few open source packages and a few custom tools.. It has served me very well for the last few years. I find that I have a bit more control over my email and how I manage my communications. It does require period attention and maintenance. I recently completed a small project to make sure that I had SPF, DKIM, and DMARC setup for each of my personal domains. This allows me to verify incoming email and also gives recievers the ability to authenticate email from my domains, which protects the reputation of my domains. The process is relatively simple for my platform.
The first major step in setting up SPF is to create the SPF records in DNS. These are simple TXT records that identify all of sources where email from your domains originate. You will need to know all of the IP addresses and/or networks where your email originates.This is a bit more challenging if you use cloud services to send email on your behalf. Your service provider should be able to assist with identify their IP addresses. SPF records look very different depending on the sender sources so I won't go into all of the details. The SPF wiki page explains the syntax fairly well. All my email is generated from my single server so my SPF record looks like: v=spf1 mx a ip4:a.b.c.d/e -all
where a.b.c.d/e
is the IP address of my server and the -all
instructs the receiver to fail the SPF check for all email not from this IP address. There are less severe options for unmatched senders, but I am confident in my email sources. You might want to consider a less restrictive option while you debug your senders list.
The procedure for publishing the TXT record will vary depending on your hosting and DNS provider. For example, in my SPF TXT record for this domain the hostname is peterwoods.online
and the value is v=spf1 mx a ip4:173.255.253.43/32 -all
.
After creating SPF records for each of my domains, I installed the pypolicyd-spf
package and configured Postfix to require the SPF checks for incoming email. This requires small changes to the master.cf
and main.cf
files. Please note that the file paths in this example are specific to where the package installs the milter.
Add the following at the end of /etc/postfix/master.cf.
policyd-spf unix - n n - 0 spawn
user=pyspf-milter argv=/usr/libexec/postfix/policyd-spf
Add the following at the end of /etc/postfix/main.cf at the end smtp_recipient_restrictions seconf. Be sure to add the comma to the end of the previous line.
check_policy_service unix:private/policyd-spf
Restart Postfix on your server. Monitor your email logs and check SMTP headers in individual messages to make sure that everything is working. You might also want to do some outbound tests with the major cloud email providers to make sure your email is going through.
Setting up DKIM is a bit more complex. The process involves installing packages, identifying servers, generating signing keys, and publishing them in a DNS TXT record. I installed the opendkim
and opendkim-tools
packages first. All of the major configuration files are located under /etc/opendkim
.
The next major step is to create key pairs for each domain and subdomain that sends email. I do not use subdomains for email so I can create just one key pair per domain and use the default selector. The general form of my command is:
opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v
where example.com
is the domain that I am generating the key for and default
is the selector. Note that the domain name is in the directory path to key location so you will need to create a directory for each domain and subdomain. I decided to 2048 for my key size.
The opendkim-genkey command will generate two files called default.txt
and default.private
. The private file is just that. Do not share or publish it anywhere. The txt file contains the information that you want to publish in your DNS record, but you will need to modify the format slightly. If you look at the file, you will notice that the key is split across multiple lines. The value for your TXT record will start with the v=DKIM1
and end with the string that ends right before the last parenthesis in the last line. Be sure to remove any quotation marks and spaces when joining the lines for your TXT record. Do not modify the original file. Join them in a scratch file if you need to. For example, in the DMARC TXT record for this domain, the hostname contains default._domainkey
and the value of v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ_truncated_for_readability_yPfCRMrLpNnnT+LzWXXtbENwIDAQAB
.
The next step is to update the KeyTable
, SigningTable
and TrustedHosts
files. On a good note, each file has a good example line to follow.
In KeyTable, add a line similar to:
default._domainkey.example.com example.com:default:/etc/opendkim/keys/default.private
In SigningTable, add a line similar to:
*@example.com default._domainkey.example.com
Add your sending IP address and hostnames to TrustedHosts.
host.example.com
192.168.1.0/24
Edit /etc/opendkim.conf
and change the value for the Mode
field to sv
. This enables signing and verification.
Mode sv
Optional I also prefer to use a TCP connection instead of a socket. In the same configuration file, find the Socket line and change to the following. You might need to tinker with file permissions if you stay with the file system socket.
Socket inet:8891@localhost
Find the following commented out values and uncomment them.
KeyTable
SigningTable
ExternalIgnoreList
Enable and start the opendkim service.
systemctl enable opendkim.service
systemctl start opendkim.service
Edit /etc/postfix/main.cf
and at the following lines to add the DKIM milter. Choose the correct smtpd_milters line for either a socket or TCP connection.
smtpd_milters = unix:/run/opendkim/opendkim.sock
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
Restart postfix with the following command.
systemctl restart postfix.service
Send some test messages through your email server. Check the maillog file for errors. If the emails make it to their destination, check the message headers to make sure that your message is signed. Just search for DKIM
. You should see the Dkim-Filter
and Dkim-Signature
headers for outgoing email.
Yes we need another DNS entry for DMARC. The abbreviate DNS entry is another TXT record with host set to _dmarc
and the value set to v=DMARC1; p=policy; rua=mailto:reports@example.com
where policy
is action that postfix when it fails to authenticate an email. The choices are none
means take no action, quarantine
, or reject
. The email address in the TXT record is where you want reports to be sent for failures.
After creating the DMARC records for each of my domains, I installed the opendmarc
and opendmarc-tools
packages on my server. The only change that I made /etc/opendmarc.conf
was to change the socket to a TCP connection. The syntax is a bit odd with the port number in front of the IP address. It works just fine as a socket connection.
Socket inet:8893@127.0.0.1
Enable and start the opendmarc daemon.
systemctl enable opendmarc.service
systemctl start opendmarc.service
Add the milter to the Postfix configuration. Edit the /etc/postfix/main.cf file and append the appropriate socket type.
smtpd_milters = unix:/run/opendkim/opendkim.sock, unix:/run/opendmarc/opendmarc.sock
smtpd_milters = inet:127.0.0.1:8891, inet:127.0.0.1:8893
Restart Postfix and validate the results by sending and receiving emails.
systemctl restart postfix.service
Not every email provider is setup to verify DMARC and send reports. I did find that outlook.com did send a report back the first time that I sent email to that service.
Implementing SPF, DKIM, and DMARC might seem like a technical deep dive, and admittedly, it requires a bit of configuration. However, as demonstrated with my own Fedora/Postfix setup, the process is manageable and the benefits are significant. I did intentionally not mention the specific versions for the packages since the version numbers will surely change shortly after I publish this. I also did this manually for two of my lesser used domains, but I highly recommend doing this via IAC to ensure consistency and reproducability. I presented it here with multiple edits to the configuration files, but this can be reduced once you have the sum of the changes for each config file.
Taking these steps provides a robust defense for your domains, bolstering your email's trustworthiness and shielding recipients from potential threats. This has eliminated quite a bit of spam and phishing messages from being delivered to my mailbox. In addition to reducing my risk, I am also protecting the repution of my personal domains.