I run a small mail server at home with the mailcow Docker stack. This mail server can be unreachable for various reasons, and I don’t want to rely on SMTP clients to retry the delivery when my server is unreachable. For that reason, I have setup a backup mail server that can be tried to deliver mail to when the primary server is unreachable. SMTP clients, in this context, are external mail servers trying to deliver mail to my server. The sending strategy as specified in RFC5321 states the delivery must be periodically retried, but leaves the intervals implementation and situation dependent.
I am running a reduced version of the mailcow stack on a cheap VPS (Virtual Private Server) hosted in the cloud. In other words, my backup server is geographically located elsewhere than my primary mail server. When my primary server is unreachable, then the SMTP client will try the secondary server instead. This secondary server keeps the received messages in a queue and will try to relay them to my primary server.
Mail servers talk to each other with the SMTP protocol over port 25. And that is a problem for my backup server. Most (if not all) cloud providers by default block outbound traffic on port 25. They do this to prevent their infrastructure is being used as spam generating networks. The result is that my backup mail server won’t be able to relay mail to my primary server. Of course, most providers will provide you with options to unblock port 25, but that requires work and proof that you are not a malicious mail server. Since my backup server only needs to reach my primary server, I can use an alternative port for this traffic, bypassing the blocked port.
This post describes the configuration steps I took to make the backup server relay mail to my primary server.
DNS #
When a SMTP client wants to deliver mail addressed to @bkpt.eu
, that client will look up the MX records1 for the domain bkpt.eu
.
This query gives back two MX records, pointing to two different hosts in the domain:
$ dig bkpt.eu MX +noall +answer
bkpt.eu. 300 IN MX 20 mx2.bkpt.eu.
bkpt.eu. 300 IN MX 10 mail.bkpt.eu.
The fifth field of these records indicate the preference of the host and the lower the number, the higher the priority.
This number may be anything that is a valid 16-bit unsigned number2.
When the priorities differ, the client must try the host with the highest priority first. In this case that is mail.bkpt.eu
, which is my primary mail server.
When this host is not reachable, the client tries the second host. That is mx2.bkpt.eu
Mailcow #
Technically, we need only need Postfix on our backup server, but since I am running mailcow on my primary server already, I will use it on the secondary server as well. I choose port 2525 as alternative port that my servers use to communicate.
Primary server #
First, the primary server need to be configured to accept SMTP traffic on port 2525. Postfix is responsible for accepting mail and the relevant configuration file is master.cfg
3.
In this configuration file we need to tell the Postfix master process to start a daemon responsible for handling incoming SMTP connection on port 2525.
Mailcow, by default, has postcreen configured as a daemon that filters traffic to smtpd.
For configuring port 2525, duplicate the line that configures postcreen in /opt/mailcow-dockerized/data/conf/postfix/master.cfg
and change the port to 2525:
smtp inet n - n - 1 postscreen
2525 inet n - n - 1 postscreen
To test if the port is working:
$ telnet mail.bkpt.eu 2525
Trying 2a10:3781:4c81:1::1...
Connected to mail.bkpt.eu.
Escape character is '^]'.
220 mail.bkpt.eu ESMTP Postcow
HELO bkpt.eu
250 mail.bkpt.eu
^]
telnet> q
Connection closed.
The server is responding with the SMTP protocol on port 2525.
Furthermore, the primary server needs to be told that it unconditionally accepts all incoming messages from our backup server.
This avoids any filtering and message loss between the backup server and primary server. This is safe to do since I manage both servers myself.
This can be done in the administration area of mailcow under System > Configuration
and then the tab Options > Forwarding Hosts
.
Backup server #
For the backup server, first, mailcow needs to be configured to accept mail for the domain that the primary server is accepting. What is different from the configuration on the primary server, is that the relay options are enabled.
Second, the backup server needs to be told what the next hop for the relaying domain is.
By default, Postfix will request the MX records for the domain and uses port 25 to communicate to the listed servers.
Since the goal here is to connect to port 2525, the default behavior needs to be overridden.
This behavior can be changed with transport maps and are found under System > Configuration
and the Routing
tab.
When just a domain is specified bkpt.eu
as destination, this transport map matches all mail addressed to @bkpt.eu
.
The next hop syntax where the server is specified in square brackets tells Postfix not to use MX lookup for the mail server.
Therefore, this transport map tells that the mail to the bkpt.eu
domain should be send to the mail server found on mail.bkpt.eu
, which is the primary server.
If the MX lookup mechanism was used instead, Postfix would try the primary server first, which is likely unreachable the moment, then tries the secondary server, which is this server itself.
Postfix will detect this loop and aborts any further attempt to relay the message.
This transport can be conveniently tested from the mailcow UI. Be sure to use a “From:” address that has a domain that has MX records (yahoo.com has MX records). And be sure to use a “To:” address that the primary server accepts.