Configure Postfix with DKIM using Mailcow for External Relay

I like to tinker with new scripts/programs, and I also like to figure out how to do things properly, or at least as proper as can be through reading the manuals and browsing forums/guides.

So this is one thing I had to figure out in order to allow my servers to send e-mail that wouldn't get bounced back through the various providers, and my goal was to accomplish this, without using SMTP authentication to my Mailcow server in order to send e-mail.

For example, this is how you would configure postfix to relay e-mail to G-Mail.

3 easy steps to configure gmail smtp relay with postfix | GoLinuxCloud
Step by step tutorial to configure postfix using third party gmail smtp relay to send mails to external network. Use SASL with Google 2-Step Authentication

I would do a similar type of setup for my Mailcow server, instead of sending/authenticating with G-Mail, I would send my mail directly to my mail server and authenticate that way. While this solution would work for me, I just didn't like the way it was handled.

This guide is how I accomplished my goal of sending e-mail through my various servers, to relay safely to my Mailcow server. I take no credit for the majority of what is listed here, I merely collected information from various other sites/guides, I will do my best to link to the original guides I used to ensure they get credit.

This guide assumes you have a fully working Mailcow server, with all configurations and DNS completed. It also assumes you are running at least Debian 11. I am running Debian 11 on all my servers, again the commands for other versions of Linux variants are likely similar, but packages/paths are going to be different.

Step 1: Add your servers hostnames as domains to your Mailcow Server

This does two things for us.

  1. It allows us to send/recieve mail via our hostnames. This allows us to setup {abuse,postmaster,etc..}@hostname.yourdomain.com so you can monitor the mailbox if someone were to send e-mail to those mailboxes. I don't actually know if the standard is to only send e-mail to abuse@yourdomain.com or if they can send it to your subdomains as well ... May need to check, but in any case its a thing!
  2. It also configures a signed DKIM key that can be shared via all your domains hosted on Mailcow, this is just easy if you have the same DKIM signature for all your domains, this way you don't have to update your configuration if you add more domains in future.

Make sure to use the same DKIM key you have for all your domains, this is assuming you are using the same key for all your domains.  You do have the option to assign different DKIM keys for each domain/subdomain, but that is beyond my needs, and while it does add a few more steps, I am sure if you require unique DKIM keys for each domain, you can figure it out.

Step 2: Update your DNS records.

So the first thing we need to do is update your DNS records, to allow

Create a SPF record for hostname.yourdomain.com:

Type: "TXT"

Name: "hostname"

Value: "v=spf1 a mx -all"

Now you can configure your SPF record how you like, typically you would use the same format as your main domain, but it can be different.

Next create a DMARC record for hostname.yourdomain.com:

Type: "TXT"

Name: "_dmarc.hostname"

Value: "v=DMARC1; p=reject; rua=mailto:mailauth-reports@yourdomain.com" 

Again, this is going to be different for you, this is how I have my DMARC record configured, I do not suggest just copy and pasting. Do some research and find the configuration that best suits your needs.

Finally create a DKIM record for hostname.yourdomain.com:

Type: "TXT"

Name: "dkim._domainkey.hostname"

Value: "v=DKIM1;k=rsa;t=s;s=email;p={YOUR-PUBLIC-KEY}"
This is the DKIM record for my hostname dbz.danlee.ca

The above value will once again be different for you, make sure to copy/paste the value you recieved from your Mailcow administration page. Login to your Mailcow installation as the Admin, click on Configuration -> ARC/DKIM keys.

Step 3: Modify Mailcow web config so you can view the Private Key

This requires creating/modifying a php file that allows Mailcow to allow the private keys to be viewable on the web interface. I don't know if there is a security risk to leaving the option turned on, but by default it is disabled.

SSH into your server. (I have a bad habit of "sudo su" into root and running commands as the root user, I know, I know ...)

root@dbz:[~]> cd /opt/mailcow-dockerized/data/web/inc
root@dbz:[/opt/mailcow-dockerized/data/web/inc]> nano vars.local.inc.php

This assumes you have installed your mailcow into /opt/mailcow-dockerized

Paste the following contents into the new file you have created.

<?php

$SHOW_DKIM_PRIV_KEYS = true;

Just refresh the Mailcow admin page and you can now view the private key.

Step 4: Configure Mailcow so it will allow your servers to relay safely to your Mailcow server.

This where you have to be VERY careful, if you mess this up, you can potentially configure your Mailcow server to be an open relay, which is very bad for everyone and extremely bad for you.

Mailcow has a pretty good guide on how to do this:

Add trusted networks - mailcow: dockerized documentation

The only modification I have done, is that we only want our servers to be relayed, and that typically means we only want a SINGLE ip to be allowed, not an entire range. This is simple enough. Add your IP to the mynetworks = configuration line in data/conf/postfix/extra.cf and add the subnet mask of /32.

For example if your server is located at 1.1.1.1 and you only want 1.1.1.1 to be accepted you would add 1.1.1.1/32to your mynetworks =.

You repeat this process for however many servers you may control.

Next thing we need to do, and this is just for safety, is whitelist your servers in Mailcow.  We need to do this in two places, the first is fail2bans, this is located at  Configuration -> Fail2ban parameters.

Just add you servers dedicated IP addresses, no need to add the mask /32, you can if you want, but it is redundant.

The last thing we need to do in Mailcow is configure our fowarding hosts, this is located at Configuration -> Forwarding Hosts.

Add your hostname.yourdomain.com to the Host field, select Active from the Spam Filter drop down box, then click + Add

Repeat as needed for all your servers. We are finally done playing Mailcow, now to setup your servers.

Step 5: Install all the needed packages

As always, make sure your system is updated.

root@dbz:[~]> apt update
root@dbz:[~]> apt install postfix opendkim opendkim-tools

When the packages install, just accept all the defaults.

Step 6: Set your hostname and /etc/hosts file

Set your hostname with the command below, then edit your hosts file.

root@dbz:[~]> hostnamectl set-hostname hostname
root@dbz:[~]> nano /etc/hosts

Modify your hosts file to something similar to this:

127.0.0.1       localhost localhost.localdomain
::1     localhost localhost.localdomain
YOUR.IP.GOES.HERE  hostname hostname.yourdomain.com

If you are able to, not always possible, configure the RDNS for your dedicated IP to your hostname. It's good practice, and also required if you are hosting e-mail on your server. Do not modify your RDNS if you already configured it for the hostname for your Mailcow Docker instance.

Step 7: Configure postfix

Let's configure postfix, the settings I use are basically what Mailcow already has documented to allow your Local MTA on Docker host to sendmail.

Local MTA on Docker host - mailcow: dockerized documentation

The modification to /etc/postfix/master.cf is exactly the same for our needs, we don't want or need to advertise postfix on port 25.

Postfix users disable the listener by commenting the following line (starting with smtp or 25) in /etc/postfix/master.cf:
#smtp      inet  n       -       -       -       -       smtpd

The next part is a little different from the guide that Mailcow shows, and this is because we are not running Mailcow in a docker container on our satellite servers.

Modify your /etc/postfix/main.cf just replace all the contents with the following, make sure to modify the relayhost and myhostname to match your requirments.

relayhost needs to be the public dedicated IP for your Mailcow server.

# See /usr/share/postfix/main.cf.dist for a commented, more complete version


# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2



# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level=may

smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache


smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = hostname.yourdomain.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, hostname, localhost.localdomain, localhost
relayhost = YOUR.MAILCOW.PUBLIC.IP
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = loopback-only
inet_protocols = all
relay_transport = relay
default_transport = smtp
milter_default_action = accept
milter_protocol = 2
smtpd_milters = unix:/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock

Last step is to add the postfix use to the opendkim group.

root@dbz:[~]> usermod -a -G opendkim postfix

Just a little note, on one of my server /etc/mailname was created, and on another it was not. Not sure why ... anyway, all you have to do is create a text file and enter a single line of text with the following:

hostname.yourdomain.com

Step 8: Configure Opendkim

Create the folder /etc/opendkim

Grab the private key for DKIM you were able to view in Step 3. It looks something like this:

-----BEGIN PRIVATE KEY-----
Contrarytopopularbelief,LoremIpsumisnotsimplyrandomtext.Ithas
rootsinapieceofclassicalLatinliteraturefrom45BC,makingitover2
000yearsold.RichardMcClintock,aLatinprofessoratHampdenydneyCo
llegeinVirginia,lookeduponeofthemoreobscureLatinwords,consect
etur,fromaLoremIpsumpassage,andgoingthroughthecitesofthewordi
nclassicalliterature,discoveredtheundoubtablesource.LoremIpsu
mcomesfromsections1.10.32and1.10.33of"deFinibusBonorumetMalor
um"(TheExtremesofGoodandEvil)byCicero,writtenin45BC.Thisbooki
satreatiseonthetheoryofethics,verypopularduringtheRenaissance
.ThefirstlineofLoremIpsum,"Loremipsumdolorsitamet..",comesfro
malineinsection1.10.32.
-----END PRIVATE KEY-----

Create a new file called /etc/opendkim/private.key and paste the contents into this file, make sure you have Linux line endings if you are using a special editor.  Let's protect the file to ensure it's not readable by anyone except root.

root@dbz:[~]> nano /etc/opendkim/private.key
root@dbz:[~]> chmod 600 /etc/opendkim/private.key 

Next we create our /etc/opendkim/opendkimhosts file which indiacate what hostnames will our DKIM signature be attached to.

root@dbz:[~]> nano /etc/opendkim/opendkimhosts

Paste and modify the following contents:

127.0.0.1
localhost
YOUR.HOSTNAME.PUBLIC.IP
yourdomain.com
hostname.yourdomain.com

I have added all my controlled IP's, domains, and hostnames to this file. Just easier than modifying it on a per server instance.

Next we need to modify /etc/opendkim.conf

root@dbz:[~]> nano /etc/opendkim.conf

Replace the contents with the following:

# This is a basic configuration for signing and verifying. It can easily be
# adapted to suit a basic installation. See opendkim.conf(5) and
# /usr/share/doc/opendkim/examples/opendkim.conf.sample for complete
# documentation of available configuration parameters.

Syslog                  yes
SyslogSuccess           yes
#LogWhy                 no

# Common signing and verification parameters. In Debian, the "From" header is
# oversigned, because it is often the identity key used by reputation systems
# and thus somewhat security sensitive.
Canonicalization        relaxed/simple
#Mode                   sv
#SubDomains             no
OversignHeaders         From

# Signing domain, selector, and key (required). For example, perform signing
# for domain "example.com" with selector "2020" (2020._domainkey.example.com),
# using the private key stored in /etc/dkimkeys/example.private. More granular
# setup options can be found in /usr/share/doc/opendkim/README.opendkim.
#Domain                 example.com
#Selector               2020
#KeyFile                /etc/dkimkeys/example.private

# In Debian, opendkim runs as user "opendkim". A umask of 007 is required when
# using a local socket with MTAs that access the socket as a non-privileged
# user (for example, Postfix). You may need to add user "postfix" to group
# "opendkim" in that case.
UserID                  opendkim
UMask                   0002

# Socket for the MTA connection (required). If the MTA is inside a chroot jail,
# it must be ensured that the socket is accessible. In Debian, Postfix runs in
# a chroot in /var/spool/postfix, therefore a Unix socket would have to be
# configured as shown on the last line below.
#Socket                 local:/run/opendkim/opendkim.sock
#Socket                 inet:8891@localhost
#Socket                 inet:8891
Socket                  local:/var/spool/postfix/run/opendkim/opendkim.sock

PidFile                 /run/opendkim/opendkim.pid

# Hosts for which to sign rather than verify, default is 127.0.0.1. See the
# OPERATION section of opendkim(8) for more information.
#InternalHosts          192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12

# The trust anchor enables DNSSEC. In Debian, the trust anchor file is provided
# by the package dns-root-data.
TrustAnchorFile         /usr/share/dns/root.key
#Nameservers            127.0.0.1

Domain *
KeyFile /etc/opendkim/private.key
Selector dkim
InternalHosts /etc/opendkim/opendkimhosts
ExternalIgnoreList /etc/opendkim/opendkimhosts
AutoRestart yes
Background yes
Canonicalization simple
DNSTimeout 5
Mode sv
SignatureAlgorithm rsa-sha256
SubDomains no
#UseASPDiscard no
#Version rfc4871
X-Header no

We are almost done, now we need to modify /etc/default/opendkim

root@dbz:[~]> nano /etc/default/opendkim

Look for a line similar to the following and match what you see below, basically you need to comment out one, and uncomment the other line.

RUNDIR=/var/spool/postfix/run/opendkim
#RUNDIR=/run/opendkim

The last thing we need to do is create a folder and set the right permissions:

root@dbz:[~]> mkdir -p /var/spool/postfix/run/opendkim
root@dbz:[~]> chown -R opendkim:opendkim /var/spool/postfix/run/opendkim

If you have paid attention to the contents of the files you have posted, you may have noticed soemthing that doesn't add up. Have you spotted it yet?

If you check the config we did in /etc/postfix/main.cf you will see we are pointing to the unix socket for opendkim at /run/opendkim/opendkim.sock. But if you check out our config we did for opendkim, it goes to /var/spool/postfix/run/opendkim/opendkim.sock. The reason we did this is because postfix is running in chroot of /var/spool/postfix.

Step 9: Enable services to start at boot and restart services to take effect

root@dbz:[~]> systemctl enable postfix
root@dbz:[~]> systemctl enable opendkim
root@dbz:[~]> systemctl restart postfix opendkim

Now just send some e-mails from your satellite server via sendmail to a G-Mail e-mail. Why you may ask?  Well it's a great indicator to tell if your setup works, and you can check the headers in G-Mail to quickly check pass/fail for SPF/DKIM/DMARC.  See the following to see how:

DKIM Test - DKIM Verify - DKIM Validator

Alternatively you can use:

Newsletters spam test by mail-tester.com
mail-tester.com is a free online service that allows you to test your emails for Spam, Malformed Content and Mail Server Configuration problems

or

Email Spam Check, DKIM, SPF, DMARC Validation and Content Analysis
The complete solution for email check and analysis. A validation of DKIM, SPF and DMARC as well as a detailed content analysis and much more are integrated.