--- tags: * Proxmox Mail Gateway * PMG * Mailcow * Email * SMTP * Gateway * Spam Filtering --- # Proxmox Mail Gateway Inbound Filtering for Mailcow ## Purpose This document defines the procedure for placing `Proxmox Mail Gateway` in front of an existing `Mailcow` server for inbound SMTP filtering. PMG will handle inbound SMTP inspection before delivering accepted mail to Mailcow. This document covers **inbound SMTP filtering only**. It does not move: * Outbound SMTP delivery * DKIM signing * SMTP submission * IMAP * POP3 * ManageSieve * Mailcow certificates * Mailcow web access * Roundcube access ## Assumptions Mailcow is already deployed and functional. Mailcow already handles: * Mailbox hosting * User authentication * Webmail * Mailcow admin interface * IMAP * POP3 * SMTP submission * Outbound delivery * DKIM signing * TLS certificates for `mail.bunny-lab.io` Example environment: ```text Proxmox Mail Gateway: 192.168.3.15 Mailcow Server: 192.168.3.61 Mail Hostname: mail.bunny-lab.io Mail Domain: bunny-lab.io Firewall: pfSense Reverse Proxy: Traefik ``` !!! warning "Inbound SMTP Only" Only move public inbound SMTP port `25` to PMG during this stage. ``` Do not move mail client ports, outbound relay behavior, DKIM signing, or Mailcow web access. ``` ## Architecture ### Existing Mail Flow ```text Internet | v pfSense WAN :25 | v Mailcow 192.168.3.61:25 ``` ### Target Mail Flow ```text Internet | v pfSense WAN :25 | v PMG 192.168.3.15:25 | v Mailcow 192.168.3.61:25 ``` ### Final Service Ownership ```text PMG - Inbound SMTP on port 25 - Spam filtering - Virus filtering - Tracking Center - Quarantine - Delivery of accepted inbound mail to Mailcow Mailcow - Mailbox hosting - User authentication - Webmail - Mailcow admin interface - IMAP - POP3 - SMTP submission - Outbound mail delivery - DKIM signing - TLS certificates for mail.bunny-lab.io Traefik - Public HTTP - Public HTTPS - Mailcow / Roundcube frontend routing ``` ## DNS Public DNS remains unchanged. ```text bunny-lab.io MX 10 mail.bunny-lab.io mail.bunny-lab.io A ``` The public DNS records continue pointing to the WAN IP. The firewall determines where inbound SMTP is delivered internally. ```text pfSense WAN :25 -> PMG 192.168.3.15:25 ``` !!! note "DNS Does Not Point to PMG Directly" The public MX and A records do not point to the internal PMG IP. ``` NAT controls the internal SMTP destination. ``` ## Firewall and NAT Design Only public inbound SMTP changes. Change this: ```text WAN :25 -> Mailcow 192.168.3.61:25 ``` To this: ```text WAN :25 -> PMG 192.168.3.15:25 ``` Leave Mailcow client access ports pointed directly at Mailcow. ```text WAN :465 -> Mailcow 192.168.3.61:465 WAN :587 -> Mailcow 192.168.3.61:587 WAN :993 -> Mailcow 192.168.3.61:993 WAN :995 -> Mailcow 192.168.3.61:995 WAN :110 -> Mailcow 192.168.3.61:110 WAN :143 -> Mailcow 192.168.3.61:143 WAN :4190 -> Mailcow 192.168.3.61:4190 ``` Leave web traffic on the existing reverse proxy path. ```text WAN :80 -> Traefik :80 WAN :443 -> Traefik :443 ``` !!! warning "Do Not Move Mail Client Ports to PMG" PMG is an SMTP gateway. ``` Do not forward IMAP, POP3, SMTPS, Submission, or ManageSieve ports to PMG. ``` ## Initial PMG Access Access the PMG management interface. ```text https://192.168.3.15:8006 ``` Use the `root` credentials configured during PMG installation. !!! note "Certificate Warning" Browser certificate warnings are expected when accessing PMG by IP address unless a trusted certificate has already been configured for the management interface. ## Pre-Cutover Connectivity Checks Confirm PMG can reach Mailcow on SMTP port `25`. Run from the PMG shell: ```sh # Confirm PMG can reach Mailcow SMTP nc -vz 192.168.3.61 25 ``` Expected result: ```text (UNKNOWN) [192.168.3.61] 25 (smtp) open ``` Reverse DNS warnings are not automatically failures. ```text inverse host lookup failed: Unknown host ``` If port `25` still reports as open, SMTP connectivity is working. Confirm Mailcow presents an SMTP banner. ```sh # Connect from PMG directly to Mailcow SMTP nc 192.168.3.61 25 ``` Expected banner: ```text 220-mail.bunny-lab.io ESMTP Postcow 220 mail.bunny-lab.io ESMTP Postcow ``` Exit the SMTP session. ```text quit ``` !!! note "Mailcow SMTP Banner" Mailcow commonly identifies its SMTP service as `Postcow`. ``` That is expected. ``` ## PMG Mail Proxy Ports In PMG, navigate to: ```text Configuration > Mail Proxy > Ports ``` Confirm: ```text External SMTP Port: 25 ``` No outbound filtering is configured during this stage. !!! note "Internal SMTP Port" PMG also has an internal SMTP port used for outbound filtering from an internal mail server. ``` This deployment does not use outbound PMG filtering yet. ``` ## PMG Relay Domains In PMG, navigate to: ```text Configuration > Mail Proxy > Relay Domains ``` Add the accepted mail domain. ```text bunny-lab.io ``` This authorizes PMG to accept mail for the domain. !!! warning "Relay Domains Are Required" If the domain is missing from Relay Domains, PMG may reject inbound mail because it is not configured as responsible for that domain. ## PMG Default Relay In PMG, navigate to: ```text Configuration > Mail Proxy > Relaying ``` Configure Mailcow as the default relay. ```text Default Relay: 192.168.3.61 Relay Port: 25 Relay Protocol: smtp Disable MX Lookup: Yes Smarthost: none ``` Target internal relay path: ```text PMG 192.168.3.15 | v Mailcow 192.168.3.61:25 ``` !!! note "Disable MX Lookup" PMG should deliver accepted inbound mail directly to the internal Mailcow server. ``` It should not perform public MX lookup for the local mail domain. ``` !!! note "No Smarthost" Leave `Smarthost` unset or set to `none` for inbound-only filtering. ``` Smarthost configuration is used for outbound relay behavior. ``` ## Mailcow Forwarding Host Configure Mailcow to trust PMG as a forwarding host. In Mailcow, navigate to: ```text System > Configuration Dropdown > Options > Forwarding Hosts Dropdown ``` Add the PMG IP address. ```text 192.168.3.15 ``` Set the forwarding-host spam filter option to: ```text Inactive ``` !!! note "Forwarding Host Behavior" After cutover, Mailcow sees PMG as the immediate SMTP source for inbound mail. ``` Trusting PMG allows Mailcow to interpret forwarded mail correctly. ``` !!! note "Spam Filtering Placement" PMG is the primary inbound spam and virus filtering system. ``` Leave Mailcow forwarding-host spam filtering inactive to avoid double-filtering messages already inspected by PMG. ``` ## Outbound Mail Leave outbound mail unchanged. ```text Mailcow 192.168.3.61 | v Internet ``` Do not configure Mailcow to relay outbound mail through PMG during this stage. Do not change: ```text Relayhost Outbound firewall rules DKIM signing SPF record DMARC record ``` !!! warning "Do Not Move DKIM" DKIM signing applies to outbound mail. ``` This document only moves inbound SMTP filtering. ``` ## Filtering Policy Initial filtering ownership: ```text PMG = primary inbound SMTP filtering, tracking, quarantine Mailcow = mailbox hosting, authentication, webmail, mail client access ``` Avoid configuring both PMG and Mailcow to aggressively quarantine the same inbound mail stream. !!! note "Keep Filtering Boring" PMG should own edge filtering first. ``` Mailcow should continue owning mailbox and client access behavior. ``` ## SMTP NAT Cutover After PMG relay domains, PMG default relay, and Mailcow forwarding host settings are configured, update the pfSense NAT rule. Change: ```text WAN :25 -> Mailcow 192.168.3.61:25 ``` To: ```text WAN :25 -> PMG 192.168.3.15:25 ``` Do not change the remaining Mailcow port forwards. ```text 465 -> 192.168.3.61 587 -> 192.168.3.61 993 -> 192.168.3.61 995 -> 192.168.3.61 143 -> 192.168.3.61 110 -> 192.168.3.61 4190 -> 192.168.3.61 ``` Do not change the Traefik web path. ```text 80 -> Traefik 443 -> Traefik ``` !!! warning "Cutover Point" Changing `WAN :25` is the actual inbound mail cutover. ``` External SMTP servers will begin connecting to PMG instead of Mailcow directly. ``` ## Validation ### External SMTP Reachability From an external system: ```sh # Confirm public SMTP is reachable nc -vz mail.bunny-lab.io 25 ``` Alternative: ```sh # Confirm public SMTP banner using telnet telnet mail.bunny-lab.io 25 ``` Expected result: ```text Port 25 open SMTP banner returned by gateway ``` !!! note "Internal Testing Limitations" Internal tests may not represent public mail flow if NAT reflection or split-horizon DNS is involved. ``` Prefer external testing. ``` If external port testing is unavailable, send real mail from an outside provider. Usable external sources: ```text Gmail Outlook.com iCloud Proton Mail Work mailbox hosted outside Mailcow ``` ### Inbound Delivery Send an external message to a Mailcow-hosted mailbox. Expected path: ```text External mailbox | v mail.bunny-lab.io | v pfSense WAN :25 | v PMG 192.168.3.15 | v Mailcow 192.168.3.61 | v User mailbox ``` Check PMG: ```text PMG > Tracking Center ``` Expected PMG status: ```text Status: accepted/delivered Relay: 192.168.3.61[192.168.3.61]:25 ``` Check Mailcow: ```text System > Logs ``` or review the relevant Mailcow Postfix and Dovecot logs. ### Mail Client Access Confirm normal mail client behavior remains unchanged. Test: ```text IMAP receive SMTP submission send Mobile mail client access Desktop mail client access Webmail / Roundcube access Mailcow UI access ``` Expected service paths: ```text IMAPS: 993 -> Mailcow Submission: 587 -> Mailcow SMTPS: 465 -> Mailcow Web: 443 -> Traefik -> Mailcow ``` Confirm outbound mail still works by replying from a Mailcow-hosted mailbox to the external sender. ### PMG Queues Check PMG queues after test delivery. ```text PMG > Queues ``` Expected state: ```text Queue empty or near-empty after delivery ``` Queue status confirms PMG is not silently holding or deferring mail because of relay, DNS, or delivery errors. ## Validation Checklist * [ ] Public MX record points to `mail.bunny-lab.io` * [ ] `mail.bunny-lab.io` resolves to the correct public WAN IP * [ ] DNS records are unchanged * [ ] PMG can reach Mailcow on `192.168.3.61:25` * [ ] Mailcow SMTP banner is visible from PMG * [ ] PMG external SMTP port is `25` * [ ] PMG has `bunny-lab.io` configured as a relay domain * [ ] PMG default relay points to `192.168.3.61` * [ ] PMG relay port is `25` * [ ] PMG relay protocol is `smtp` * [ ] PMG internal delivery has MX lookup disabled * [ ] PMG smarthost is unset or `none` * [ ] Mailcow trusts `192.168.3.15` as a forwarding host * [ ] Mailcow forwarding-host spam filter is `Inactive` * [ ] Firewall forwards `WAN :25` to `192.168.3.15:25` * [ ] Firewall still forwards mail client ports directly to Mailcow * [ ] Traefik still handles Mailcow / Roundcube web traffic * [ ] Inbound test mail appears in PMG Tracking Center * [ ] PMG Tracking Center shows `accepted/delivered` * [ ] PMG log shows delivery to `192.168.3.61:25` * [ ] Inbound test mail is delivered to the Mailcow mailbox * [ ] PMG queue is empty after delivery * [ ] Mobile email client still works * [ ] Desktop email client still works * [ ] Webmail still works * [ ] Replying outbound from Mailcow still works * [ ] DKIM behavior is unchanged * [ ] SPF record is unchanged * [ ] DMARC record is unchanged * [ ] Outbound mail routing is unchanged ## Troubleshooting ### Inbound Mail Never Reaches PMG Verify NAT. ```text WAN :25 -> 192.168.3.15:25 ``` Verify inbound port `25` is not blocked by the ISP. From an external system: ```sh # Test public SMTP reachability nc -vz mail.bunny-lab.io 25 ``` If external testing is unavailable, send a real external test message and check: ```text PMG > Tracking Center ``` ### PMG Receives Mail but Does Not Deliver to Mailcow Verify PMG relay settings. ```text Default Relay: 192.168.3.61 Relay Port: 25 Relay Protocol: smtp Disable MX Lookup: Yes ``` Verify Mailcow SMTP is reachable from PMG. ```sh # Test Mailcow SMTP from PMG nc -vz 192.168.3.61 25 ``` Confirm the Mailcow SMTP banner. ```sh # Inspect Mailcow SMTP banner from PMG nc 192.168.3.61 25 ``` Expected banner: ```text 220-mail.bunny-lab.io ESMTP Postcow 220 mail.bunny-lab.io ESMTP Postcow ``` ### PMG Shows Reverse DNS Warning for Mailcow A warning like this is not automatically a failure: ```text inverse host lookup failed: Unknown host ``` If the connection still reports port `25` as open, SMTP connectivity is working. ### Mailcow Rejects Mail from PMG Verify Mailcow trusts PMG as a forwarding host. ```text 192.168.3.15 ``` Verify the recipient domain exists in Mailcow. ```text bunny-lab.io ``` Verify the recipient mailbox or alias exists in Mailcow. ### Mail Clients Stop Working Verify only inbound SMTP port `25` was moved to PMG. These ports should still forward directly to Mailcow: ```text 465 587 993 995 110 143 4190 ``` Expected service ownership: ```text PMG -> inbound SMTP gateway only Mailcow -> client access and mailbox services ``` ### Roundcube or Mailcow Web UI Stops Working Verify web traffic was not moved to PMG. Expected path: ```text WAN :80 -> Traefik :80 WAN :443 -> Traefik :443 ``` PMG should not replace Traefik for Mailcow or Roundcube web access. ### Outbound Mail Stops Working Outbound mail should not change during this deployment. Verify no changes were made to: ```text Mailcow relayhost Outbound firewall behavior DKIM signing SPF record DMARC record Public DNS records ``` ### Spam Filtering Behavior Is Confusing Use one primary inbound filtering authority. Recommended initial state: ```text PMG = primary inbound edge spam filter Mailcow = mailbox hosting and client access ``` Avoid dual aggressive quarantine policies until basic mail flow is stable. ## Confirmed Final State After Stage 1, the environment should operate as follows: ```text Inbound SMTP: Internet -> pfSense WAN :25 -> PMG 192.168.3.15:25 -> Mailcow 192.168.3.61:25 Outbound SMTP: Mailcow -> Internet Mail Client Access: Clients -> Mailcow Webmail / Roundcube: Internet -> Traefik -> Mailcow ``` The only public NAT behavior changed is: ```text WAN :25 ``` Unchanged components: ```text DNS records DKIM behavior SPF record DMARC record Outbound mail routing SMTP submission IMAP POP3 ManageSieve Mailcow certificates Traefik web routing Mailcow / Roundcube web access ``` ## Deployment Status This document completes Stage 1 of the PMG deployment. ```text Stage 1: Inbound filtering only Stage 2: Optional outbound filtering ``` At the end of Stage 1: ```text PMG = inbound SMTP filtering only Mailcow = mailboxes, webmail, authenticated submission, certificates, DKIM, outbound delivery, user-facing mail services ```