This document covers the complete high availability (HA) Mailcow deployment on two Dell R240 servers with Pacemaker/Corosync clustering and shared NFS storage.
/mnt/ha-shared/mailcow/mailcow-dockerized/
/mnt/ha-shared/mailcow/mailcow-dockerized/mailcow.conf
Key settings:
MAILCOW_HOSTNAME=mail.customstack.nyc
ADDITIONAL_SAN=mail.accentmedspa.com,mail.ipodrepair.nyc,mail.stdftd.com
All critical Mailcow data is stored on NFS for zero-data-loss failover:
/mnt/ha-shared/mailcow/mysql-data/ # MySQL database
/mnt/ha-shared/mailcow/vmail/ # Mail storage (Maildir format)
/mnt/ha-shared/mailcow/mail-crypt/ # Encryption keys (CRITICAL)
/mnt/ha-shared/mailcow/sogo-cache/ # SOGo webmail cache
/mnt/ha-shared/letsencrypt/ # SSL certificates
File: /mnt/ha-shared/mailcow/mailcow-dockerized/docker-compose.override.yml
version: '2.1'
services:
mysql-mailcow:
volumes:
- /mnt/ha-shared/mailcow/mysql-data:/var/lib/mysql
nginx-mailcow:
volumes:
- /mnt/ha-shared/letsencrypt:/etc/letsencrypt:ro
dovecot-mailcow:
volumes:
- /mnt/ha-shared/mailcow/vmail:/var/vmail
- /mnt/ha-shared/mailcow/mail-crypt:/mail_crypt
sogo-mailcow:
volumes:
- /mnt/ha-shared/mailcow/sogo-cache:/var/lib/sogo
Mailcow uses the mail-crypt plugin for encryption at rest. The encryption keys MUST be shared:
# Keys location
/mnt/ha-shared/mailcow/mail-crypt/ecprivkey.pem
/mnt/ha-shared/mailcow/mail-crypt/ecpubkey.pem
# Ownership
chown 999:999 /mnt/ha-shared/mailcow/mail-crypt/
Without shared encryption keys, mail will be unreadable on the failover server!
Location: /etc/systemd/system/ha-mailcow.service
[Unit]
Description=Mailcow Docker Compose Stack (HA)
After=network-online.target docker.service pacemaker.service
Wants=network-online.target
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/mnt/ha-shared/mailcow/mailcow-dockerized
ExecStartPre=/usr/sbin/iptables -t nat -F DOCKER
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=600
Restart=on-failure
[Install]
WantedBy=multi-user.target
Key features:
sudo systemctl daemon-reload
sudo systemctl enable ha-mailcow.service
Mailcow runs as a Pacemaker-managed resource for automatic failover:
# Resource: ha-mailcow
# Type: systemd service
# Failover: Automatic between thing-1 and thing-2
# Monitor interval: 30s
Verify status:
sudo pcs status
Mailcow serves multiple domains with individual DKIM keys:
ipodrepair.nyc
customstack.nyc
stdftd.com (optional/legacy)
All domains use the same mail server hostname:
mail.customstack.nyc
Users connect to mail.customstack.nyc regardless of their email domain.
For each domain (example: accentmedspa.com):
accentmedspa.com. MX 10 mail.customstack.nyc.
accentmedspa.com. TXT "v=spf1 ip4:108.27.135.4 -all"
NOTE: Update IP address based on your current static IP.
dkim._domainkey.accentmedspa.com. TXT "v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjAN..."
Get the public key from: Mailcow Admin → Configuration → Domains → Click domain → DKIM section
_dmarc.accentmedspa.com. TXT "v=DMARC1; p=quarantine; adkim=r; aspf=r; rua=mailto:dmarc@accentmedspa.com"
mail.accentmedspa.com. A 108.27.135.4
mail.ipodrepair.nyc. A 108.27.135.4
mail.customstack.nyc. A 108.27.135.4
mail.stdftd.com. A 108.27.135.4
autodiscover.accentmedspa.com. CNAME mail.customstack.nyc.
autoconfig.accentmedspa.com. CNAME mail.customstack.nyc.
A single Let's Encrypt certificate covers all mail subdomains:
/mnt/ha-shared/mailcow/mailcow-dockerized/data/assets/ssl/cert.pem
/mnt/ha-shared/mailcow/mailcow-dockerized/data/assets/ssl/key.pem
Certificate includes SANs for:
Generated using certbot on the host:
# Stop services
cd /mnt/ha-shared/mailcow/mailcow-dockerized
docker compose down
cd /mnt/ha-shared/web-containers
docker compose down
# Get certificate
sudo certbot certonly --standalone \
-d mail.customstack.nyc \
-d mail.accentmedspa.com \
-d mail.ipodrepair.nyc \
-d mail.stdftd.com
# Copy to Mailcow
sudo cp /etc/letsencrypt/live/mail.customstack.nyc/fullchain.pem \
/mnt/ha-shared/mailcow/mailcow-dockerized/data/assets/ssl/cert.pem
sudo cp /etc/letsencrypt/live/mail.customstack.nyc/privkey.pem \
/mnt/ha-shared/mailcow/mailcow-dockerized/data/assets/ssl/key.pem
sudo chown guernica:guernica /mnt/ha-shared/mailcow/mailcow-dockerized/data/assets/ssl/*.pem
# Restart services
cd /mnt/ha-shared/web-containers
docker compose up -d
cd /mnt/ha-shared/mailcow/mailcow-dockerized
docker compose up -d
Certificates auto-renew via certbot. After renewal, copy to Mailcow and restart:
sudo cp /etc/letsencrypt/live/mail.customstack.nyc/*.pem \
/mnt/ha-shared/mailcow/mailcow-dockerized/data/assets/ssl/
docker compose restart nginx-mailcow dovecot-mailcow postfix-mailcow
https://mail.customstack.nyc/SOGo/so/
https://mail.accentmedspa.com/SOGo/so/
https://mail.ipodrepair.nyc/SOGo/so/
https://mail.customstack.nyc:8443
Default credentials in: /mnt/ha-shared/mailcow/mailcow-dockerized/mailcow.conf
sudo firewall-cmd --permanent --add-service=smtp # 25
sudo firewall-cmd --permanent --add-service=smtps # 465
sudo firewall-cmd --permanent --add-service=imap # 143
sudo firewall-cmd --permanent --add-service=imaps # 993
sudo firewall-cmd --permanent --add-service=pop3 # 110
sudo firewall-cmd --permanent --add-service=pop3s # 995
sudo firewall-cmd --permanent --add-service=http # 80
sudo firewall-cmd --permanent --add-service=https # 443
sudo firewall-cmd --permanent --add-port=587/tcp # Submission
sudo firewall-cmd --permanent --add-port=4190/tcp # Sieve
sudo firewall-cmd --reload
Docker creates its own firewall zone (docker) which handles container traffic. Mail ports are accessible even with firewalld active.
If Mailcow fails to start with "port already in use" errors:
# Flush Docker iptables NAT rules
sudo iptables -t nat -F DOCKER
sudo systemctl restart docker
sleep 10
sudo pcs resource cleanup ha-mailcow
If Dovecot gets stuck "Waiting for DNS...":
docker compose restart unbound-mailcow
sleep 5
docker compose restart dovecot-mailcow
This occurs when Dovecot starts before unbound (internal DNS) is ready.
If you see errors like "Decryption error: no private key available":
Problem: Encryption keys are not shared on NFS
Solution: Ensure /mnt/ha-shared/mailcow/mail-crypt/ is mounted in dovecot container
Verify:
docker exec mailcowdockerized-dovecot-mailcow-1 ls -la /mail_crypt/
# Should show: ecprivkey.pem and ecpubkey.pem
If webmail shows gray placeholder boxes instead of emails:
Cause: Usually indicates vmail data not on NFS or encryption key issues
Solution: Verify vmail and mail-crypt volumes are properly mounted
# Postfix (SMTP)
docker logs mailcowdockerized-postfix-mailcow-1 --tail 100
# Dovecot (IMAP)
docker logs mailcowdockerized-dovecot-mailcow-1 --tail 100
# SOGo (Webmail)
docker logs mailcowdockerized-sogo-mailcow-1 --tail 100
# All containers
docker compose logs -f
# SPF
dig domain.com TXT +short | grep spf
# DKIM
dig dkim._domainkey.domain.com TXT +short
# DMARC
dig _dmarc.domain.com TXT +short
# MX
dig domain.com MX +short
Send test email to: check-auth@verifier.port25.com
You'll receive an automated reply showing:
Issue: Verizon Business static IP (108.27.135.4) is on Spamhaus Policy Block List (PBL)
Impact:
Evidence:
550 5.7.1 Service unavailable, Client host [108.27.135.4] blocked using Spamhaus.
Symptoms:
Root Cause:
Resolution Options:
Request new IP from Verizon (RECOMMENDED)
SMTP Relay via guernica (TEMPORARY)
[192.168.1.166]:25SMTP Relay Service (ALTERNATIVE)
Current PTR:
108.27.135.4 → static-108-27-135-4.nycmny.fios.verizon.net
Problem:
Required PTR:
108.27.135.4 → mail.customstack.nyc
Resolution: Contact Verizon to set proper PTR record (requires business account)
Put thing-1 in standby mode:
sudo pcs node standby thing-1
Verify services move to thing-2:
sudo pcs status
Bring thing-1 back online:
sudo pcs node unstandby thing-1
Pacemaker monitors the ha-mailcow service every 30 seconds. If it fails, automatic failover occurs:
After failover, verify all data is accessible:
# Check NFS mounts
df -h | grep ha-shared
# Verify mail data
ls -la /mnt/ha-shared/mailcow/vmail/
# Check encryption keys
ls -la /mnt/ha-shared/mailcow/mail-crypt/
# Test mail access
# Send/receive test email via webmail
Critical Data (All on NFS):
/mnt/ha-shared/mailcow/mysql-data/ # User accounts, settings
/mnt/ha-shared/mailcow/vmail/ # All email messages
/mnt/ha-shared/mailcow/mail-crypt/ # Encryption keys (CRITICAL!)
/mnt/ha-shared/mailcow/sogo-cache/ # Webmail cache (optional)
Configuration Files:
/mnt/ha-shared/mailcow/mailcow-dockerized/mailcow.conf
/mnt/ha-shared/mailcow/mailcow-dockerized/docker-compose.override.yml
/etc/systemd/system/ha-mailcow.service
# Backup to external storage
sudo rsync -av /mnt/ha-shared/mailcow/ /backup/location/mailcow-$(date +%Y%m%d)/
# Backup encryption keys separately (CRITICAL)
sudo tar czf mailcow-encryption-keys-$(date +%Y%m%d).tar.gz \
-C /mnt/ha-shared/mailcow/mail-crypt/ .
# Store encryption keys backup OFF-SITE
WARNING: Without the encryption keys backup, encrypted mail cannot be recovered!
To restore Mailcow on new servers:
/mnt/ha-sharedsudo pcs resource enable ha-mailcowAll mail and settings will be immediately available.
# On active node
cd /mnt/ha-shared/mailcow/mailcow-dockerized
./update.sh
# Follow prompts
# Containers will be rebuilt and restarted
Mailcow Admin Panel:
DNS Configuration:
SSL Certificate (if needed):
Create Mailboxes:
Check Service Status:
sudo pcs status
sudo systemctl status ha-mailcow
docker compose ps
Check Mail Queue:
docker exec mailcowdockerized-postfix-mailcow-1 postqueue -p
Monitor Logs:
docker compose logs -f --tail=100
All NFS traffic between servers and Synology NAS uses 10GbE networking for optimal performance:
Typical per server:
Admin Panel: https://mail.customstack.nyc:8443
SSH Access:
Firewall:
Transport:
At Rest:
Internet
↓
VIP: 192.168.1.100 (Pacemaker managed)
↓
┌─────────────────┬─────────────────┐
│ thing-1 │ thing-2 │
│ (Active/Backup) │ (Active/Backup) │
│ │ │
│ Mailcow Stack │ Mailcow Stack │
│ 18 Containers │ 18 Containers │
└────────┬────────┴────────┬────────┘
│ │
│ 10GbE Network │
│ │
┌────┴─────────────────┴────┐
│ Synology NAS │
│ 192.168.1.246 │
│ │
│ /mnt/ha-shared/mailcow/ │
│ ├── mysql-data/ │
│ ├── vmail/ │
│ ├── mail-crypt/ │
│ └── sogo-cache/ │
└───────────────────────────┘
Document maintained by: Josh Kenney
Last updated: January 5, 2026
Infrastructure: Dell R240 HA Cluster with Mailcow