After completing part 1, part 2 and part 3, we have a working Postfix SMTP server and Dovecot IMAP server. We can send and receive emails using a desktop email client. In this tutorial, we are going to learn how to improve email delivery to the recipient’s inbox by setting up SPF and DKIM on Debian server.
SPF and DKIM are two types of TXT records in DNS that allow you to detect email spoofing and help legitimate emails deliver into the recipient’s inbox instead of the spam folder. If your domain is abused by email spoofing, then your emails are likely to land in the recipient’s spam folder if they didn’t add you to the address book.
SPF (Sender Policy Framework) record specifies which hosts or IP addresses are allowed to send emails on behalf of a domain. You should allow only your own email server or your ISP’s server to send emails for your domain.
DKIM (DomainKeys Identified Mail) uses a private key to add a signature to emails sent from your domain. Receiving SMTP servers verify the signature by using the corresponding public key, which is published in your domain’s DNS zone.
In your DNS management interface, create a new TXT record like below.
Record Type | Name | Value |
---|---|---|
TXT | @ | v=spf1 mx ~all |
Where:
@
in the name field to represent the apex domain (example.com).+all
, -all
, ?all
, but they are rarely used.
-all
means that emails sent from not-allowed hosts should be rejected, never to land in the recipient’s inbox or spam folder. I have seen it used by facebook.com, but we generally don’t need such a strict policy.
Some folks might think that -all
will be better as it will reject emails from untrusted hosts. Well, using -all
in your SPF policy can cause your own emails to be rejected when the recipient has two SMTP servers and when the main SMTP server goes offline, your emails will be temporarily stored on the backup SMTP server. When the main SMTP server comes back online, the email will be relayed from the backup SMTP server to the main SMTP server. Since you didn’t list the recipient’s backup SMTP server in your SPF record, the email will be rejected by the recipient’s main SMTP server. So you should use ~all
in your SPF policy.
You can also list a specific IP address in your SPF record.
TXT @ v=spf1 mx ip4:12.34.56.78 ~all
Note that some DNS managers require you to wrap the SPF record with quotes like below.
TXT @ "v=spf1 mx ~all"
To check if your SPF record is propagated to the public Internet, you can use the dig
utility on your Linux box like below.
On Debian, you need to install the bind9-dnsutils
package in order to use dig command:
sudo apt install bind9-dnsutils
dig example.com txt
The txt
option tells dig
that we only want to query TXT records.
You can also use the dmarcian SPF surveyor to test your SPF record syntax.
We also need to tell our Postfix SMTP server to check SPF record for incoming emails. This doesn’t help outgoing email delivery but will help with detecting forged incoming emails.
Install required packages:
sudo apt install postfix-policyd-spf-python
Then edit the Postfix master process configuration file.
sudo nano /etc/postfix/master.cf
Add the following lines at the end of the file, which tells Postfix to start the SPF policy daemon when it’s starting itself.
policyd-spf unix - n n - 0 spawn user=policyd-spf argv=/usr/bin/policyd-spf
Save and close the file. Next, edit Postfix main configuration file.
sudo nano /etc/postfix/main.cf
Append the following lines at the end of the file. The first line specifies the Postfix policy agent timeout setting. The following lines will impose a restriction on incoming emails by rejecting unauthorized email and checking SPF record.
policyd-spf_time_limit = 3600 smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf
Save and close the file. Then restart Postfix.
sudo systemctl restart postfix
Next time, when you receive an email from a domain that has an SPF record, you can see the SPF check results in the raw email header. The following header indicates the sender sent the email from an authorized host.
Received-SPF: Pass (sender SPF authorized).
Install OpenDKIM which is an open-source implementation of the DKIM sender authentication system.
sudo apt install opendkim opendkim-tools
Then add postfix user to opendkim group.
sudo gpasswd -a postfix opendkim
Edit OpenDKIM main configuration file.
sudo nano /etc/opendkim.conf
Uncomment the following lines.
Canonicalization relaxed/simple Mode sv SubDomains no
Find the following line.
#Nameservers 127.0.0.1
Change it to
Nameservers 8.8.8.8,1.1.1.1,9.9.9.9
This tells OpenDKIM to use 8.8.8.8, 1.1.1.1 and 9.9.9.9 to query DNS records. It’s always a good idea to use more than one name server. If one fails, the other can still answer DNS queries. Separate each IP address with a comma.
Next, add the following lines at the end of this file.
# Map domains in From addresses to keys used to sign messages KeyTable refile:/etc/opendkim/key.table SigningTable refile:/etc/opendkim/signing.table # Hosts to ignore when verifying signatures ExternalIgnoreList /etc/opendkim/trusted.hosts # A set of internal hosts whose mail should be signed InternalHosts /etc/opendkim/trusted.hosts
Save and close the file.
Create a directory structure for OpenDKIM
sudo mkdir -p /etc/opendkim/keys
Change the owner from root
to opendkim
and make sure only opendkim
user can read and write to the keys directory.
sudo chown -R opendkim:opendkim /etc/opendkim sudo chmod go-rw /etc/opendkim/keys
Create the signing table.
sudo nano /etc/opendkim/signing.table
Add the following line to the file. This tells OpenDKIM that if a sender on your server is using a @example.com
address, then it should be signed with the private key identified by default._domainkey.your-domain.com
. The second line tells that your sub-domains will be signed by the private key as well.
*@example.com default._domainkey.example.com *@*.example.com default._domainkey.example.com
Save and close the file. Then create the key table.
sudo nano /etc/opendkim/key.table
Add the following line, which tells the location of the private key.
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private
Save and close the file. Next, create the trusted hosts file.
sudo nano /etc/opendkim/trusted.hosts
Add the following lines to the newly created file. This tells OpenDKIM that if an email is coming from localhost or from the same domain, then OpenDKIM should not perform DKIM verification on the email.
127.0.0.1 localhost *.example.com
Save and close the file.
We need to generate two keys:
Create a separate folder for the domain.
sudo mkdir /etc/opendkim/keys/example.com
Generate keys using opendkim-genkey
tool.
sudo opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -v
The above command will create 2048 bits keys. -d (domain)
specifies the domain. -D (directory)
specifies the directory where the keys will be stored and we use default
as the selector (-s)
, also known as the name. Once the command is executed, the private key will be written to default.private
file and the public key will be written to default.txt
file.
Make opendkim
as the owner of the private key.
sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/default.private
And change the permission, so only the opendkim
user has read and write access to the file.
sudo chmod 600 /etc/opendkim/keys/example.com/default.private
Display the public key
sudo cat /etc/opendkim/keys/example.com/default.txt
The string after the p
parameter is the public key.
In your DNS manager, create a TXT record, enter default._domainkey
in the name field. Then go back to the terminal window, copy everything in the parentheses and paste it into the value field of the DNS record. You need to delete all double quotes and white spaces in the value field. If you don’t delete them, then the key test in the next step will probably fail.
Enter the following command on Debian server to test your key.
sudo opendkim-testkey -d example.com -s default -vvv
If everything is OK, you will see Key OK
in the command output.
Note that your DKIM record may need some time to propagate to the Internet. Depending on the domain registrar you use, your DNS record might be propagated instantly, or it might take up to 24 hours to propagate. You can go to https://www.dmarcanalyzer.com/dkim/dkim-check/, enter default
as the selector and enter your domain name to check DKIM record propagation.
If you see Key not secure in the command output, don’t panic. This is because DNSSEC isn’t enabled on your domain name. DNSSEC is a security standard for secure DNS query. Most domain names haven’t enabled DNSSEC. There’s absolutely no need to worry about Key not secure. You can continue to follow this guide.
If you see the query timed out error, it means there’s DNS resolution problem on your server. You can run the above command again to see if it will work. If it still doesn’t work, you can comment out the following line in /etc/opendkim.conf file and restart opendkim.service.
TrustAnchorFile /usr/share/dns/root.key
Postfix can talk to OpenDKIM via a Unix socket file. The default socket file used by OpenDKIM is /run/opendkim/opendkim.sock
, as shown in /etc/opendkim.conf
file. But the postfix SMTP daemon shipped with Debian runs in a chroot jail, which means the SMTP daemon resolves all filenames relative to the Postfix queue directory (/var/spool/postfix
). So we need to change the OpenDKIM Unix socket file.
Create a directory to hold the OpenDKIM socket file and allow only opendkim
user and postfix
group to access it.
sudo mkdir /var/spool/postfix/opendkim sudo chown opendkim:postfix /var/spool/postfix/opendkim
Then edit the OpenDKIM main configuration file.
sudo nano /etc/opendkim.conf
Find the following line.
Socket local:/run/opendkim/opendkim.sock
Replace it with the following line.
Socket local:/var/spool/postfix/opendkim/opendkim.sock
Save and close the file. Next, open the /etc/default/opendkim
file.
sudo nano /etc/default/opendkim
Find the following line.
SOCKET=local:$RUNDIR/opendkim.sock
Change it to
SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"
Save and close the file.
Next, we need to edit the Postfix main configuration file.
sudo nano /etc/postfix/main.cf
Add the following lines at the end of this file, so Postfix will be able to call OpenDKIM via the milter protocol.
# Milter configuration milter_default_action = accept milter_protocol = 6 smtpd_milters = local:opendkim/opendkim.sock non_smtpd_milters = $smtpd_milters
Save and close the file. Then restart opendkim and postfix service.
sudo systemctl restart opendkim postfix
You can now send a test email from your mail server to your Gmail account to see if SPF and DKIM checks are passed. On the right side of an opened email message in Gmail, if you click the show original
button from the drop-down menu, you can see the authentication results.
If your message is not signed and DKIM check failed, you can check postfix log (/var/log/mail.log
) to see what’s wrong with your configuration.
If you see the following message in the mail log:
warning: connect to Milter service local:opendkim/opendkim.sock: No such file or directory
you may want to check if the opendkim systemd service is actually running.
sudo systemctl status opendkim
If opendkim is running and you still see the above error, you might need to edit the /etc/postfix/main.cf file, change
smtpd_milters = local:opendkim/opendkim.sock
to
smtpd_milters = local:/opendkim/opendkim.sock
Then restart Postfix.
Your email server will also perform SPF and DKIM checks on the sender’s domain. You can see the results in the email headers. The following is SPF and DKIM check on a sender using Gmail.
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=2607:f8b0:4864:20::c2d; helo=mail-yw1-xc2d.google.com; envelope-from=someone@gmail.com; receiver=<UNKNOWN> Authentication-Results: email.linuxbabe.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="XWMRd2co"; dkim-atps=neutral
You can also go to https://www.mail-tester.com. You will see a unique email address. Send an email from your domain to this address and then check your score (when you've complete the next part, you should have an perfect score).
If your emails are rejected by Microsoft Outlook or Hotmail, you need to submit the sender information form. After that, your email will be accepted by Outlook/hotmail, but may still be labeled as spam.
What if Your Emails Are Still Being Marked as Spam? I have more tips for you in this article: 7 effective tips to stop your emails from being marked as spam.
Next Step In part 5, we will see how to create DMARC record to protect your domain from email spoofing. As always, if you found this post useful, please subscribe to our free newsletter or follow us on Twitter, or like our Facebook page.