Add DKIM Signing to FreeBSD Servers

In today’s very spammy internet, it becomes increasingly important to verify the source of emails are valid (this is especially true if you want to send emails to gmail or outlook.com!)

One method is to add DKIM signing to outbound emails.

This will detail how to add DKIM signing to a stock FreeBSD server (e.g. a webserver) using sendmail.

First, we need to install the DKIM package:

pkg install opendkim

Now, we need to edit /usr/local/etc/mail/opendkim.conf and change the following lines:

Canonicalization  relaxed/simple
KeyTable          refile:/usr/local/etc/mail/opendkim.keytable
LogWhy            yes
Selector          myselector
SigningTable      refile:/usr/local/etc/mail/opendkim.signingtable
Socket            local:/var/run/dkim/opendkim.sock
SyslogSuccess     Yes
UserID            mailnull:mailnull

You can change myselector to anything you wish to use.  This will form part of the DNS entry required for signature verification so it should be fairly unique (this allows you to have different DKIM signing keys for different servers/services.  I often call them based on the server name.

And we need to do a few steps to setup things:

touch /usr/local/etc/mail/opendkim.keytable
touch /usr/local/etc/mail/opendkim.signingtable
mkdir -p /var/run/dkim /var/db/dkim
chown mailnull:mailnull /var/run/dkim /var/db/dkim
chmod 0700 /var/run/dkim

This creates some files and folders, and sets the necessary permissions.

Now we need to tell opendkim to start on boot, add to /etc/rc.conf the following:

milteropendkim_enable=”YES”
milteropendkim_uid=”mailnull”
milteropendkim_cfgfile=”/usr/local/etc/mail/opendkim.conf”

and now we can start with:

service milter-opendkim start

If all is well, you’ll see two copies of opendkim running in your process list.

Finally, we need to tell sendmail to sign emails that go out.

cd /etc/mail
cp freebsd.mc dkimsigned.mc

Now edit /etc/mail/dkimsigned.mc and add the following line just before the ‘MAILER’ lines:

INPUT_MAIL_FILTER(`opendkim’, `S=local:/var/run/dkim/opendkim.sock’)dnl

Now we can build a new sendmail configuration file and activate it:

cd /etc/mail
make dkimsigned.cf
mv sendmail.cf sendmail.cf.pre-dkim
cp dkimsigned.cf sendmail.cf
service sendmail restart

Now, at this point, nothing has really changed as we haven’t given opendkim any domains to sign.

I wrote a handle script for generating new DKIM configuration… Add the following lines to a script called generate-dkim somewhere in your path:

#!/bin/sh

SELECTOR=myselector
DOMAIN=${1}

if [ -z ${1} ]; then
echo “Usage: ${0} <domain>”
exit 1
fi

if [ -f /var/db/dkim/${DOMAIN}/${SELECTOR}.txt ]; then
echo “=> ERROR: found DKIM keys for ${DOMAIN} already”
/bin/cat /var/db/dkim/${DOMAIN}/${SELECTOR}.txt
exit 1
fi

echo “==> Generating DKIM key for ${DOMAIN}…”
/bin/mkdir /var/db/dkim/${DOMAIN}
/usr/sbin/chown mailnull:mailnull /var/db/dkim/${DOMAIN}
/usr/local/sbin/opendkim-genkey -a -b 2048 -d ${DOMAIN} -D /var/db/dkim/${DOMAIN} -s ${SELECTOR}
/usr/sbin/chown mailnull:mailnull /var/db/dkim/${DOMAIN}/*
echo “==> Done”
echo “==> Updating config to enable DKIM signing…”
echo “${SELECTOR}._domainkey.${DOMAIN}      ${DOMAIN}:${SELECTOR}:/var/db/dkim/${DOMAIN}/${SELECTOR}.private” >> /usr/local/etc/mail/opendkim.keytable
echo “*@${DOMAIN}       ${SELECTOR}._domainkey.${DOMAIN}” >> /usr/local/etc/mail/opendkim.signingtable
echo “==> Done”
echo “==> Reloading OpenDKIM configuration…”
/bin/pkill -USR1 -F /var/run/milteropendkim/pid
echo “==> Done”
echo “DKIM DNS entry to add is below:”
/bin/cat /var/db/dkim/${DOMAIN}/${SELECTOR}.txt

If you chose a different ‘selector’ in the earlier step, remember to change the SELECTOR= line above to match or your signing wont work!

And dont forget to make it executable:

chmod a+x generate-dkim

Now, whenever we want to add a new signing domain, for example dan.me.uk, we can run

generate-dkim dan.me.uk

This will generate a new key, add it to the opendkim configuration files, reload opendkim, and output the required DNS entry to you.

If you’ve previously added the domain, it will just show the DNS entry to you without modifying anything.

Now any emails sent out with a sender of *@dan.me.uk will be DKIM signed using the key generated in the step above.

You MUST add the DNS entry to the domain’s DNS zone for DKIM signing to work.  If the receiving mailserver can’t lookup the DNS entry then it can’t verify your email!

If you ever need to remove a domain from signing, you have to do this manually…

Remove it from /usr/local/etc/mail/opendkim.keytable and /usr/local/etc/mail/opendkim.signingtable (one line in each file), then there’s a folder in /var/db/dkim/ named after the domain which needs to be removed.

For the best chance of delivering to the world’s biggest spammers (gmail/outlook/aol/yahoo/etc), I would recommend SPF, DKIM and DMARC!

19 thoughts on “Add DKIM Signing to FreeBSD Servers

  1. David Dodd

    Thanks for the excellent notes on setting this up.

    FYI … the following are not required in /etc/rc.conf as they match the default vaules used in /usr/local/etc/rc.d/milter-opendkim:

    milteropendkim_uid=”mailnull”
    milteropendkim_cfgfile=”/usr/local/etc/mail/opendkim.conf”

    Reply
  2. Charlie

    Excellent write up. When generating the dkim signature, what is the layout for the dns txt entry?
    I am not following the format. Normally there is a ; at the end of each line.

    Reply
    1. dan Post author

      It gives you the exact entry to put in a bind zonefile.
      The entry starts with “v=DKIM1; k=rsa; p=” and then a very long string.
      Those are the only 2 semicolons in the output that are required.

      Reply
  3. Derek Ragona

    Great write up. I followed this but I am having an issue that outgoing email is not dkim signed. Iincreased the verbosity but only see the messages when I start or stop milter-opendkim.

    Any good methods to debug the missing signatures?

    Reply
    1. dan Post author

      the opendkim configuration (“LogWhy” setting) should add log lines to your maillog saying whether it can or cant sign your mails.
      If you don’t see those then it’s likely that sendmail is not requesting it to. (did you do the last part of the instructions which adds configuration to sendmail to push it through the milter?)

      Reply
  4. Greg Schmit

    This was a hugely helpful resource for setting up tkim with sendmail on FreeBSD. Thank you!

    I just wanted to note that I was getting signature failures (body failures) until I set the Canonicalization to either simple/relaxed or relaxed/relaxed. Just in case you want to change this blog or if someone comes along and gets this same problem.

    Reply
    1. dan Post author

      Body failures shouldn’t really happen unless something modifies the body of course – but the canonicalization should be set to whatever works best for your email 🙂

      Reply
  5. Nicolas Hainaux

    Thanks alot for this excellent tutorial! The script is really helpful to setup things faster.

    I encountered one problem (unrelated to your post): I first did put an underscore in the selector (e.g. mydomainname_dkim). I don’t know the rules about selector strings and had to struggle a little bit to figure out why my ISP wouldn’t let me valid the DKIM entry I was typing (do these rules depend on the ISP, maybe?)… Finally I just had to remove it and rerun the setup script in order to have a working DKIM signature.

    Reply
    1. dan Post author

      technically underscores shouldn’t be in DNS hostnames… although there are exceptions for things like SRV records etc (they are always at the start of the name in that case though) 🙂

      Reply
  6. Rob

    Thanks for this quick start. I’m almost there.

    The key generated for me is on two lines and I’m not sure how to handle that, or if it’s generated incorrectly due to my cut/past of your script.

    The output look like this:
    “v=DKIM1; k=rsa; ”
    “p=1x1x1xetc”
    “bunchmorelettersandnumbershere” ) ; —– DKIM key

    So are those two separate lines correct? Should they be combined into one long string without the quotes? I tried the latter but GMail says the public key is invalid.

    Reply
  7. heximal

    Hello Dan, thanks for your manual, it’s very helpful. One thing I can’t handle remains, can you help me?

    I’ve setup opendkim with my sendmail and it looks like everything works correctly, but it signs only those messages that sent locally (from webmail client or automatic subscriptions). when I try to send message using email-client (e.g. The Bat), maillog shows the following

    Apr 21 11:48:02 d3355 opendkim[57808]: v3L8m2jE059174: [xx.167.249.178] [xx.167.249.178] not internal
    Apr 21 11:48:02 d3355 opendkim[57808]: v3L8m2jE059174: not authenticated
    Apr 21 11:48:02 d3355 opendkim[57808]: v3L8m2jE059174: no signature data

    where xx.167.249.178 is my local IP address. If I add it to TrustedHosts, then opendkim starts to sign messages sent from this IP. But is’t not a solution of course, I can’t add IP addresses each time I send messages from new network.

    Reply
    1. dan Post author

      It should detect when you use SMTP authentication (unless you’ve modified the sendmail config not to pass the authentication flag along to milters).
      Alternatively if your local IP address is not an rfc1918 IP, it may assume that it is an external IP (quite rightly) and therefore not sign the mails.
      The only way it can be an rfc1918 IP is if it is 10.167.249.178.
      If you’re not using SMTP authentication, then you need to turn that on 🙂

      Local generated things will be delivered via localhost which is always signed due to being in the trusted hosts.

      If you’re using SMTP authentication and opendkim isn’t picking it up, check your sendmail MC file.
      If it contains any ‘confMILTER_MACROS_’ defines, they may be removing the feature of passing along SMTP authentication information to milters.

      Hope that helps.

      Reply
      1. heximal

        sorry for hiding first octet, there is actually no secret, the IP is 109.167.249.178, and it is external. that is the difference. why opendkim doesn’t sign messages from external IP?
        SMTP authentication is enabled.

        Reply
        1. dan Post author

          If you’re authenticating, it should pick that up and attempt to sign… the only time it wouldn’t would be if there was a problem in your opendkim.keytable or opendkim.signingtable files I think…

          Reply
  8. John Von Essen

    For some reason, the tutorial is not working on a fresh FreeBSD 11.2 install. I used it last year without issue. This time around, I can’t a weird chmod error when I start (/usr/local/etc/rc.d/milter-opendkim start) and syslog never shows it attempting to insert a skim signature. Its very bizarre.

    Reply
    1. dan Post author

      the chmod error can be ignored (it’ll be fixed soon)
      If you’re not getting signatures added then restart milter-opendkim and then check the end of /var/log/maillog to make sure there’s no warnings about the socket file.
      Assuming no warnings, check the opendkim.keytable and opendkim.signingtable to make sure the entries match the sender address you’re using.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *