Netgrimoire/Keystone-Grimoire/Mail/Hardening.md
2026-04-12 09:53:51 -05:00

16 KiB
Raw Blame History

title description published date tags editor dateCreated
MailCow Hardening Securing Mailcow true 2026-02-23T21:56:32.211Z markdown 2026-02-23T21:56:22.997Z

MailCow Security Hardening

Service: MailCow Dockerized Host: 192.168.5.16 (MailCow_Ngnx alias) Relay: MXRoute (outbound only) Last Reviewed: February 2026


Overview

Running MailCow with MXRoute as an outbound relay creates a specific threat model that's different from either a fully self-hosted or fully managed setup. Your server receives inbound directly (MX points to your IP), stores all mailboxes locally, and hands outbound to MXRoute. This means you carry the risk surface of both — inbound SMTP exposure plus the credential and reputation exposure of a relay relationship.

The security areas that matter most for this setup:

Area Risk Priority
DNS authentication (SPF/DKIM/DMARC) Spoofing, deliverability failure, relay abuse 🔴 Critical
MTA-STS + TLS-RPT SMTP downgrade attacks on inbound 🔴 Critical
MXRoute relay credential security Relay hijacking, spam abuse on your reputation 🔴 Critical
Mailcow admin hardening Account takeover, open relay creation 🔴 Critical
Postfix TLS hardening Weak cipher negotiation 🟡 High
Nginx header hardening XSS, clickjacking on webmail 🟡 High
Rspamd tuning Inbound spam, outbound policy enforcement 🟡 High
DMARC reporting Visibility into spoofing and misdelivery 🟡 High
ClamAV / attachment scanning Malware distribution via your domain 🟢 Medium
Rate limiting Compromised account spam runs 🟢 Medium

DNS Authentication

This is the foundation. If any of these are misconfigured your mail either doesn't deliver or your domain gets spoofed. With MXRoute in the mix the SPF record requires special attention.

SPF — Include Both Sources

Your SPF must authorize both your own IP (for any direct sends) and MXRoute's sending infrastructure:

@ IN TXT "v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com ~all"

Replace YOUR_ATT_MAIL_IP with the static IP you've dedicated to mail (ATT_Mail virtual IP). The include:mxroute.com covers MXRoute's sending servers.

⚠ Do not use -all (hard fail) until you have confirmed all your sending sources are covered. Use ~all (softfail) initially, then tighten after verifying DMARC reports show no legitimate sources failing.

⚠ SPF has a 10 DNS lookup limit. Each include: costs lookups. If you add more includes (e.g. transactional services), check your SPF lookup count at mxtoolbox.com/spf.

DKIM — Two Selectors for Two Signers

Because MXRoute re-signs outbound mail with their own DKIM key, you need a DKIM record for both signers:

Selector Signer Where to get the key
mailcow._domainkey MailCow (inbound, internal sends) MailCow UI → Configuration → ARC/DKIM Keys
mxroute._domainkey (or x._domainkey) MXRoute (outbound relay) MXRoute control panel

Add both as TXT records. Having both means DMARC passes regardless of which path the mail took.

✓ MailCow lets you choose the DKIM selector name. Use mailcow as the selector to avoid confusion with the MXRoute selector.

DMARC — Start Monitoring, Then Enforce

DMARC ties SPF and DKIM together and tells receiving servers what to do with failures. Start in monitoring mode, review reports for 24 weeks, then advance to enforcement.

Phase 1 — Monitor (add immediately):

_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; ruf=mailto:dmarc-failures@yourdomain.com; fo=1"

Phase 2 — Quarantine (after reviewing reports, no legitimate failures):

_dmarc IN TXT "v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc-reports@yourdomain.com; fo=1"

Phase 3 — Reject (final enforcement):

_dmarc IN TXT "v=DMARC1; p=reject; pct=100; rua=mailto:dmarc-reports@yourdomain.com; fo=1"

fo=1 requests forensic reports on any authentication failure — more detail for debugging.

DMARC Report Processing: Raw DMARC reports are XML and not human-readable. Use one of these free tools to process them:


MTA-STS (MailCow September 2025+)

MTA-STS forces other mail servers to use TLS when delivering to you, preventing downgrade attacks that try to force plaintext SMTP. The September 2025 MailCow update added the postfix-tlspol-mailcow container which enforces MTA-STS on outbound connections too.

What You Need

1. DNS records — three records for each domain:

# For your mail server's hostname domain (e.g. netgrimoire.com)
mta-sts       IN CNAME   mail.netgrimoire.com.
_mta-sts      IN TXT     "v=STSv1; id=20260223"
_smtp._tls    IN TXT     "v=TLSRPTv1; rua=mailto:tls-reports@netgrimoire.com"

The id value in _mta-sts is a version string — update it (e.g. to today's date) whenever you change your MTA-STS policy.

2. Policy file — served by MailCow's nginx at https://mta-sts.yourdomain.com/.well-known/mta-sts.txt:

# On your MailCow host:
mkdir -p /opt/mailcow-dockerized/data/web/.well-known/
cat > /opt/mailcow-dockerized/data/web/.well-known/mta-sts.txt << 'EOF'
version: STSv1
mode: enforce
max_age: 86400
mx: mail.netgrimoire.com
EOF

Start with mode: testing for the first week, then switch to mode: enforce.

3. For additional domains — add CNAMEs pointing to your primary domain's records:

# For each additional mail domain you host on MailCow:
mta-sts.otherdomain.com    IN CNAME   mail.netgrimoire.com.
_mta-sts.otherdomain.com   IN CNAME   _mta-sts.netgrimoire.com.
_smtp._tls.otherdomain.com IN CNAME   _smtp._tls.netgrimoire.com.

✓ TLS-RPT (_smtp._tls TXT record) sends you reports about TLS failures when other servers connect to you. Pipe these to Graylog or Postmark for visibility.


MXRoute Relay Security

This is the most overlooked area. Your MXRoute credentials can send mail as your domain — if they're compromised, someone else is spamming from your reputation.

Credential Hardening

  • Use a unique, strong password for your MXRoute account — not shared with anything else
  • Store the MXRoute SMTP credentials in MailCow's relay configuration only, not in any config file or environment variable that gets committed to git
  • If MXRoute supports API tokens or app passwords, use those instead of your main account password

Relay Configuration in MailCow

In MailCow UI: Configuration → Routing → Sender-Dependent Transports

Verify the relay is configured to authenticate via TLS (port 587 with STARTTLS or port 465 with SSL). Do not relay over port 25 without authentication.

# What the relay entry should look like in Postfix terms:
# relayhost = [smtp.mxroute.com]:587
# smtp_sasl_auth_enable = yes
# smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
# smtp_tls_security_level = encrypt   ← ensures TLS is required, not optional

⚠ Set smtp_tls_security_level = encrypt (not may) so the connection to MXRoute is always encrypted. If the TLS negotiation fails, Postfix should reject rather than fall back to plaintext.

Rate Limiting (Prevent Relay Abuse if Account Compromised)

Add rate limits in MailCow UI: Configuration → Mail Setup → Domains → [your domain] → Rate Limit

Setting Recommended Value Notes
Outbound messages/hour 500 Adjust for your actual sending volume
Outbound messages/day 2000 A sudden spike above this = red flag

This doesn't stop abuse but limits blast radius if a mailbox is compromised and starts spamming through MXRoute.


MailCow Admin Hardening

Two-Factor Authentication

Enable 2FA on the admin account and all mailbox accounts that have access to the admin panel.

MailCow UI: Edit mailbox → Two-Factor Authentication → TOTP

⚠ There was a session fixation vulnerability in the MailCow web panel (GHSA-23c8-4wwr-g3c6, January 2025) and a critical SSTI vulnerability (GHSA-8p7g-6cjj-wr9m, July 2025). Both require staying current on updates. Enable auto-updates or check the MailCow blog monthly.

Restrict Admin UI to Internal Network

The MailCow admin panel should not be reachable from the public internet. Access should require being on your internal network or connected via WireGuard.

In OPNsense, add a firewall rule blocking external access to port 443 on 192.168.5.16 except from your static admin IP or WireGuard peers.

Alternatively, configure MailCow's nginx to restrict the admin path by IP:

# In data/conf/nginx/includes/site-defaults.conf
# Add inside the server block for the admin panel:
location /admin {
    allow 192.168.3.0/24;
    allow 192.168.5.0/24;
    allow 192.168.32.0/24;  # WireGuard peers
    deny all;
}

API Key Rotation

If you use the MailCow API (for automation or Netgrimoire tooling), generate a dedicated read-only key where possible, and rotate keys annually or after any suspected compromise.


Postfix TLS Hardening

Add to /opt/mailcow-dockerized/data/conf/postfix/extra.cf:

# Enforce TLS 1.2+ and strong ciphers
tls_high_cipherlist = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
tls_preempt_cipherlist = yes

# Inbound SMTP (smtpd) — receiving from other mail servers
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_ciphers = high
smtpd_tls_mandatory_ciphers = high

# Outbound SMTP (smtp) — delivery to MXRoute and direct sends
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_ciphers = high
smtp_tls_mandatory_ciphers = high

# Require encryption on the MXRoute relay connection
smtp_tls_security_level = encrypt

After editing, restart Postfix:

cd /opt/mailcow-dockerized
docker compose restart postfix-mailcow

Nginx Header Hardening

Add to /opt/mailcow-dockerized/data/conf/nginx/includes/site-defaults.conf:

# Strong SSL ciphers only
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_conf_command Options PrioritizeChaCha;

# HSTS — include subdomains if all your services use HTTPS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";

# Disable X-XSS-Protection (deprecated, CSP replaces it)
add_header X-XSS-Protection "0";

# Deny unused browser permissions
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";

# Content Security Policy — if NOT using Gravatar with SOGo
add_header Content-Security-Policy "default-src 'none'; connect-src 'self' https://api.github.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content; base-uri 'none'";

# Cross-origin isolation headers
add_header Cross-Origin-Resource-Policy same-origin;
add_header Cross-Origin-Opener-Policy same-origin;
add_header Cross-Origin-Embedder-Policy require-corp;

# Disable gzip to prevent BREACH attack
# Change gzip on; → gzip off; in the main nginx conf

⚠ The December 2025 MailCow update already removed the deprecated X-XSS-Protection header from defaults. If you're current, you may already have this. Check before duplicating.

After editing, restart nginx:

docker compose restart nginx-mailcow

Rspamd Tuning

Rspamd is MailCow's spam filter. The defaults are reasonable but a few adjustments improve both inbound protection and outbound policy enforcement.

Key Settings to Review

Navigate to MailCow UI → Configuration → Rspamd UI (or directly at https://mail.yourdomain.com/rspamd/)

Actions → Score Thresholds:

Action Default Recommended
Greylist 4 3
Add header 6 5
Reject 15 12

Lowering the reject threshold from 15 to 12 catches more aggressive spam while avoiding false positives.

Modules to enable/verify:

Module Purpose
DKIM verification Verify incoming DKIM signatures
SPF Verify incoming SPF
DMARC Enforce DMARC on inbound
MX Check Verify sending domain has a valid MX
RBL (Realtime Blacklists) Check sending IPs against blocklists
Greylisting Temporary reject new senders (forces retry)

Add CrowdSec as an Rspamd Feed

If you also have the CrowdSec bouncer running on the MailCow host (or can reach it), you can feed CrowdSec decisions into Rspamd to reject mail from banned IPs. This is advanced but powerful — see the CrowdSec Bouncer for Rspamd hub entry.


Deliverability Verification

Run these checks after making any DNS or config changes:

Tool What It Checks URL
MXToolbox SPF, DKIM, DMARC, MX, PTR, blacklists mxtoolbox.com
mail-tester.com Send a test email, get a 110 score mail-tester.com
Port25 verifier Send to check-auth@verifier.port25.com Email-based
DKIM validator Validates DKIM signature dkimvalidator.com
Google Postmaster Tools Gmail reputation monitoring (requires setup) postmaster.google.com
Microsoft SNDS Outlook/Hotmail reputation sendersupport.olc.protection.outlook.com

✓ Aim for 910/10 on mail-tester.com. Anything below 8 indicates a misconfiguration that will hurt deliverability.


Keeping MailCow Updated

MailCow has had several critical security vulnerabilities in 2025 (session fixation, SSTI, password reset poisoning). Staying current is non-negotiable.

cd /opt/mailcow-dockerized

# Pull latest images
docker compose pull

# Apply update
./update.sh

# Or if using the newer helper:
docker compose up -d

✓ Subscribe to the MailCow blog or watch the GitHub releases for security advisories. The update cadence is roughly monthly.

Set up a cron job or Monit check to alert you when MailCow is more than 30 days behind the latest release.


Checklist Summary

Item Status
SPF includes both own IP and mxroute.com
Two DKIM selectors (mailcow + mxroute)
DMARC in monitoring mode, advancing to reject
DMARC reports being processed (Postmark/dmarcian)
MTA-STS policy published and enforced
TLS-RPT record in DNS
MXRoute relay connection uses TLS/encrypt level
Admin UI restricted to internal network
2FA on admin and all privileged accounts
Postfix TLS 1.2+ enforced via extra.cf
Nginx security headers added
Rate limits set on outbound per-domain
MailCow updated to latest (monthly check)
Rspamd thresholds reviewed
PTR/rDNS record matches mail hostname