Enterprise · Infrastructure · Home Lab

Implementing a Mail Relay for On-Premises Exchange

By Richard Gamarra Infoplay Technologies · IT / Infrastructure July 1, 2026

A personal project: running and managing a self-hosted Microsoft Exchange server from a home office: reliably sending and receiving mail from behind a residential internet connection, using a small cloud VPS as a mail gateway.

Exchange 2019 Postfix / OpenDKIM WireGuard SPF · DKIM · DMARC Dynamic DNS Windows Server

This write-up documents a personal home-lab / home-office project: keeping a full on-premises Microsoft Exchange server usable for real email while it lives on a residential internet line. Residential connections make self-hosted mail hard for three reasons, and this project solves all three with a small cloud VPS acting as a mail relay gateway, connected to the home server over a private WireGuard tunnel.

The problem with residential mail hosting
Contents
  1. Architecture overview
  2. Prerequisites
  3. Part 1: Outbound relay (Exchange → VPS → Internet)
  4. Part 2: The WireGuard tunnel
  5. Part 3: Inbound relay with store-and-forward
  6. Part 4: Dynamic DNS on Windows
  7. Part 5: Public DNS records
  8. Part 6: Verification
  9. Results & lessons

1. Architecture overview

The key idea is to separate the public-facing role from the mailbox role. A cloud VPS with a clean static IP and a proper reverse-DNS (PTR) record becomes the only machine exposed to the internet. The home Exchange server never talks to the internet directly. It only speaks to the VPS through an encrypted tunnel it dials out itself.

Internet senders / clients VPS mail gateway Static IP + PTR Postfix ยท OpenDKIM port 25 open queues if home is down Home Exchange residential line no public ports MX โ†’ VPS (25) WireGuard tunnel inbound โ†’ Exchange outbound โ† Exchange
Inbound: senders hit the VPS on port 25; it relays to Exchange over the tunnel (and queues if home is offline). Outbound: Exchange authenticates to the VPS and mail leaves from the VPS's clean IP.

2. Prerequisites

Anonymized values. Throughout this guide the domain is example.com, the VPS public IP is 203.0.113.10, tunnel IPs are 10.8.0.1 (VPS) and 10.8.0.2 (Exchange), and the DDNS name is myexchange.duckdns.org. Replace them with your own. Every token, password and private key below is a placeholder. Never publish the real ones.

3. Part 1: Outbound relay (Exchange → VPS → Internet)

Exchange can't send on port 25 directly, so it authenticates to the VPS over submission (port 587) and the VPS relays the message to the internet from its clean IP. The VPS also DKIM-signs the mail for our domain.

3.1 A relay mailbox for authentication

Create (or reuse) a mailbox on the VPS that Exchange will authenticate as, e.g. relay@example.com, and give it a strong password. Exchange will send From your real address while authenticating as this account, which is fine.

3.2 DKIM signing on the VPS (OpenDKIM, multi-domain)

Generate a key and add the domain to OpenDKIM's key/signing tables:

# On the VPS
mkdir -p /etc/opendkim/keys/example.com
cd /etc/opendkim/keys/example.com
opendkim-genkey -b 2048 -d example.com -s default
chown opendkim:opendkim default.private && chmod 600 default.private

# /etc/opendkim/KeyTable
default._domainkey.example.com example.com:default:/etc/opendkim/keys/example.com/default.private

# /etc/opendkim/SigningTable   (refile: allows the *@ wildcard)
*@example.com default._domainkey.example.com

# /etc/opendkim.conf  (multi-domain mode)
KeyTable       /etc/opendkim/KeyTable
SigningTable   refile:/etc/opendkim/SigningTable

opendkim -n && systemctl restart opendkim   # -n validates config before restart

default.txt holds the public key you'll publish in DNS (see Part 5).

opendkim-testkey output
Confirming the published DKIM public key matches the private key on the VPS.

3.3 The Exchange send connector

Point Exchange's outbound at the VPS as a smarthost, with authentication over TLS:

# Exchange Management Shell
$cred = Get-Credential   # relay@example.com  +  YOUR_RELAY_PASSWORD
New-SendConnector -Name "VPS Relay" -Custom -AddressSpaces "*" `
  -SmartHosts "mail.myvps.net" -Port 587 `
  -SmartHostAuthMechanism BasicAuth -AuthenticationCredential $cred `
  -DNSRoutingEnabled $false -SourceTransportServers EXCHANGE
Tip: test before cutover. Inject a test message through Exchange's Pickup directory and watch the VPS mail log: you want to see sasl_username=relay@example.com and status=sent (250 ...) to the destination. If the VPS presents a self-signed certificate, use BasicAuth (opportunistic TLS). The submission service still requires STARTTLS before accepting the login, so credentials stay encrypted.

4. Part 2: The WireGuard tunnel

A private tunnel lets the VPS reach the Exchange server without exposing any port at home. The home side dials out to the VPS, so a dynamic IP and NAT are non-issues.

4.1 VPS side (server)

apt-get install -y wireguard-tools
cd /etc/wireguard
wg genkey | tee server_private.key | wg pubkey > server_public.key

# /etc/wireguard/wg0.conf
[Interface]
Address    = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <VPS_PRIVATE_KEY>

[Peer]                       # the home Exchange
PublicKey  = <EXCHANGE_PUBLIC_KEY>
AllowedIPs = 10.8.0.2/32

systemctl enable --now wg-quick@wg0

Allow inbound UDP 51820 on the VPS firewall.

4.2 Home side (Windows Exchange): WireGuard for Windows

Install WireGuard for Windows, then Import tunnel from file so it shows in the app and runs as an auto-starting service:

[Interface]
PrivateKey = <EXCHANGE_PRIVATE_KEY>
Address    = 10.8.0.2/24

[Peer]                       # the VPS
PublicKey           = <VPS_PUBLIC_KEY>
Endpoint            = 203.0.113.10:51820
AllowedIPs          = 10.8.0.1/32
PersistentKeepalive = 25

AllowedIPs = 10.8.0.1/32 keeps only tunnel traffic in the tunnel, so normal networking is untouched. Verify from the VPS that it can reach Exchange's SMTP over the tunnel: nc -zv 10.8.0.2 25 should return the Exchange banner.

WireGuard Windows app, tunnel active
The tunnel managed from the WireGuard app, handy for later troubleshooting.

5. Part 3: Inbound relay with store-and-forward

Now make the VPS accept mail for the domain and relay it to Exchange over the tunnel. Because Postfix queues and retries automatically, if the home server is temporarily offline the VPS holds the mail and delivers it when the tunnel comes back: a real store-and-forward buffer.

If the domain was previously a local mail domain on the VPS (e.g. under a control panel), first disable local mail for it so it isn't delivered locally, then configure the relay:

# /etc/postfix/transport   ->  route the domain to Exchange over the tunnel
echo "example.com  smtp:[10.8.0.2]:25" > /etc/postfix/relay_transport
postmap /etc/postfix/relay_transport

postconf -e "relay_domains = example.com"
postconf -e "transport_maps = hash:/etc/postfix/relay_transport, \$transport_maps"

postfix check && postfix reload

On the Exchange side, its default anonymous receive connector on port 25 already accepts mail for an authoritative accepted domain, so mail arriving over the tunnel is delivered straight to the mailbox. Test by sending to the VPS's port 25 and watching for relay=10.8.0.2[10.8.0.2]:25 ... status=sent (250 ...), then confirm the message lands in the mailbox.

Shared-server caution. If the VPS runs a control panel that manages Postfix, relay_domains / transport_maps may be reverted by a "repair mail" operation. Keep a backup of main.cf and re-apply if needed, and always run postfix check before reloading on a server that hosts other domains.

6. Part 4: Dynamic DNS on Windows

Mail no longer depends on the home IP (the MX points to the VPS). But if you want to reach the mailbox from outside (OWA / mobile ActiveSync) directly, you still need a name that tracks the changing home IP. A tiny DuckDNS updater on the Exchange box handles it.

REM  C:\DuckDNS\duck.bat
@echo off
C:\Windows\System32\curl.exe -k -s -o C:\DuckDNS\duck.log ^
 "https://www.duckdns.org/update?domains=myexchange&token=YOUR_DUCKDNS_TOKEN&ip="
REM  Run it every 5 minutes as SYSTEM (PowerShell, one time)
$a = New-ScheduledTaskAction -Execute "C:\DuckDNS\duck.bat"
$t = New-ScheduledTaskTrigger -Once -At (Get-Date)
$t.Repetition = (New-ScheduledTaskTrigger -Once -At (Get-Date) `
      -RepetitionInterval (New-TimeSpan -Minutes 5)).Repetition
$p = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "DuckDNS Update" -Action $a -Trigger $t -Principal $p

C:\DuckDNS\duck.log shows OK on success, so it's easy to check any time.

DuckDNS domain tracking the home IP
A free dynamic-DNS host tracking the residential IP, updated every 5 minutes.

7. Part 5: Public DNS records

The mail entry point is the VPS's static IP, so every record is static. Client-access names point to the DuckDNS host so OWA/mobile still reach home.

TypeHostValuePurpose
Amx203.0.113.10MX target = VPS
MX@10 mx.example.commail enters at the VPS
CNAMEmail, autodiscovermyexchange.duckdns.orgclient access โ†’ home
TXT@v=spf1 ip4:203.0.113.10 -allSPF (authorize the VPS)
TXTdefault._domainkeyv=DKIM1; h=sha256; k=rsa; p=YOUR_PUBLIC_KEYDKIM public key
TXT_dmarcv=DMARC1; p=none; rua=mailto:you@example.comDMARC (monitor)
SRV_autodiscover._tcp10 10 443 autodiscover.example.comOutlook autodiscover
Note. The MX target must be an A record (mx.example.com), never a CNAME. Keep the DKIM public key on one line. Start DMARC at p=none and only move to quarantine/reject once you've confirmed all legitimate mail aligns on SPF and DKIM.
DNS zone records
The published zone. All records static except the DuckDNS host, which self-updates.

8. Part 6: Verification

Confirm the live zone from the authoritative nameservers and from public resolvers:

# MX + SPF + DKIM as the world sees them
dig +short MX example.com
dig +short TXT example.com
dig +short TXT default._domainkey.example.com

# From the VPS: cryptographically confirm DKIM public == private
opendkim-testkey -d example.com -s default -vvv     # -> "key OK"

A deliverability checker (e.g. MXToolbox) should show the MX published and SPF/DKIM/DMARC present. Finally, send a real message to a strict provider (Gmail/Outlook) and confirm it lands in the Inbox with SPF and DKIM passing. For a brand-new sending IP, marking the first message "not spam" trains the recipient's filter quickly.

Deliverability check
External verification of MX, SPF, DKIM and DMARC.

9. Results & lessons