Setup Email Server From Scratch On FreeBSD #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