Setup Email Server From Scratch Debian #2 - 06 SPF DKIM DMARC
We believe in data independence, and support others who want data independence.
Debian Email From Scratch version 2 finished 2025-07-30.
We are still adding to it but it all works!
#########################################
# Setting Up SPF DMARC and DKIM Records #
#########################################
To improve mail delivery to other email servers we need to setup SPF, DKIM, and
DMARC records so recipient mail servers can identify we are allowed to send
emails for our domain. We also need these mail filters, milters, to mark or
block incoming spam. To setup these records we need to modify the DNS records
for our domain. I covered DMARC and SPF DNS setup on the first page of server
installation, and I'll post it here again to refresh.
Login to your DNS provider, which is usually the same as domain registrar. I use
Namecheap and DNS is included with the domain registration, no extra charge.
# DKIM requires dnssec so enable dns security aka dnssec. Go to the NameCheap
# advanced DNS page and toggle on DNSSEC. The toggle may be a little broken, so
# click in the empty space where it's supposed to appear till it toggles on and
# is visible. Test with dig and look for the ad flag using another dnssec
# enabled name server. The 'ad' flag won't show up if you test against the
# master nameserver for the same domain.
# Namecheap Advanced DNS -> Toggle On DNS Sec
# Test if dnssec is enabled, look for the line with the 'ad' flag
root@okdeb.com:~# dig @8.8.8.8 okdeb.com +dnssec +multiline
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
# Here is what you should have setup with your DNS already.
Namecheap -> Account -> Domains -> DNS -> Advanced DNS
# Add New Records
A @ 15.204.113.148
AAAA @ 2604:2dc0:202:300::3645
A mail 15.204.113.148
A mx 15.204.113.148
AAAA mail 2604:2dc0:202:300::3645
AAAA mx 2604:2dc0:202:300::3645
CNAME autoconfig mx.okdeb.com
CNAME autodiscover mx.okdeb.com
CNAME www okdeb.com
TXT @ v=spf1 ip4:15.204.113.148 ip6:2604:2dc0:202:300::3645/64 mx ~all
TXT _dmarc v=DMARC1; p=quarantine; rua=mailto:postmaster@okdeb.com; ruf=mailto:postmaster@okdeb.com; sp=quarantine
# In Custom MX Records
MX @ mx.okdeb.com 0
MX @ mail.okdeb.com 10
# You will need to add a SV records for Autodiscover
Type Service Protocol Priority Weight Port Target
SRV Record _autodiscover _tcp 5 0 443 mx.okdeb.com
# DMARC tells recieving servers what action to take if SPF lookup fails or
# doesn't match and where to send reports of failures or problems.
# SPF identifies which servers are authorized to send email on the domains behalf,
# in DNS the @ means this domain and the domain name is appended to the other
# entries unless you put a dot on the end.
# Check the spf record ...
nslookup -type=txt okdeb.com
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
okdeb.com text = "v=spf1 ip4:15.204.113.148 ip6:2604:2dc0:202:300::3645/64 mx ~all
# Check the dmarc record ...
nslookup -type=txt _dmarc.okdeb.com
Server: 127.0.0.53
Address: 127.0.0.53#53
Non-authoritative answer:
_dmarc.okdeb.com text = "v=DMARC1; p=quarantine; rua=mailto:postmaster@okdeb.com; ruf=mailto:postmaster@okdeb.com; sp=quarantine"
# You can also check your record validity with mxtoolbox.com
https://mxtoolbox.com/
Enter your domain name and submit
# Setup SPF milter to check SPF records for incoming email.
apt install postfix-policyd-spf-python
# Integrate policyd-spf with postfix
nano /etc/postfix/master.cf
# --- add at the end
# spf milter
policyd-spf unix - n n - 0 spawn
user=policyd-spf argv=/usr/bin/policyd-spf
# ---
# If there already is a smtpd_recipient_restrictions, add the "check_policy_service"
# after the "reject_unauth_destination" or the server will become an open relay.
nano /etc/postfix/main.cf
# ---
policyd-spf_time_limit = 3600
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
check_policy_service unix:private/policyd-spf
# ---
# Optional, change the format of the spf header line, leave as default
# Header-Type = Recieved-SPF (default)
# Header-Type = AR requires Authserv_Id = hostname
nano /etc/postfix-policyd-spf-python/policyd-spf.conf
# ---
# For a fully commented sample config file see policyd-spf.conf.commented
debugLevel = 1
TestOnly = 1
HELO_reject = Fail
Mail_From_reject = Fail
PermError_reject = False
TempError_Defer = False
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
# Authserv_Id = mx.okdeb.com
# Header_Type = Received-SPF
# Header_Type = SPF
# Header_Type = AR
# ---
systemctl restart postfix dovecot
# Check if the unix socket is created in the correct location
ls /var/spool/postfix/private/policyd-spf
/var/spool/postfix/private/policyd-spf
# Send a test email and check the raw email source for a line like ...
# Check if SPF milter is working, send an email to the new account jack@okdeb.com
# and view the source and make sure it contains a line like ...
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip...
- or -
Authentication-Results: mx.okdeb.com; spf=pass (sender SPF authorized) ...
# If you have problems recieving email check with
journalctl -eu postfix
journalctl -eu dovecot
# DKIM is a signature that is included in the email, the recieving server looks up
# the DKIM public record on DNS does some sort of comparison with the signature in
# the email to determine if it is from a valid sender. Configure the email server
# to append the DKIM signature, and add the public key the DNS record.
# DKIM is supposed to report secure if dnssec is enabled, so make sure dnssec is
# enabled. I won't go into setting up a dnssec nameserver here.
apt install opendkim opendkim-tools
adduser postfix opendkim
# Configure opendkim - change and add to
nano /etc/opendkim.conf
# ---
Canonicalization relaxed/simple
On-BadSignature reject
BodyLengthDB refile:/etc/mail/bodylengthdb.cfg
Mode sv
SubDomains no
OversignHeaders From
Socket local:/var/spool/postfix/opendkim/opendkim.sock
# add these lines to end of file
TrustAnchorFile /usr/share/dns/root.key
#Nameservers 127.0.0.1
# Specify the list of keys
KeyTable file:/etc/opendkim/keytable
# Match keys and domains. To use regular expressions in the file, use refile:
# instead of file:
SigningTable refile:/etc/opendkim/signingtable
# Match a list of hosts whose messages will be signed. By default, only
# localhost is considered as internal host.
InternalHosts refile:/etc/opendkim/trustedhosts
# Hosts to ignore when verifying signatures
ExternalIgnoreList refile:/etc/opendkim/trustedhosts
# ---
mkdir -p /etc/dkimkeys
chown -R opendkim:opendkim /etc/dkimkeys
chmod go-rw /etc/dkimkeys
nano /etc/opendkim/signingtable
# ---
*@okdeb.com 20250718._domainkey.okdeb.com
# *@domain2.com 20250721._domainkey.domain2.com
# ---
nano /etc/opendkim/keytable
# ---
20250718._domainkey.okdeb.com okdeb.com:20250718:/etc/dkimkeys/okdeb.com/20250718.private
# 20250721._domainkey.domain2.com domain2.com:20250721:/etc/dkimkeys/domain2.com/20250718.private
# ---
nano /etc/opendkim/trustedhosts
# ---
127.0.0.1
::1
localhost
15.204.113.148
2604:2dc0:202:300::3645
mx.okdeb.com
mail.okdeb.com
okdeb.com
# ---
mkdir -p /etc/dkimkeys/okdeb.com
opendkim-genkey -b 2048 -d okdeb.com -D /etc/dkimkeys/okdeb.com -s 20250718 -v
find /etc/dkimkeys/* -type d -exec chown -R opendkim:postfix {} \;
find /etc/dkimkeys/* -type d -exec chmod 700 {} \;
chmod 600 /etc/dkimkeys/*/*.private
chown opendkim:postfix /var/spool/postfix/opendkim
echo '.*' > /etc/mail/bodylengthdb.cfg
# Publish the public key with your nameserver, again I am using NameCheap, go to
# Domains -> Domain -> Manage -> Advanced DNS
# Make sure you have enabeled dnssec here
cat /etc/dkimkeys/okdeb.com/*.txt
# This will output the key as several lines, you will need to paste this as one line,
# there are usually 2 " " sections to remove and also remove the beginning and ending
# quote. Then remove the quotes and spaces like ....
"v=DKIM1; h=sha256; k=rsa; " "p=MII...
v=DKIM1; h=sha256; k=rsa; p=MII...
and
...1234" "5678...
...12345678...
# add this to your DNS, choose TXT Record and add the selector._domainkey then the
# DKIM line all one line ...
20250718._domainkey v=DKIM1; h=sha256; k=rsa; p=MIIBI ... a very long line no spaces
# The txt record can take time to propagate but is usually a few minutes.
# Check it - it is normal to have a space and quotes in the output here
dig txt 20250718._domainkey.okdeb.com +dnssec +multiline
nslookup -type=txt 20250718._domainkey.okdeb.com
# Test the key - in some situations it will say key not secure, it can be
# ignored and is usually the result of /etc/dkimkeys directory permissions not
# being secure, TrustAnchorFile is not specified in opendkim.conf, dnssec is not
# enabled for the domain, using a local authoritative dns server, or dnssec
# depends on ntpsec .. eg time problems.
opendkim-testkey -d okdeb.com -s 20250718 -vvv
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key '20250718._domainkey.okdeb.com'
opendkim-testkey: key secure
opendkim-testkey: key OK
# Check it with mxtoolbox
https://mxtoolbox.com
# Configure openkim socket
mkdir -p /var/spool/postfix/opendkim
chown -R opendkim:postfix /var/spool/postfix/opendkim
nano /etc/default/opendkim
# --- change the SOCKET line as follows
SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"
# ---
# Configure openkim in postfix
nano /etc/postfix/main.cf
# --- add these to end of file
milter_default_action = accept
milter_protocol = 6
smtpd_milters = unix:opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
# ---
systemctl restart opendkim postfix dovecot
# Now try sending an email from the jack@okdeb.com account to see if the header has
# been signed by opendkim. You should see some lines as follows...
# Send a test mail from your mail server, in this case jack@okdeb.com.
# The mail should have a header that contains something like this ...
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=okdeb.com;
s=CRHdAnOqqitUaWRuNkHLdIpbgw76; t=1747256382;
bh=6GInUBItXoVjcpY1TOdSfAVLN/R6eU2ujlFwzTrfFtw=;
h=Date:To:From:Subject:From;
b=RdNarAxFLxIVz/D4sMfkK7OTzpQdgzCBX1tN6Z1xiRlPAEtq3Z6PMEbIanKhBB5Zu
BbHUodrOHVuib8hyvoCV0U6sp5Kz0jexiYqgM4R2KQkHrg+nIICfewSj6PHtfwMy8i
Tq67eaEv7jr7ShwRkZaQcqNHaZHyjFUmXlMy9y82F0mH5f2vVw+bZ2zbVMMVW3AYEv
f/espHlUSbKbQsuLxEj/TTHcNGQ7YD3Moji7PL7e57vkQTS8r4QyFh3OkI8Jc62W8E
9Rj9BA0kZ6lEFcH89wvi/BwmJowspeOcLYX3OoQtJ2UeZhrvpXg8kZAlytXJap+1dv
xdjzbn58GIq4g==
Authentication-Results: mail.otherdomain.com (amavisd-new); dkim=pass (2048-bit key)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=okdeb.com;
s=20250718; t=1752857804;
bh=PU2XIErWsXvhvt1W96ntPWZ2VImjVZ3vBY2T/A+wA3A=;
h=Date:To:From:Subject:From;
b=bbdnwmFL4iZDHbP+u9feWSwHMbaqp7N+Stq7lF09ePLDjGzegxMR5Z2p4hrzMUHB/
...
m/KD8R8a5DOGQ==
ARC-Authentication-Results: i=1; mx.google.com;
dkim=pass header.i=@okdeb.com header.s=20250718 header.b=O2Rb3Ztg;
spf=pass (google.com: domain of jack@okdeb.com designates 15.204.113.148 as permitted sender) smtp.mailfrom=jack@okdeb.com;
dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=okdeb.com
# Install and Configure DMARC
# We do not need reports so select dbconfig-common: No
apt install opendmarc
dbconfig-common: No
nano /etc/opendmarc.conf
# ---
AuthservID OpenDMARC
PidFile /run/opendmarc/opendmarc.pid
PublicSuffixList /usr/share/publicsuffix/public_suffix_list.dat
RejectFailures true
Socket local:/var/spool/postfix/opendmarc/opendmarc.sock
Syslog true
TrustedAuthservIDs localhost,mx.okdeb.com,mail.okdeb.com,okdeb.com
UMask 0002
UserID opendmarc
IgnoreAuthenticatedClients true
RequiredHeaders true
SPFSelfValidate true
IgnoreHosts /etc/opendmarc/ignore.hosts
# ---
# Make the ignore.hosts file
nano /etc/opendmarc/ignore.hosts
# ---
127.0.0.1
147.135.65.9
2604:2dc0:202:300::3645
::1
localhost
# ---
# Create and set permissions for opendmarc socket
mkdir -p /var/spool/postfix/opendmarc
chown -R opendmarc:opendmarc /var/spool/postfix/opendmarc
chmod -R 750 /var/spool/postfix/opendmarc
adduser postfix opendmarc
systemctl enable opendmarc
systemctl restart opendmarc
systemctl status opendmarc
# Integrate DMARC into Postfix - change the line below to include the opendmarc socket
# Do NOT use smtpd_milters = local:opendkim/opendkim.sock
# Use smtpd_milters = unix:opendkim/opendkim.sock
nano /etc/postfix/main.cf
# ---
smtpd_milters = unix:opendkim/opendkim.sock,unix:opendmarc/opendmarc.sock
# ---
systemctl restart opendkim opendmarc postfix dovecot
# If opendmarc opendkim or spf daemon fails to restart or usually it will give
# the error restarting too quickly it is a configuration, path, or permissions
# error. It can also be a module is not installed eg. I had a module not loading
# error and though I had written it up in this howto I had not actually run the
# command to install the module: apt get dovecot-mysql. Check permissions on the
# socket files in /var/spool/postfix, this will usually show up as unable to
# create socket or permissions error, or just can't restart. In another case I
# used /etc/opendkim/keys then switched to /etc/dkimkeys and made the change in
# this howto but not in the actual opendkim.conf ... so opendkim happily failed
# to restart with a restarting too quickly error, no indication of what was the
# actual problem. So went I went through the configuration files very carefully
# till I found the mistake.
# Send yourelf an email and check the mail header for DMARC results.
Authentication-Results: OpenDMARC; dmarc=pass (p=quarantine dis=none) header.from ...
# Send an email from another account to your new mail server to check DMARC
grep dmarc /var/log/maillog
May 15 12:15:48 okdeb opendmarc[96573]: A853C2BE2D: domain2.net pass
# As suggested by linuxbabe, a great tool to test the spammyness if your emails is
https://www.mail-tester.com
# Next up Roundcube Webmail