New Grimoire

This commit is contained in:
traveler 2026-04-12 09:53:51 -05:00
parent 77d589a13d
commit cc574f8aed
157 changed files with 29420 additions and 0 deletions

View file

@ -0,0 +1,401 @@
---
title: Sample Domain Setup
description: Graymutt@nucking-futz.com
published: true
date: 2026-03-16T00:34:08.387Z
tags:
editor: markdown
dateCreated: 2026-02-25T22:02:27.719Z
---
# Mail Setup — nucking-futz.com
## Part 0 — OPNsense: Configure ATT_Mail Secondary IP
Before configuring DNS or Mailcow, the secondary AT&T static IP must be configured in OPNsense as a virtual IP on the WAN interface and NAT rules must be set so only raw SMTP traffic (ports 25, 465, 587, 993, 143) uses this address. Webmail, the Mailcow admin UI, and all other traffic continue to use the primary WAN IP (107.133.34.145).
| Address | Purpose |
|---------|---------|
| 107.133.34.145 | Primary WAN — web, admin, everything else |
| 107.133.34.146 | ATT_Mail — SMTP/IMAP inbound and outbound only |
### Step 0.1 — Add Virtual IP
1. Go to **Interfaces → Virtual IPs → Settings**
2. Click **+ Add**
3. Set the following:
| Field | Value |
|-------|-------|
| Mode | IP Alias |
| Interface | WAN (igc1) |
| Network / Address | `107.133.34.146 / 28` |
| Description | `ATT_Mail` |
4. Click **Save**, then **Apply changes**
> The /28 subnet mask matches the AT&T block (107.133.34.144/28). All 5 static IPs in the block share this mask.
### Step 0.2 — Outbound NAT for SMTP Traffic
This ensures Mailcow's outbound SMTP connections leave through the ATT_Mail IP rather than the primary WAN IP. OPNsense must be in **Hybrid** or **Manual** outbound NAT mode.
1. Go to **Firewall → NAT → Outbound**
2. Confirm mode is set to **Hybrid Outbound NAT** (or Manual — either works)
3. Click **Add** to create a new rule
**Rule for outbound SMTP (port 587 relay to MXRoute):**
| Field | Value |
|-------|-------|
| Interface | WAN |
| TCP/IP Version | IPv4 |
| Protocol | TCP |
| Source | `192.168.5.16 / 32` (Mailcow host) |
| Source Port | any |
| Destination | any |
| Destination Port | 587 |
| Translation / Target | `107.133.34.146` (ATT_Mail) |
| Description | `Mailcow outbound relay via ATT_Mail` |
4. Repeat for port **25** (direct outbound SMTP, if used) and port **465** (SMTPS)
5. Click **Save** and **Apply changes**
### Step 0.3 — Inbound NAT (Port Forwards) for Mail Ports
Route inbound connections on mail ports to Mailcow using the ATT_Mail IP as the external address.
1. Go to **Firewall → NAT → Port Forward**
2. Create rules for each mail port:
| External IP | Port(s) | Forward to | Description |
|-------------|---------|-----------|-------------|
| 107.133.34.146 | 25 | 192.168.5.16:25 | SMTP inbound |
| 107.133.34.146 | 465 | 192.168.5.16:465 | SMTPS inbound |
| 107.133.34.146 | 587 | 192.168.5.16:587 | Submission inbound |
| 107.133.34.146 | 993 | 192.168.5.16:993 | IMAPS |
| 107.133.34.146 | 143 | 192.168.5.16:143 | IMAP (if needed) |
> **Do not** add port forwards for 80, 443, or 3443 (Mailcow admin/webmail ports) on this IP. Those remain on the primary WAN IP via Caddy.
3. Click **Save** and **Apply changes**
### Step 0.4 — Firewall Rules
Ensure the WAN firewall rules permit inbound traffic on the mail ports to the ATT_Mail IP. If you have a default deny-all WAN rule (recommended), add explicit pass rules:
1. Go to **Firewall → Rules → WAN**
2. Add pass rules for each port in the table above with destination `107.133.34.146`
### Step 0.5 — Verify
```bash
# From outside your network, confirm the mail IP is live
telnet 107.133.34.146 25
# Should see: 220 hermes.netgrimoire.com ESMTP
# Confirm primary WAN IP does NOT respond on port 25
telnet 107.133.34.145 25
# Should time out or be refused
# Check that Mailcow outbound connections leave from the ATT_Mail IP
# Send a test to check-auth@verifier.port25.com and inspect the Return-Path
# or check the Received: header — the sending IP should be 107.133.34.146
```
> ⚠ If the verify step shows port 25 still responding on 107.133.34.145, check that no leftover port forward rules exist on the primary WAN IP for mail ports.
---
## Overview
This guide covers complete mail setup for `nucking-futz.com` using MXRoute as the inbound gateway and Mailcow as the mailbox host. MXRoute receives all inbound mail from the internet (solving residential IP filtering issues with banks and financial institutions) and forwards to Mailcow for storage and retrieval. Mailcow handles outbound mail via the MXRoute SMTP relay.
**Architecture:**
```
Inbound: Internet → MXRoute (commercial IP) → Mailcow (192.168.5.16)
Outbound: Mailcow → MXRoute SMTP relay → Internet
```
**Why two domains in Mailcow:**
MXRoute forwarders require a valid destination email address. You cannot forward `graymutt@nucking-futz.com` back to `graymutt@nucking-futz.com` — that loops. The solution is to have Mailcow own a subdomain (`mail.nucking-futz.com`) with its own MX record pointing directly to your server. MXRoute forwards to `graymutt@mail.nucking-futz.com`, Mailcow delivers locally, and an alias domain maps `nucking-futz.com` back so users only ever see and use `graymutt@nucking-futz.com`.
---
## Prerequisites
- MXRoute account active with DirectAdmin access
- Mailcow running at 192.168.5.16
- DNS management access for nucking-futz.com
- Your MXRoute server hostname from your MXRoute welcome email (e.g. `arrow.mxrouting.net`)
---
## Step 1 — DNS Records
Create all DNS records before configuring either service. Keep TTL at 300 during setup — raise to 3600 once confirmed working.
![image.png](/image.png)
![arec.png](/email/arec.png)
![txt.png](/email/txt.png)
### Required DNS Records
| Type | Host | Value | Notes |
|------|------|-------|-------|
| A | `mail` | `YOUR_ATT_MAIL_IP` | Points to Mailcow — MXRoute forwards to this server |
| MX | `@` | `heracles.mxrouting.net (Priority 10)` | Check MXRoute welcome email for exact hostname |
| MX | `@` | `heracles-relay.mxrouting.net (Priority 20)` (priority 20) | Secondary MXRoute server from welcome email |
| MX | `mail` | `mail.nucking-futz.com` (priority 10) | Mailcow handles this subdomain directly |
| CNAME | `imap` | `mail.nucking-futz.com` | Client autoconfiguration |
| CNAME | `smtp` | `mail.nucking-futz.com` | Client autoconfiguration |
| CNAME | `webmail` | `mail.nucking-futz.com` | Roundcube access |
| CNAME | `autodiscover` | `mail.nucking-futz.com` | Outlook autodiscover |
| CNAME | `autoconfig` | `mail.nucking-futz.com` | Thunderbird autoconfig |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` | SPF — authorizes both Mailcow direct and MXRoute relay |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` | SPF for subdomain — Mailcow sends directly from here |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` | DMARC enforcement |
> DKIM TXT records (two selectors) are added in Steps 2 and 3 after generating keys in Mailcow and MXRoute.
---
## Step 2 — Mailcow Configuration
### 2.1 Add the Subdomain as Primary Domain
Mailcow owns `mail.nucking-futz.com` as its active mail domain. Mailboxes live internally on this subdomain.
1. Log into Mailcow admin UI → **Mail Setup → Domains**
2. Click **Add domain**
3. Set **Domain:** `mail.nucking-futz.com`
4. Leave all other settings as default
5. Click **Add domain**
### 2.2 Add the Alias Domain
This makes Mailcow accept mail addressed to `@nucking-futz.com` and deliver it to the matching `@mail.nucking-futz.com` mailbox. Users send and receive as `@nucking-futz.com` — the subdomain is invisible to them.
1. Go to **Mail Setup → Alias Domains**
2. Click **Add alias domain**
3. Set **Alias Domain:** `nucking-futz.com`
4. Set **Target Domain:** `mail.nucking-futz.com`
5. Click **Add**
### 2.3 Create Mailbox
1. Go to **Mail Setup → Mailboxes**
2. Click **Add mailbox**
3. Set **Username:** `graymutt`
4. Set **Domain:** `mail.nucking-futz.com`
5. Set a strong password
6. Set quota as needed
7. Click **Add**
The mailbox is internally `graymutt@mail.nucking-futz.com`. The alias domain from Step 2.2 means Mailcow also accepts and delivers mail for `graymutt@nucking-futz.com` to this same mailbox.
### 2.4 Generate DKIM Key
1. Go to **Configuration → Configuration & Diagnostics → Configuration**
2. Click **ARC/DKIM Keys** tab
3. Select domain `mail.nucking-futz.com`
4. Set **Selector:** `mailcow`
5. Set **Key length:** 2048
6. Click **Generate**
7. Copy the full TXT record value — needed for DNS
### 2.5 Add Mailcow DKIM DNS Record
| Type | Host | Value |
|------|------|-------|
| TXT | `mailcow._domainkey.mail` | *(full key string from Mailcow — begins with `v=DKIM1;`)* |
### 2.6 Add MXRoute to Trusted Networks
Prevents Mailcow from applying spam scoring to forwarded mail arriving from MXRoute's IPs.
1. Go to **Configuration → Configuration & Diagnostics → Configuration**
2. Click **Extra Postfix configuration** tab
3. Add to `extra.cf`:
```
# Trust MXRoute forwarding IPs
mynetworks = 127.0.0.1/8 [::1]/128 192.168.5.0/24 69.167.160.0/19 198.54.120.0/22
```
> Verify current MXRoute IP ranges in your MXRoute account documentation — these may change.
4. Click **Save**
5. Click **Restart affected containers**
### 2.7 Configure Outbound Relay
Routes outbound mail through MXRoute for best deliverability.
1. Go to **Configuration → Routing → Sender-Dependent Transports**
2. Click **Add transport**
3. Set **Domain:** `nucking-futz.com`
4. Set **Relay host:** `[smtp.mxroute.com]:587` (confirm SMTP hostname from MXRoute welcome email)
5. Set **Username:** your MXRoute relay username
6. Set **Password:** your MXRoute relay password
7. Click **Add**
8. Repeat for domain `mail.nucking-futz.com` using the same relay credentials
---
## Step 3 — MXRoute Configuration
### 3.1 Add Domain in DirectAdmin
1. Log into MXRoute DirectAdmin
2. Go to **Account Manager → Domain Setup**
3. Add domain: `nucking-futz.com`
4. Complete the domain wizard
### 3.2 Create Forwarder
MXRoute does not support domain-level remote MX routing — forwarders must be created per address. The destination must be on a domain whose MX resolves to Mailcow, not back to MXRoute.
1. Go to **Forwarders** in the MXRoute control panel
2. Click **Create New Forwarder**
3. Set **Forwarder Name:** `graymutt` (the `@nucking-futz.com` part is shown automatically)
4. Set **Destination Type:** `Forward to Email(s)`
5. Set **Recipients:** `graymutt@mail.nucking-futz.com`
6. Click **Create Forwarder**
> Every new mailbox requires a matching forwarder entry. The pattern is always `user@nucking-futz.com``user@mail.nucking-futz.com`. See the Adding a New Mailbox section below.
### 3.3 Get MXRoute DKIM Key
1. Go to **Email Manager → DKIM Keys** for `nucking-futz.com`
2. Generate or view the DKIM key — note the selector name assigned (often `x`)
3. Copy the full TXT record value
### 3.4 Add MXRoute DKIM DNS Record
| Type | Host | Value |
|------|------|-------|
| TXT | `x._domainkey` *(replace `x` with MXRoute's actual selector)* | *(full key string from MXRoute DirectAdmin)* |
---
## Step 4 — Verify DNS
Once DNS has propagated, verify all records:
```bash
# MX for main domain — should show MXRoute servers
dig MX nucking-futz.com +short
# MX for subdomain — should show mail.nucking-futz.com
dig MX mail.nucking-futz.com +short
# A record — should show your ATT IP
dig A mail.nucking-futz.com +short
# SPF
dig TXT nucking-futz.com +short
dig TXT mail.nucking-futz.com +short
# DMARC
dig TXT _dmarc.nucking-futz.com +short
# DKIM — Mailcow
dig TXT mailcow._domainkey.mail.nucking-futz.com +short
# DKIM — MXRoute (replace x with your selector)
dig TXT x._domainkey.nucking-futz.com +short
```
Run a full check at [https://mxtoolbox.com](https://mxtoolbox.com) → Email Health for `nucking-futz.com`.
---
## Step 5 — Test Mail Flow
### Inbound Test
Send a test email to `graymutt@nucking-futz.com` from an external Gmail or Outlook account. Verify:
- Mail arrives in the Mailcow mailbox
- Headers show the MXRoute → Mailcow forwarding path (two `Received:` hops)
- No spam flagging
In Roundcube open the test message → **More → View Source** and check the `Received:` chain.
### Outbound Test
Send from `graymutt@nucking-futz.com` to an external Gmail address. Run through [https://mail-tester.com](https://mail-tester.com) for a full delivery score.
### DKIM/SPF/DMARC Test
Send a test to `check-auth@verifier.port25.com` — you will receive an automated reply confirming pass/fail for SPF, DKIM, and DMARC.
### Bank/Financial Test
Send from a bank address to `graymutt@nucking-futz.com` and confirm delivery. This is the primary goal — banks see MXRoute's commercial IPs in the MX record, not your residential AT&T IP.
---
## Email Client Settings
| Setting | Value |
|---------|-------|
| Email address | `graymutt@nucking-futz.com` |
| IMAP server | `mail.nucking-futz.com` |
| IMAP port | `993` (SSL/TLS) |
| SMTP server | `mail.nucking-futz.com` |
| SMTP port | `465` (SSL/TLS) |
| Username | `graymutt@nucking-futz.com` |
| Password | *(mailbox password set in Step 2.3)* |
> Users log in and send as `graymutt@nucking-futz.com`. Mailcow resolves this to the internal `mail.nucking-futz.com` mailbox transparently via the alias domain.
---
## Adding a New Mailbox
Every new address on `nucking-futz.com` requires entries in both Mailcow and MXRoute.
**In Mailcow:**
1. Mail Setup → Mailboxes → Add mailbox
2. Username: `newuser`, Domain: `mail.nucking-futz.com`
**In MXRoute control panel:**
1. Forwarders → Create New Forwarder
2. Forwarder Name: `newuser`, Destination Type: `Forward to Email(s)`, Recipients: `newuser@mail.nucking-futz.com`
---
## Credentials Reference
| Service | Account | Password |
|---------|---------|----------|
| Mailcow mailbox | `graymutt@mail.nucking-futz.com` | *(set during mailbox creation)* |
| MXRoute relay | *(from MXRoute welcome email)* | *(from MXRoute welcome email)* |
| MXRoute DirectAdmin | *(from MXRoute welcome email)* | *(from MXRoute welcome email)* |
---
## Known Gotchas
**Forwarder destination must not loop.** Never set the MXRoute forwarder destination to an address on the same domain that has MXRoute as its MX. `graymutt@nucking-futz.com``graymutt@nucking-futz.com` will loop. Always forward to `@mail.nucking-futz.com` which has its own MX resolving directly to Mailcow.
**Two DKIM selectors required.** `mailcow._domainkey.mail.nucking-futz.com` covers mail Mailcow sends directly from the subdomain. `x._domainkey.nucking-futz.com` (MXRoute selector) covers outbound mail relayed through MXRoute. Both must exist for DMARC to pass on all paths.
**New mailboxes need matching MXRoute forwarders.** MXRoute has no catch-all forwarding to remote servers. Every address that needs to receive mail must have an explicit forwarder in DirectAdmin. Add the MXRoute forwarder step to your mailbox creation checklist.
**Alias domain vs. alias mailbox.** The alias domain in Step 2.2 maps the entire `nucking-futz.com` domain to `mail.nucking-futz.com`. Do not also create individual alias mailboxes for the same addresses — this creates duplicate delivery and may cause unexpected behavior.
**SPF differs between the two domains.** The main domain SPF includes `include:mxroute.com` because MXRoute relay sends outbound from there. The subdomain SPF (`mail.nucking-futz.com`) only needs your ATT IP — Mailcow sends directly from that domain without going through MXRoute. Two different records for two different send paths.
---
## Related Documentation
- [MailCow Configuration](./mailcow)
- [MXRoute Outbound Relay Setup](./mxroute-outbound-relay)
- [OPNsense Firewall](./opnsense-firewall) — static IP allocation for ATT_Mail

View file

@ -0,0 +1,391 @@
---
title: MailCow Hardening
description: Securing Mailcow
published: true
date: 2026-02-23T21:56:32.211Z
tags:
editor: markdown
dateCreated: 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:
```dns
@ 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](https://mxtoolbox.com/spf.aspx).
### 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):**
```dns
_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):**
```dns
_dmarc IN TXT "v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc-reports@yourdomain.com; fo=1"
```
**Phase 3 — Reject (final enforcement):**
```dns
_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:
- [Postmark DMARC](https://dmarc.postmarkapp.com/) — free, email-based weekly digest
- [dmarcian.com](https://dmarcian.com) — free tier, dashboard view
- Self-hosted: [Parsedmarc](https://github.com/domainaware/parsedmarc) → send to Graylog/Grafana
---
## 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:
```dns
# 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`:
```bash
# 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:
```dns
# 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:
```nginx
# 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`:
```ini
# 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:
```bash
cd /opt/mailcow-dockerized
docker compose restart postfix-mailcow
```
---
## Nginx Header Hardening
Add to `/opt/mailcow-dockerized/data/conf/nginx/includes/site-defaults.conf`:
```nginx
# 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:
```bash
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](https://hub.crowdsec.net) 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.
```bash
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](https://mailcow.email/posts/) or watch the [GitHub releases](https://github.com/mailcow/mailcow-dockerized/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 | ☐ |
---
## Related Documentation
- [OPNsense Firewall](./opnsense-firewall) — dedicated ATT_Mail virtual IP, port NAT
- [CrowdSec](./crowdsec) — IP reputation blocking at firewall level
- [Graylog](./graylog) — DMARC report and TLS-RPT ingestion target
- [Caddy Reverse Proxy](./caddy-reverse-proxy) — if MailCow webmail is proxied through Caddy

View file

@ -0,0 +1,490 @@
---
title: Mailcow Dockerized Install and Config
description:
published: true
date: 2026-02-25T21:05:48.256Z
tags:
editor: markdown
dateCreated: 2026-02-25T21:05:38.864Z
---
# MailCow — Installation & Configuration
**Host:** docker4 (192.168.5.16)
**Hostname:** hermes.netgrimoire.com
**Admin URL:** https://mail.netgrimoire.com
**Version:** 2025-10a (update 2026-01 available as of documentation date)
**Installed:** /opt/mailcow-dockerized
**Timezone:** America/Chicago
**Architecture:** x86_64
**CPU:** 16 cores
**RAM:** 30.63 GB
**Disk:** /dev/nvme0n1p2 — 442G / 502G used (93% — monitor this)
---
## Overview
Mailcow runs as a Docker stack on docker4, attached to the `netgrimoire` overlay network. All containers use `restart: unless-stopped` via a compose override. Outbound mail routes through MXRoute via sender-dependent transports. Inbound mail arrives from MXRoute which acts as the public-facing inbound gateway (solving residential AT&T IP filtering issues with banks).
See [MXRoute Master Configuration](./mxroute-master) for full inbound/outbound/DNS detail per domain.
---
## Installation Paths
| Path | Purpose |
|------|---------|
| `/opt/mailcow-dockerized/` | Mailcow root |
| `/opt/mailcow-dockerized/mailcow.conf` | Primary configuration file |
| `/opt/mailcow-dockerized/docker-compose.yml` | Base compose (do not edit) |
| `/opt/mailcow-dockerized/docker-compose.override.yml` | Local overrides — network and restart policy |
| `/opt/mailcow-dockerized/data/conf/postfix/extra.cf` | Persistent Postfix overrides |
| `/opt/mailcow-dockerized/data/conf/postfix/main.cf` | Postfix base config (managed by Mailcow) |
| `/opt/mailcow-dockerized/data/conf/rspamd/` | Rspamd configuration |
| `/opt/mailcow-dockerized/data/assets/ssl/` | TLS certificates |
---
## mailcow.conf — Key Settings
```ini
MAILCOW_HOSTNAME=hermes.netgrimoire.com
MAILCOW_PASS_SCHEME=BLF-CRYPT
# Database
DBNAME=mailcow
DBUSER=mailcow
DBPASS=mg7Z8W9UsPlOh0S6vF7TmmPb6n1s
DBROOT=JdymsZFFACHkDcOdziQ53QruCTG2
# Redis
REDISPASS=6AduWQsmBYGMKfOi1CNEGQfTE3RH
# Ports — HTTPS runs on 3443, proxied through Caddy
HTTP_PORT=80
HTTP_BIND=
HTTPS_PORT=3443
HTTPS_BIND=
HTTP_REDIRECT=n
# Mail ports (standard)
SMTP_PORT=25
SMTPS_PORT=465
SUBMISSION_PORT=587
IMAP_PORT=143
IMAPS_PORT=993
POP_PORT=110
POPS_PORT=995
SIEVE_PORT=4190
# Internal ports (localhost only)
DOVEADM_PORT=127.0.0.1:19991
SQL_PORT=127.0.0.1:13306
REDIS_PORT=127.0.0.1:7654
# TLS cert coverage
ADDITIONAL_SAN=smtp.*,imap.*
AUTODISCOVER_SAN=y
# ACME / Let's Encrypt
SKIP_LETS_ENCRYPT=n
SKIP_IP_CHECK=y
SKIP_HTTP_VERIFICATION=y
# Services — all enabled
SKIP_CLAMD=n
SKIP_OLEFY=n
SKIP_SOGO=n
SKIP_FTS=n
# FTS (Flatcurve/Xapian)
FTS_HEAP=128
FTS_PROCS=1
# Watchdog
USE_WATCHDOG=y
WATCHDOG_NOTIFY_START=y
WATCHDOG_NOTIFY_BAN=n
WATCHDOG_EXTERNAL_CHECKS=n
# Networking
IPV4_NETWORK=172.22.1
IPV6_NETWORK=fd4d:6169:6c63:6f77::/64
ENABLE_IPV6=false
# Misc
MAILDIR_GC_TIME=7200
MAILDIR_SUB=Maildir
SOGO_EXPIRE_SESSION=480
SOGO_URL_ENCRYPTION_KEY=ojmPfhnM4MYMsA2f
ACL_ANYONE=disallow
ALLOW_ADMIN_EMAIL_LOGIN=n
DOCKER_COMPOSE_VERSION=native
COMPOSE_PROJECT_NAME=mailcow
LOG_LINES=9999
```
---
## docker-compose.override.yml
All services are attached to the external `netgrimoire` overlay network and set to `restart: unless-stopped`. The override does not change any image versions or environment variables — it only adds network membership and restart policy.
```yaml
services:
unbound-mailcow:
networks:
netgrimoire:
restart: unless-stopped
mysql-mailcow:
networks:
- netgrimoire
restart: unless-stopped
redis-mailcow:
networks:
- netgrimoire
restart: unless-stopped
clamd-mailcow:
networks:
- netgrimoire
restart: unless-stopped
rspamd-mailcow:
networks:
- netgrimoire
restart: unless-stopped
php-fpm-mailcow:
networks:
- netgrimoire
restart: unless-stopped
sogo-mailcow:
networks:
- netgrimoire
restart: unless-stopped
dovecot-mailcow:
networks:
- netgrimoire
restart: unless-stopped
postfix-mailcow:
networks:
- netgrimoire
restart: unless-stopped
postfix-tlspol-mailcow:
networks:
- netgrimoire
restart: unless-stopped
memcached-mailcow:
restart: unless-stopped
nginx-mailcow:
networks:
- netgrimoire
restart: unless-stopped
acme-mailcow:
networks:
- netgrimoire
restart: unless-stopped
watchdog-mailcow:
networks:
- netgrimoire
restart: unless-stopped
dockerapi-mailcow:
networks:
- netgrimoire
restart: unless-stopped
olefy-mailcow:
networks:
- netgrimoire
restart: unless-stopped
ofelia-mailcow:
networks:
- netgrimoire
restart: unless-stopped
networks:
netgrimoire:
external: true
driver: overlay
```
---
## Container Image Versions
From `docker-compose.yml` (base file — version 2025-10a):
| Service | Image |
|---------|-------|
| unbound-mailcow | ghcr.io/mailcow/unbound:1.24 |
| mysql-mailcow | mariadb:10.11 |
| redis-mailcow | redis:7.4.6-alpine |
| clamd-mailcow | ghcr.io/mailcow/clamd:1.71 |
| rspamd-mailcow | ghcr.io/mailcow/rspamd:2.4 |
| php-fpm-mailcow | ghcr.io/mailcow/phpfpm:1.94 |
| sogo-mailcow | ghcr.io/mailcow/sogo:1.136 |
| dovecot-mailcow | ghcr.io/mailcow/dovecot:2.35 |
| postfix-mailcow | ghcr.io/mailcow/postfix:1.81 |
| postfix-tlspol-mailcow | ghcr.io/mailcow/postfix-tlspol:1.0 |
| memcached-mailcow | memcached:alpine |
| nginx-mailcow | ghcr.io/mailcow/nginx:1.05 |
| acme-mailcow | ghcr.io/mailcow/acme:1.94 |
| netfilter-mailcow | ghcr.io/mailcow/netfilter:1.63 |
| watchdog-mailcow | ghcr.io/mailcow/watchdog:2.09 |
| dockerapi-mailcow | ghcr.io/mailcow/dockerapi:2.11 |
| olefy-mailcow | ghcr.io/mailcow/olefy:1.15 |
| ofelia-mailcow | mcuadros/ofelia:latest |
---
## Postfix Configuration
### extra.cf
```
myhostname = hermes.netgrimoire.com
```
> The MXRoute trusted network entries should also be here. Current extra.cf only contains myhostname — confirm mynetworks is set correctly or add the MXRoute IP ranges if not already present via the UI.
### Key Postfix Settings (from running config)
```
mynetworks = 127.0.0.0/8 172.22.1.0/24 10.0.1.0/24 [::1]/128 [fd4d:6169:6c63:6f77::]/64 [fe80::]/64
message_size_limit = 104857600 # 100MB
mailbox_size_limit = 0 # unlimited
bounce_queue_lifetime = 1d
maximal_queue_lifetime = 5d
delay_warning_time = 4h
postscreen_dnsbl_threshold = 6
postscreen_dnsbl_action = enforce
postscreen_greet_action = enforce
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
disable_vrfy_command = yes
broken_sasl_auth_clients = yes
```
---
## Domains
10 domains configured. All active.
| Domain | Mailboxes | Sender-Dependent Transport | Created |
|--------|-----------|---------------------------|---------|
| bamalady.com | 0 / 10 | *(not confirmed)* | — |
| bill740.com | 1 / 10 | *(not confirmed)* | — |
| florosafd.org | 4 / 10 | ID 4: heracles.mxrouting.net:587 (relay@florosafd.org) | 2025-11-21 |
| gnarlypandaproductions.com | 2 / 10 | ID 5: heracles.mxrouting.net:587 (relay@gnarlypandaproductions.com) | 2025-11-21 |
| netgrimoire.com | 2 / 10 | ID 2: heracles.mxrouting.net:587 (relay@netgrimoire.com) | 2025-11-21 |
| nucking-futz.net | 0 / 10 | *(not confirmed)* | — |
| pncfishandmore.com | 4 / 10 | ID 6: heracles.mxrouting.net:587 (relay@pncfishandmore.com) | — |
| pncharris.com | 4 / 10 | ID 3: heracles.mxrouting.net:587 (passer@pncharris.com) | 2025-11-21 |
| pncharrisenterprises.com | 2 / 10 | *(not confirmed from screenshots)* | — |
| wasted-bandwidth.net | 1 / 10 | ID 1: heracles.mxrouting.net:587 (relay@wasted-bandwidth.net) | — |
> MXRoute relay hostname is `heracles.mxrouting.net:587` — note this differs from the generic `smtp.mxroute.com` placeholder used in setup docs. Always use `heracles.mxrouting.net:587` for this account.
---
## Mailboxes
19 active mailboxes across all domains:
| Mailbox | Messages | Domain |
|---------|----------|--------|
| bill@bill740.com | 1 | bill740.com |
| chieflee@florosafd.org | 2124 | florosafd.org |
| cindy@pncfishandmore.com | 1109 | pncfishandmore.com |
| cindy@pncharris.com | 33797 | pncharris.com |
| cindy@pncharrisenterprises.com | 819 | pncharrisenterprises.com |
| dads_attic@pncharris.com | 0 | pncharris.com |
| jim.harris@florosafd.org | 8 | florosafd.org |
| kyle@gnarlypandaproductions.com | 486 | gnarlypandaproductions.com |
| kyle@pncfishandmore.com | 110 | pncfishandmore.com |
| kyle@pncharris.com | 31182 | pncharris.com |
| phil@florosafd.org | 5 | florosafd.org |
| phil@gnarlypandaproductions.com | 5 | gnarlypandaproductions.com |
| phil@netgrimoire.com | 1 | netgrimoire.com |
| phil@pncfishandmore.com | 10 | pncfishandmore.com |
| phil@pncharris.com | 3210 | pncharris.com |
| phil@pncharrisenterprises.com | 1 | pncharrisenterprises.com |
| times@florosafd.org | 191 | florosafd.org |
| traveler@netgrimoire.com | 3 | netgrimoire.com |
| traveler@wasted-bandwidth.net | 138 | wasted-bandwidth.net |
---
## Aliases
| ID | Alias | Target Domain | Internal |
|----|-------|---------------|---------|
| 7 | cindy@bamalady.com | bamalady.com | No |
---
## Sender-Dependent Transports
All outbound relay routes through `heracles.mxrouting.net:587`. This is your MXRoute server hostname — use this exact value when adding new transports.
| ID | Host | Username | Password |
|----|------|----------|----------|
| 1 | heracles.mxrouting.net:587 | relay@wasted-bandwidth.net | dZ4yLYznVvgSJtqWZJFA |
| 2 | heracles.mxrouting.net:587 | relay@netgrimoire.com | TVGCnJp9SxRbWU8EhkMw |
| 3 | heracles.mxrouting.net:587 | passer@pncharris.com | bBJtPhrGkHvvhxhukkae |
| 4 | heracles.mxrouting.net:587 | relay@florosafd.org | 2Fe8XMyaeh6Z5dvdHYdq |
| 5 | heracles.mxrouting.net:587 | relay@gnarlypandaproductions.com | vG5ZsUQhRWD2UyzLPsqA |
| 6 | heracles.mxrouting.net:587 | relay@pncfishandmore.com | *(confirm from MXRoute panel)* |
---
## DKIM Keys
Two DKIM selectors are configured per domain — one for Mailcow (selector: `dkim`) and one added separately for MXRoute outbound signing. The Mailcow-managed keys use selector `dkim._domainkey`.
### pncharris.com
```
v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqhgQV7r+KKQwJceWenZ3FNq8AsllgW6cIm/0jpsLT62vF1yy0nh2MdhjYgQAX2MK9HHYzNZcCB3+OPpqBbXeNbSDckxB/dC+z/vboMHrJmYonfaSYshZjSR80V/a2Yoq+hiXQ9eBcuOggENtMm4XvEsl/vOWLBMfasqe+X11gzQBeRv1tTaXJB0C4i7tAcfi0O/AxH8QFTr2099+k2iepn8J15ukk1zu4zemBJj4Z3uFTNnBP8YpgKbYoUDyMVIKIxGjANVBBypcrMKavpQ4F1JLhgGFhWAsAuFRwZsnOaftZyMuzAZxM37DTd/bF2WanmK3Xe75SN5uOnEXjuzW/wIDAQAB
```
### netgrimoire.com
```
v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoJ9YKqV9+6gOcVKI+UJ0TRcMmergxU8HLO+mwTMfqOhblsEcDPO60c8ya24iIXg51AA2k5Xcbb0bLScaaIi0P/TRzP/bonAZkPS1Y8Fx1se9dikTsA9Lazho u6DvoFkkV/IPH1ZNg68Cd9teAD5tvoY18OSneJJsocXwFo57c+XccUaTxjpV7eReuT4da7iNHMmUmZNfKenxVMKD740zrDJAeAsXtEb/71CochHYSm+qAvuG9/WPixJbMsJLF/iVhV3Byp0LCrB+CwGTwnsiUcd7QpuD6rRs/7zzdGBtoN22m/j390GimFstYvB61I20h8sHWGAG66dLko6Sgvs47wIDAQAB
```
### gnarlypandaproductions.com
```
v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
```
*(scroll cut off in screenshot — retrieve full key from Mailcow UI → Edit domain → bottom of page)*
> All other domain DKIM keys should be retrieved from the Mailcow domain edit page and recorded here for disaster recovery completeness.
---
## Network Configuration
Mailcow containers join the `netgrimoire` external overlay network, allowing communication with other Docker Swarm services (Caddy reverse proxy, etc.) without exposing ports directly to the host network.
**Internal Docker network:** `172.22.1.0/24`
Key container IPs within the mailcow-network:
- unbound: 172.22.1.254
- redis: 172.22.1.249
- sogo: 172.22.1.248
- dovecot: 172.22.1.250
- postfix: 172.22.1.253
**IPv6:** disabled (`ENABLE_IPV6=false`)
---
## Caddy Reverse Proxy
Mailcow's nginx listens on HTTPS port 3443 internally. Caddy proxies external requests to it. Mailcow handles its own TLS for direct mail client connections (IMAP 993, SMTP 465/587).
The admin UI at `mail.netgrimoire.com` is proxied through Caddy on the `netgrimoire` overlay network.
---
## Updating Mailcow
```bash
cd /opt/mailcow-dockerized
# Pull latest
git fetch origin
git checkout origin/master
# Update containers
docker compose pull
./update.sh
```
> As of documentation date, version 2026-01 is available. Current running version is 2025-10a. Update when convenient — check the [MailCow changelog](https://github.com/mailcow/mailcow-dockerized/releases) for breaking changes first.
Monthly update check is recommended. MailCow had multiple security vulnerabilities in 2025 — staying current is important.
---
## Common Operations
### Restart all containers
```bash
cd /opt/mailcow-dockerized
docker compose restart
```
### Restart single container (e.g. after extra.cf change)
```bash
docker compose restart postfix-mailcow
```
### View logs
```bash
# Postfix
docker compose logs postfix-mailcow -f
# Dovecot
docker compose logs dovecot-mailcow -f
# All containers
docker compose logs -f
```
### Check queue
```bash
docker exec mailcow-postfix-mailcow-1 postqueue -p
```
### Flush queue
```bash
docker exec mailcow-postfix-mailcow-1 postqueue -f
```
### Check container health
```bash
docker compose ps
```
---
## Known Gotchas
**Disk usage is at 93%.** The nvme0n1p2 volume has 442G used of 502G. This needs attention — vmail storage grows over time and garbage collection runs hourly but only removes items older than 7200 minutes (5 days). Monitor this and consider quota enforcement per mailbox if growth continues.
**extra.cf is minimal.** The MXRoute trusted network IPs should be confirmed in the running Postfix config. The `mynetworks` value from `postconf` shows `10.0.1.0/24` is already trusted — confirm whether MXRoute IP ranges `69.167.160.0/19` and `198.54.120.0/22` are included. If not, add them to extra.cf and restart postfix.
**MXRoute relay hostname.** The actual relay hostname for this account is `heracles.mxrouting.net:587` — not the generic `smtp.mxroute.com` placeholder. All 6 transports use `heracles.mxrouting.net:587`. Use this exact hostname for any new transport entries.
**pncharris.com uses passer@ not relay@.** Transport ID 3 for pncharris.com authenticates as `passer@pncharris.com`, not `relay@pncharris.com`. This is intentional — the relay@ account exists but passer@ is the current active relay credential.
**HTTPS on port 3443.** Mailcow's web UI is not on the standard 443 — it binds to 3443 and Caddy handles the public-facing 443 proxy. Direct access to the UI requires going through Caddy or using the internal port.
**nucking-futz.net vs nucking-futz.com.** The domains list shows `nucking-futz.net` but the intended new domain is `nucking-futz.com`. Verify which is actually configured and correct if needed.
**bamalady.com and bill740.com** have no transport assigned in the screenshots. Confirm whether these domains need MXRoute relay configured.
---
## Related Documentation
- [MXRoute Master Configuration](./mxroute-master) — per-domain DNS, inbound forwarding, outbound relay credentials
- [Mail Setup — nucking-futz.com](./mail-setup-nucking-futz) — new domain setup guide
- [MailCow Security Hardening](./mailcow-security-hardening)
- [Caddy Reverse Proxy](./caddy-reverse-proxy) — proxies mail.netgrimoire.com to port 3443
- [OPNsense Firewall](./opnsense-firewall) — ATT_Mail static IP, port forwarding rules

View file

@ -0,0 +1,430 @@
---
title: Integrating MXRoute with MailCow
description:
published: true
date: 2026-02-25T21:04:37.135Z
tags:
editor: markdown
dateCreated: 2026-02-25T19:22:31.514Z
---
# MXRoute — Master Configuration Reference
## Overview
MXRoute serves two roles in Netgrimoire mail infrastructure:
- **Inbound gateway** — MX records for all domains point to MXRoute's commercial IPs, solving residential AT&T IP filtering by banks and financial institutions. MXRoute receives mail and forwards to Mailcow via per-address forwarders.
- **Outbound relay** — Mailcow sends all outbound mail through MXRoute via sender-dependent transports for improved deliverability.
**Mail flow:**
```
Inbound: Internet → MXRoute (commercial IP) → Mailcow (192.168.5.16)
Outbound: Mailcow (192.168.5.16) → MXRoute SMTP relay → Internet
```
**Mailcow host:** 192.168.5.16
**MXRoute control panel:** confirm server hostname from MXRoute welcome email (e.g. `arrow.mxrouting.net`)
**MXRoute SMTP relay:** confirm from welcome email (e.g. `smtp.mxroute.com:587`)
---
## Architecture — Why Two Domains Per Hosted Domain
MXRoute forwarders require a valid destination email address. Forwarding `user@domain.com` back to `user@domain.com` creates a mail loop because MXRoute would look up the MX for `domain.com` and find itself. The solution is a `mail.domain.com` subdomain with its own MX record pointing directly to Mailcow. MXRoute forwards to `user@mail.domain.com`, Mailcow accepts and delivers, and an alias domain maps `@domain.com` back so users only ever see `@domain.com`.
```
domain.com MX → MXRoute (public-facing, receives from internet)
mail.domain.com MX → 192.168.5.16 (internal, MXRoute forwards here)
```
---
## MXRoute Control Panel
**Login:** confirm URL from MXRoute welcome email
**Interface:** MXRoute 4.0 (new UI — not old DirectAdmin)
### Creating a Forwarder
1. Go to **Forwarders**
2. Click **Create New Forwarder**
3. Set **Forwarder Name:** `username` (domain shown automatically)
4. Set **Destination Type:** `Forward to Email(s)`
5. Set **Recipients:** `username@mail.domain.com`
6. Click **Create Forwarder**
> Recipients field accepts multiple addresses comma or newline separated.
---
## Mailcow Configuration
### Adding a New Domain (One-Time Per Domain)
1. **Mail Setup → Domains → Add domain**
- Domain: `mail.domain.com` (the subdomain Mailcow owns)
- Leave relay settings as default
2. **Mail Setup → Alias Domains → Add alias domain**
- Alias Domain: `domain.com`
- Target Domain: `mail.domain.com`
- This makes Mailcow accept and deliver mail for `@domain.com` to `@mail.domain.com` mailboxes
3. **Configuration → ARC/DKIM Keys**
- Select domain `mail.domain.com`
- Selector: `mailcow`
- Key length: 2048
- Generate and copy TXT record for DNS
4. **Configuration → Extra Postfix configuration → extra.cf**
```
# Trust MXRoute forwarding IPs — prevents SPF scoring on forwarded mail
mynetworks = 127.0.0.1/8 [::1]/128 192.168.5.0/24 69.167.160.0/19 198.54.120.0/22
```
Restart affected containers after saving.
### Adding a New Mailbox
1. **Mail Setup → Mailboxes → Add mailbox**
- Username: `user`
- Domain: `mail.domain.com`
2. **MXRoute control panel → Forwarders → Create New Forwarder**
- Forwarder: `user@domain.com`
- Destination: `user@mail.domain.com`
### Outbound Relay — Sender-Dependent Transports
One transport entry per domain. **Configuration → Routing → Sender-Dependent Transports**
| Domain | Relay Host | Username | Password |
|--------|-----------|----------|----------|
| pncharris.com | `[smtp.mxroute.com]:587` | relay@pncharris.com | H@rv3yD)G123 |
| wasted-bandwidth.net | `[smtp.mxroute.com]:587` | relay@wasted-bandwidth.net | dZ4yLYznVvgSJtqWZJFA |
| netgrimoire.com | `[smtp.mxroute.com]:587` | relay@netgrimoire.com | TVGCnJp9SxRbWU8EhkMw |
| florosafd.org | `[smtp.mxroute.com]:587` | relay@florosafd.org | 2Fe8XMyaeh6Z5dvdHYdq |
| gnarlypandaproductions.com | `[smtp.mxroute.com]:587` | relay@gnarlypandaproductions.com | vG5ZsUQhRWD2UyzLPsqA |
> Confirm SMTP relay hostname from MXRoute welcome email — substitute actual hostname for `smtp.mxroute.com` if different.
### Email Client Settings (All Domains)
| Setting | Value |
|---------|-------|
| IMAP server | `mail.domain.com` |
| IMAP port | `993` (SSL/TLS) |
| SMTP server | `mail.domain.com` |
| SMTP port | `465` (SSL/TLS) |
| Username | `user@domain.com` |
> Users log in with `@domain.com`. Mailcow resolves to the internal `@mail.domain.com` mailbox via alias domain — transparent to the user.
---
## DNS Reference — All Domains
### DNS Pattern (Apply to Every Domain)
Two sets of MX records are required — one for the public domain (pointing to MXRoute) and one for the mail subdomain (pointing directly to Mailcow).
| Type | Host | Value | Notes |
|------|------|-------|-------|
| A | `mail` | `YOUR_ATT_MAIL_IP` | Mailcow server — MXRoute forwards here |
| MX | `@` | MXRoute primary (priority 10) | From MXRoute welcome email |
| MX | `@` | MXRoute secondary (priority 20) | From MXRoute welcome email |
| MX | `mail` | `mail.domain.com` (priority 10) | Mailcow handles subdomain directly |
| CNAME | `imap` | `mail.domain.com` | Client autoconfiguration |
| CNAME | `smtp` | `mail.domain.com` | Client autoconfiguration |
| CNAME | `webmail` | `mail.domain.com` | Roundcube access |
| CNAME | `autodiscover` | `mail.domain.com` | Outlook autodiscover |
| CNAME | `autoconfig` | `mail.domain.com` | Thunderbird autoconfig |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` | SPF — both Mailcow direct and MXRoute relay |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` | SPF for subdomain — Mailcow direct only |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` | DMARC enforcement |
| TXT | `mailcow._domainkey.mail` | *(generated in Mailcow ARC/DKIM Keys)* | Mailcow DKIM selector |
| TXT | `x._domainkey` | *(from MXRoute control panel)* | MXRoute DKIM selector — confirm actual selector name |
---
### pncharris.com
| Type | Host | Value |
|------|------|-------|
| A | `mail` | YOUR_ATT_MAIL_IP |
| MX | `@` | MXRoute primary (priority 10) |
| MX | `@` | MXRoute secondary (priority 20) |
| MX | `mail` | `mail.pncharris.com` (priority 10) |
| CNAME | `imap` | `mail.pncharris.com` |
| CNAME | `smtp` | `mail.pncharris.com` |
| CNAME | `webmail` | `mail.pncharris.com` |
| CNAME | `autodiscover` | `mail.pncharris.com` |
| CNAME | `autoconfig` | `mail.pncharris.com` |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` |
| TXT | `mailcow._domainkey.mail` | *(from Mailcow ARC/DKIM Keys for mail.pncharris.com)* |
| TXT | `x._domainkey` | *(from MXRoute control panel)* |
**Mailcow domains:** `mail.pncharris.com` (primary), `pncharris.com` (alias domain → mail.pncharris.com)
**Relay credentials:**
| Account | Password | Notes |
|---------|----------|-------|
| relay@pncharris.com | H@rv3yD)G123 | Current relay account |
| forwarder@pncharris.com | *(see password history below)* | Legacy account |
| passer@pncharris.com | bBJtPhrGkHvvhxhukkae | Current |
| kylr pncharris | -,68,incTeR | |
| G4@rlyf1ng3r | *(Feb 14)* | |
**passer@pncharris.com password history** (most recent last):
- !5!,_\*zDyLEhhR4
- sh7dXWnTPqbkDGsTcwtn
- MY3V8p69b2HYksygxhXX
- RS6U2GU6rcYe3THKKgYx
- yzqNysrd73yzWptVEZ5H (current)
---
### wasted-bandwidth.net
| Type | Host | Value |
|------|------|-------|
| A | `mail` | YOUR_ATT_MAIL_IP |
| MX | `@` | MXRoute primary (priority 10) |
| MX | `@` | MXRoute secondary (priority 20) |
| MX | `mail` | `mail.wasted-bandwidth.net` (priority 10) |
| CNAME | `imap` | `mail.wasted-bandwidth.net` |
| CNAME | `smtp` | `mail.wasted-bandwidth.net` |
| CNAME | `webmail` | `mail.wasted-bandwidth.net` |
| CNAME | `autodiscover` | `mail.wasted-bandwidth.net` |
| CNAME | `autoconfig` | `mail.wasted-bandwidth.net` |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` |
| TXT | `mailcow._domainkey.mail` | *(from Mailcow ARC/DKIM Keys for mail.wasted-bandwidth.net)* |
| TXT | `x._domainkey` | *(from MXRoute control panel)* |
**Mailcow domains:** `mail.wasted-bandwidth.net` (primary), `wasted-bandwidth.net` (alias domain)
**Relay credentials:**
| Account | Password |
|---------|----------|
| relay@wasted-bandwidth.net | dZ4yLYznVvgSJtqWZJFA |
---
### netgrimoire.com
| Type | Host | Value |
|------|------|-------|
| A | `mail` | YOUR_ATT_MAIL_IP |
| MX | `@` | MXRoute primary (priority 10) |
| MX | `@` | MXRoute secondary (priority 20) |
| MX | `mail` | `mail.netgrimoire.com` (priority 10) |
| CNAME | `imap` | `mail.netgrimoire.com` |
| CNAME | `smtp` | `mail.netgrimoire.com` |
| CNAME | `webmail` | `mail.netgrimoire.com` |
| CNAME | `autodiscover` | `mail.netgrimoire.com` |
| CNAME | `autoconfig` | `mail.netgrimoire.com` |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` |
| TXT | `mailcow._domainkey.mail` | *(from Mailcow ARC/DKIM Keys for mail.netgrimoire.com)* |
| TXT | `x._domainkey` | *(from MXRoute control panel)* |
**Mailcow domains:** `mail.netgrimoire.com` (primary), `netgrimoire.com` (alias domain)
**Relay credentials:**
| Account | Password |
|---------|----------|
| relay@netgrimoire.com | TVGCnJp9SxRbWU8EhkMw |
---
### florosafd.org
| Type | Host | Value |
|------|------|-------|
| A | `mail` | YOUR_ATT_MAIL_IP |
| MX | `@` | MXRoute primary (priority 10) |
| MX | `@` | MXRoute secondary (priority 20) |
| MX | `mail` | `mail.florosafd.org` (priority 10) |
| CNAME | `imap` | `mail.florosafd.org` |
| CNAME | `smtp` | `mail.florosafd.org` |
| CNAME | `webmail` | `mail.florosafd.org` |
| CNAME | `autodiscover` | `mail.florosafd.org` |
| CNAME | `autoconfig` | `mail.florosafd.org` |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` |
| TXT | `mailcow._domainkey.mail` | *(from Mailcow ARC/DKIM Keys for mail.florosafd.org)* |
| TXT | `x._domainkey` | *(from MXRoute control panel)* |
**Mailcow domains:** `mail.florosafd.org` (primary), `florosafd.org` (alias domain)
**Relay credentials:**
| Account | Password |
|---------|----------|
| relay@florosafd.org | 2Fe8XMyaeh6Z5dvdHYdq |
---
### gnarlypandaproductions.com
| Type | Host | Value |
|------|------|-------|
| A | `mail` | YOUR_ATT_MAIL_IP |
| MX | `@` | MXRoute primary (priority 10) |
| MX | `@` | MXRoute secondary (priority 20) |
| MX | `mail` | `mail.gnarlypandaproductions.com` (priority 10) |
| CNAME | `imap` | `mail.gnarlypandaproductions.com` |
| CNAME | `smtp` | `mail.gnarlypandaproductions.com` |
| CNAME | `webmail` | `mail.gnarlypandaproductions.com` |
| CNAME | `roundcube` | `roundcube.netgrimoire.com` |
| CNAME | `autodiscover` | `mail.gnarlypandaproductions.com` |
| CNAME | `autoconfig` | `mail.gnarlypandaproductions.com` |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@gnarlypandaproductions.com` |
| TXT | `mailcow._domainkey.mail` | *(from Mailcow ARC/DKIM Keys for mail.gnarlypandaproductions.com)* |
| TXT | `default._domainkey` | `v=DKIM1; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3D3vyPoBHB4eMSMq8HygVWHzYbketRX4yjk9wV4bdaar0/c89dK230FMOW6zVXEsY1sXKFk1kBxerHVw0wY8qnQyooHgINEQcEXrtB/x93Sl/cqBQXk+PHOIOymQwgni8WCUhCSnvunxXK8qX5f9J56qzd0/wpY2WSEHho+XrnQjc+c7HMvkcC3+nKJe59ZNgvQW/Y9B/L6zFDjAp+QOUYp9wwX4L+j1T4fQSygYxAJZ0aIoR8FsbOuXc38pht99HyUnYwH08HoK7xv3DL2BrVo3KVZ7xMe2S4YMxd1HkJz2evbV/ziNsJcKW/le3fFS7mza09yJXDLDcLOKLXbYUQIDAQAB` |
| TXT | `x._domainkey` | *(from MXRoute control panel — confirm actual selector)* |
**Mailcow domains:** `mail.gnarlypandaproductions.com` (primary), `gnarlypandaproductions.com` (alias domain)
**Relay credentials:**
| Account | Password |
|---------|----------|
| relay@gnarlypandaproductions.com | vG5ZsUQhRWD2UyzLPsqA |
---
### nucking-futz.com
New domain — see [Mail Setup — nucking-futz.com](./mail-setup-nucking-futz) for full setup guide.
| Type | Host | Value |
|------|------|-------|
| A | `mail` | YOUR_ATT_MAIL_IP |
| MX | `@` | MXRoute primary (priority 10) |
| MX | `@` | MXRoute secondary (priority 20) |
| MX | `mail` | `mail.nucking-futz.com` (priority 10) |
| CNAME | `imap` | `mail.nucking-futz.com` |
| CNAME | `smtp` | `mail.nucking-futz.com` |
| CNAME | `webmail` | `mail.nucking-futz.com` |
| CNAME | `autodiscover` | `mail.nucking-futz.com` |
| CNAME | `autoconfig` | `mail.nucking-futz.com` |
| TXT | `@` | `v=spf1 ip4:YOUR_ATT_MAIL_IP include:mxroute.com -all` |
| TXT | `mail` | `v=spf1 ip4:YOUR_ATT_MAIL_IP -all` |
| TXT | `_dmarc` | `v=DMARC1; p=reject; rua=mailto:admin@netgrimoire.com` |
| TXT | `mailcow._domainkey.mail` | *(from Mailcow ARC/DKIM Keys for mail.nucking-futz.com)* |
| TXT | `x._domainkey` | *(from MXRoute control panel)* |
**Mailcow domains:** `mail.nucking-futz.com` (primary), `nucking-futz.com` (alias domain)
**Relay credentials:**
| Account | Password |
|---------|----------|
| relay@nucking-futz.com | *(set during MXRoute domain creation)* |
---
## Adding a New Domain — Checklist
Use this checklist every time a new domain is added to the stack.
**DNS (at registrar):**
- [ ] A record: `mail.newdomain.com` → YOUR_ATT_MAIL_IP
- [ ] MX records: `@` → MXRoute servers
- [ ] MX record: `mail``mail.newdomain.com`
- [ ] CNAME records: imap, smtp, webmail, autodiscover, autoconfig
- [ ] SPF TXT: `@` — includes both ATT IP and `include:mxroute.com`
- [ ] SPF TXT: `mail` — ATT IP only
- [ ] DMARC TXT: `_dmarc`
- [ ] DKIM TXT: `mailcow._domainkey.mail` — after generating in Mailcow
- [ ] DKIM TXT: `x._domainkey` — after retrieving from MXRoute
**Mailcow:**
- [ ] Add domain: `mail.newdomain.com`
- [ ] Add alias domain: `newdomain.com``mail.newdomain.com`
- [ ] Generate DKIM key (selector: `mailcow`) for `mail.newdomain.com`
- [ ] Add sender-dependent transport for `newdomain.com`
- [ ] Add sender-dependent transport for `mail.newdomain.com`
- [ ] Create mailboxes as `user@mail.newdomain.com`
**MXRoute:**
- [ ] Add domain in control panel
- [ ] Create forwarder for each mailbox: `user@newdomain.com``user@mail.newdomain.com`
- [ ] Retrieve DKIM key for DNS
---
## Troubleshooting
### Mail not delivering inbound (not reaching Mailcow)
- Check MX records for `@` point to MXRoute servers: `dig MX domain.com +short`
- Check MX record for `mail` subdomain points to Mailcow: `dig MX mail.domain.com +short`
- Verify MXRoute forwarder exists for the address in the control panel
- Check Mailcow logs: **Logs → Postfix** — look for the delivery attempt and any rejection reason
- Verify MXRoute IP ranges are in Mailcow `extra.cf` trusted networks
### Mail not delivering inbound (banks / financial institutions)
- This is the residential AT&T IP problem — confirm MX records point to MXRoute, not directly to your IP
- Run `dig MX domain.com +short` — should show MXRoute servers, not your IP
- If MX still points to your ATT IP, update DNS and wait for propagation
### Outbound mail rejected or going to spam
- Verify sender-dependent transport is configured for the domain in Mailcow
- Check relay credentials are current in the transport entry
- Run an SPF check: `dig TXT domain.com +short` — confirm `include:mxroute.com` is present
- Send test to check-auth@verifier.port25.com for full SPF/DKIM/DMARC report
- Run through https://mail-tester.com for a deliverability score
### DKIM verification failing
- Confirm both selectors are published in DNS:
- `dig TXT mailcow._domainkey.mail.domain.com +short`
- `dig TXT x._domainkey.domain.com +short` (substitute actual MXRoute selector)
- Allow up to 48 hours for DNS propagation after adding records
- Verify selector names match exactly what Mailcow and MXRoute are using to sign
### DMARC failures
- SPF and DKIM must both pass and align with the From: domain
- Check DMARC reports sent to `admin@netgrimoire.com` — use [Postmark DMARC](https://dmarc.postmarkapp.com/) or [dmarcian.com](https://dmarcian.com) to parse raw XML reports
- Common cause: outbound mail going through MXRoute but `include:mxroute.com` missing from SPF
### Forwarded mail getting spam-scored
- Confirm MXRoute IP ranges are in Mailcow `extra.cf` mynetworks
- Check that Mailcow trusted networks were saved and containers restarted
- Verify SRS is working: in Roundcube open a forwarded message → More → View Source → `Return-Path` should begin with `SRS0=`
### New mailbox not receiving mail
- Two steps are required — confirm both were done:
1. Mailbox created in Mailcow as `user@mail.domain.com`
2. Forwarder created in MXRoute as `user@domain.com``user@mail.domain.com`
- If the MXRoute forwarder is missing, inbound mail silently goes nowhere
---
## Related Documentation
- [MailCow Configuration](./mailcow)
- [MailCow Security Hardening](./mailcow-security-hardening)
- [Mail Setup — nucking-futz.com](./mail-setup-nucking-futz)
- [OPNsense Firewall](./opnsense-firewall) — ATT_Mail static IP allocation

View file

@ -0,0 +1,85 @@
---
title: MailCow Overview
description: Self-hosted mail stack — architecture, domains, and key decisions
published: true
date: 2026-04-12T00:00:00.000Z
tags: keystone, mail, mailcow
editor: markdown
dateCreated: 2026-04-12T00:00:00.000Z
---
# MailCow Overview
MailCow runs on `docker4` (hermes, 192.168.5.16) via Docker Compose — not Swarm. It manages mail for all 8 domains.
---
## Architecture
| Component | Role |
|-----------|------|
| MailCow stack | Postfix, Dovecot, Rspamd, ClamAV, SOGo, Roundcube, nginx-mailcow |
| MXRoute | Inbound filtering + outbound relay for all domains |
| nginx-mailcow | Only MailCow container connected to `netgrimoire` overlay |
**Critical:** Only `nginx-mailcow` is attached to the `netgrimoire` overlay network. All other MailCow containers stay on the internal `mailcow-network` bridge. Connecting other containers to the overlay causes Redis and PHP-FPM to resolve to wrong IPs, breaking the entire stack.
---
## Domains
`netgrimoire.com` · `pncharris.com` · `wasted-bandwidth.net` · `nucking-futz.com` · `florosafd.org` · `gnarlypandaproductions.com` · `pncfishandmore.com` · `pncharrisenterprises.com`
---
## Mail Flow
**Inbound:** MXRoute filters → forwards to MailCow → Dovecot delivers
**Outbound:** Postfix → MXRoute relay → recipient
**SRS rewriting:** MXRoute rewrites the envelope sender on forwarded mail. All domains using MXRoute inbound forwarding **must** have catch-all aliases configured in MailCow, or `reject_unlisted_sender` will reject the rewritten addresses.
---
## DKIM
Two selectors required:
| Selector | Purpose |
|----------|---------|
| `mailcow` | Direct sends from MailCow |
| `mxroute` | MXRoute relay path |
---
## Key Limits (must match across all three)
Attachment size limits must be set identically in Postfix, Rspamd, and ClamAV. Changing only Postfix is insufficient — Rspamd and ClamAV reject large messages before Postfix processes them.
---
## Roundcube SSL
Internal connections to Dovecot use self-signed certs. In `config.inc.php`:
```php
$config['imap_conn_options'] = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];
```
---
## Related Docs
- [MXRoute Integration](/Keystone-Grimoire/Mail/MXRoute-Integration)
- [Domain Setup](/Keystone-Grimoire/Mail/Domain-Setup)
- [MailCow Hardening](/Keystone-Grimoire/Mail/Hardening)
- [MailCow Backup](/Vault-Grimoire/Backups/MailCow-Backup)
---
## Pending
- [ ] Dedicated ATT_Mail static IP for outbound mail (OPNsense outbound NAT rule)
- [ ] Second DKIM selector (`mxroute`) validation
- [ ] MTA-STS validation (supported since Sep 2025 update)