Complete Mailcow Multi-Server Configuration Guide
Overview
This guide documents how to configure two Mailcow servers to communicate using private IP addresses for reliable email delivery, bypassing public internet routing issues.
Server Architecture
| Server | Hostname | Public IP | Private IP | Domain |
|---|---|---|---|---|
| Server 1 | hostone | Public ip | 10.36.4.10 | domain1.com |
| Server 2 | hosttwo | public Ip | 10.36.4.4 | domain2.com |
Part 1: Initial Server Setup
1.1 Install Mailcow
Follow the official Mailcow installation guide for each server:
bash
cd /opt git clone https://github.com/mailcow/mailcow-dockerized cd mailcow-dockerized ./generate_config.sh docker compose pull docker compose up -d
1.2 Configure Domains in Mailcow UI
-
Add your domains in Mailcow admin interface
-
Set up DNS records (MX, SPF, DKIM, DMARC)
Part 2: Private Network Configuration
2.1 Identify Private IPs
On each server, find the private IP:
bash
ip addr show | grep -E "inet.*(eth0|ens|enp)"
Note down the private IPs (usually in 10.x.x.x, 172.x.x.x, or 192.168.x.x range).
2.2 Test Private IP Connectivity
bash
# From Server 1 to Server 2 ping -c 4 10.36.4.4 nc -4 -zv 10.36.4.4 25 # From Server 2 to Server 1 ping -c 4 10.36.4.10 nc -4 -zv 10.36.4.10 25
Part 3: Postfix Transport Maps Configuration
3.1 On Server 1 (domain1.com)
Configure to send emails for domain2.com via private IP:
bash
# Enter Postfix container docker exec -it mailcowdockerized-postfix-mailcow-1 bash # Create transport file echo "domain2.com smtp:[10.36.4.4]:25" > /etc/postfix/transport echo "mail.domain2.com smtp:[10.36.4.4]:25" >> /etc/postfix/transport # Process the transport map postmap /etc/postfix/transport # Get current transport_maps value current_transport=$(postconf -h transport_maps) # Add your hash map to the beginning postconf -e "transport_maps = hash:/etc/postfix/transport, $current_transport" # Verify configuration postconf transport_maps postmap -q samratnepal.com hash:/etc/postfix/transport # Reload Postfix postfix reload exit
3.2 On Server 2 (domain2.com)
Configure to send emails for domain1.com via private IP:
bash
# Enter Postfix container docker exec -it mailcowdockerized-postfix-mailcow-1 bash # Create transport file echo "domain1.com smtp:[10.36.4.10]:25" > /etc/postfix/transport echo "mail.domain1.com smtp:[10.36.4.10]:25" >> /etc/postfix/transport # Process the transport map postmap /etc/postfix/transport # Get current transport_maps value current_transport=$(postconf -h transport_maps) # Add your hash map to the beginning postconf -e "transport_maps = hash:/etc/postfix/transport, $current_transport" # Verify configuration postconf transport_maps postmap -q domain1.com hash:/etc/postfix/transport # Reload Postfix postfix reload exit
Part 4: Disable Greylisting Between Servers
4.1 On Both Servers
Create Rspamd whitelist configuration:
bash
# On Server 1 (10.36.4.10) docker exec -it mailcowdockerized-rspamd-mailcow-1 bash mkdir -p /etc/rspamd/local.d cat > /etc/rspamd/local.d/greylist.conf << 'EOF' # Whitelist Server 2 whitelisted_ip = "public ip"; whitelisted_ip = "10.36.4.4"; whitelisted_ip = "10.36.4.0/24"; # Entire private network EOF exit docker restart mailcowdockerized-rspamd-mailcow-1 # On Server 2 (10.36.4.4) docker exec -it mailcowdockerized-rspamd-mailcow-1 bash mkdir -p /etc/rspamd/local.d cat > /etc/rspamd/local.d/greylist.conf << 'EOF' # Whitelist Server 1 whitelisted_ip = "public"; whitelisted_ip = "10.36.4.10"; whitelisted_ip = "10.36.4.0/24"; # Entire private network EOF exit docker restart mailcowdockerized-rspamd-mailcow-1
Part 5: Firewall Configuration
5.1 On Both Servers
Ensure iptables allows traffic between private IPs:
bash
# Check current FORWARD policy sudo iptables -L FORWARD -n -v # If FORWARD policy is DROP, change to ACCEPT sudo iptables -P FORWARD ACCEPT # Allow traffic between servers sudo iptables -I DOCKER-USER -s 10.36.4.0/24 -j ACCEPT # Make iptables rules persistent sudo apt-get install -y iptables-persistent sudo netfilter-persistent save
5.2 Disable Mailcow Netfilter Isolation
Edit mailcow.conf on both servers:
bash
cd /opt/mailcow-dockerized nano mailcow.conf
Add or modify:
text
SKIP_NETFILTER_ISOLATION=y
Then restart Mailcow:
bash
docker compose down docker compose up -d
Part 6: Testing the Configuration
6.1 Test Email from Server 1 to Server 2
bash
swaks --to developers@samratnepal.com --from dipeshmahato@spectranepal.com --server localhost --port 587 --tls --auth LOGIN --auth-user dipeshmahato@spectranepal.com --auth-password "your-password"
6.2 Test Email from Server 2 to Server 1
bash
swaks --to dipeshmahato@spectranepal.com --from developers@samratnepal.com --server localhost --port 587 --tls --auth LOGIN --auth-user developers@samratnepal.com --auth-password "your-password"
6.3 Monitor Logs During Testing
bash
# Watch Postfix logs docker logs mailcowdockerized-postfix-mailcow-1 -f --tail 50 # Watch Rspamd logs docker logs mailcowdockerized-rspamd-mailcow-1 -f --tail 50 # Watch Dovecot logs docker logs mailcowdockerized-dovecot-mailcow-1 -f --tail 50
6.4 Check Mail Queues
bash
# On both servers docker exec mailcowdockerized-postfix-mailcow-1 mailq
Part 7: Troubleshooting Commands
7.1 Check Transport Map
bash
# Test if transport map is working docker exec mailcowdockerized-postfix-mailcow-1 postmap -q samratnepal.com hash:/etc/postfix/transport # Should return: smtp:[10.36.4.4]:25
7.2 Check Private IP Connectivity
bash
# Test ping ping -c 4 10.36.4.4 # Test port 25 nc -4 -zv 10.36.4.4 25
7.3 Clear Stuck Emails
bash
# Delete all queued messages docker exec mailcowdockerized-postfix-mailcow-1 postsuper -d ALL # Delete specific message docker exec mailcowdockerized-postfix-mailcow-1 postsuper -d MESSAGE_ID
7.4 Force Queue Processing
bash
docker exec mailcowdockerized-postfix-mailcow-1 postqueue -f
Part 8: Maintenance Commands
8.1 Restart Services
bash
# Restart specific container docker restart mailcowdockerized-postfix-mailcow-1 # Restart all Mailcow services cd /opt/mailcow-dockerized docker compose down docker compose up -d
8.2 Backup Transport Maps
bash
# Save transport files for future reference docker cp mailcowdockerized-postfix-mailcow-1:/etc/postfix/transport ./transport.backup docker cp mailcowdockerized-postfix-mailcow-1:/etc/postfix/transport.db ./transport.db.backup
8.3 Restore Transport Maps
bash
docker cp ./transport.backup mailcowdockerized-postfix-mailcow-1:/etc/postfix/transport docker exec mailcowdockerized-postfix-mailcow-1 postmap /etc/postfix/transport docker exec mailcowdockerized-postfix-mailcow-1 postfix reload
Part 9: Enable Debug Logging (If Needed)
9.1 Enable Postfix Verbose Logging
bash
cd /opt/mailcow-dockerized echo "smtp unix - - y - - smtp -v" >> data/conf/postfix/extra.cf docker restart mailcowdockerized-postfix-mailcow-1
9.2 Enable Rspamd Debug Logging
bash
docker exec -it mailcowdockerized-rspamd-mailcow-1 bash mkdir -p /etc/rspamd/local.d cat > /etc/rspamd/local.d/logging.inc << 'EOF' level = "debug"; debug_modules = ["greylist", "spf", "dkim", "dmarc", "rbl", "mime", "bayes"]; log_format = "id: <$mid>, from: <$smtp_from>, to: <$smtp_rcpt>, ip: $ip, action: $action"; log_usec = true; EOF exit docker restart mailcowdockerized-rspamd-mailcow-1
Part 10: Quick Setup Script
Save this as setup-mailcow-transport.sh for future server setups:
bash
#!/bin/bash
# Usage: ./setup-mailcow-transport.sh [local-domain] [remote-domain] [remote-private-ip]
LOCAL_DOMAIN=$1
REMOTE_DOMAIN=$2
REMOTE_IP=$3
if [ -z "$LOCAL_DOMAIN" ] || [ -z "$REMOTE_DOMAIN" ] || [ -z "$REMOTE_IP" ]; then
echo "Usage: $0 [local-domain] [remote-domain] [remote-private-ip]"
echo "Example: $0 spectranepal.com samratnepal.com 10.36.4.4"
exit 1
fi
echo "Configuring Postfix transport for $REMOTE_DOMAIN -> $REMOTE_IP"
# Enter Postfix container
docker exec -it mailcowdockerized-postfix-mailcow-1 bash -c "
# Create transport file
echo '$REMOTE_DOMAIN smtp:[$REMOTE_IP]:25' > /etc/postfix/transport
echo 'mail.$REMOTE_DOMAIN smtp:[$REMOTE_IP]:25' >> /etc/postfix/transport
# Process transport map
postmap /etc/postfix/transport
# Add to transport_maps
current_transport=\$(postconf -h transport_maps)
postconf -e \"transport_maps = hash:/etc/postfix/transport, \$current_transport\"
# Reload Postfix
postfix reload
# Test
postmap -q $REMOTE_DOMAIN hash:/etc/postfix/transport
"
echo "Configuration complete!"
Summary Checklist
-
Private IPs identified and tested with ping
-
Port 25 connectivity verified between servers
-
Postfix transport maps configured on both servers
-
Rspamd greylisting disabled for server IPs
-
iptables FORWARD policy set to ACCEPT
-
SKIP_NETFILTER_ISOLATION enabled in mailcow.conf
-
Test emails sent both directions
-
Logs show delivery via private IPs
Important Notes
-
The square brackets
[IP]in transport maps disable MX lookups and force direct delivery -
Transport maps must be processed with
postmapafter any changes -
Always add your custom transport map at the beginning of transport_maps for precedence
-
Whitelist both public and private IPs in Rspamd to bypass greylisting
-
After major changes, restart both Postfix and Rspamd containers
This configuration ensures reliable email delivery between your Mailcow servers using private network communication, bypassing public internet routing issues and reducing latency.
No Comments Yet
Be the first to share your thoughts!