Skip to main content

Setup Postfix Relay Server (Retaining Sender Address)

·1247 words·6 mins

Postfix Relay Setup for Microsoft 365 Direct Send
#

This guide walks through building a Postfix relay server on Ubuntu 24.04 that accepts mail from trusted internal systems and relays it to Microsoft 365 (Exchange Online) as an unauthenticated, IP-verified Direct Send connector. It covers the server build, Postfix configuration, the Exchange Online connector, SPF, internal DNS changes, and a troubleshooting reference.

Note: Example values below (hostnames, domains, and IP addresses) are redacted/placeholders. Replace them with your own environment’s values.


Step 1 — Install Postfix on Ubuntu 24.04
#

  1. Update the system and install Postfix and mail utilities:

    sudo apt update
    sudo apt install postfix mailutils -y
    
  2. During the installer prompts, choose:

    • General type of mail configuration: Internet Site
    • System mail name: your domain name (e.g. example.com) — not the VM hostname
  3. Identify your Microsoft 365 tenant’s MX / relay endpoint (found in the Exchange admin center or your tenant’s MX record), e.g.:

    <tenant-name>.mail.protection.outlook.com
    

Step 2 — Configure /etc/postfix/main.cf
#

Open the configuration file:

sudo nano /etc/postfix/main.cf

Full working main.cf for this relay, as deployed (IPs redacted — replace with your own):

# Debian specific: Specifying a file name will cause the first
# line of that file to be used as the name. The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on
# fresh installs.
compatibility_level = 3.6

# TLS parameters
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_security_level=may

smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = mail.example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = example.com
myorigin = $mydomain
mydestination = $myhostname, localhost.localdomain, localhost
relayhost = [<tenant-name>.mail.protection.outlook.com]:25
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128, 10.x.x.0/16, 10.y.y.0/16
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all

Validate and apply the configuration:

sudo postfix check
sudo systemctl restart postfix
sudo systemctl enable postfix

Step 3 — Allow the Public IP in the Exchange Online Connector
#

Microsoft 365 must trust this relay’s public egress IP before it will accept unauthenticated mail from it.

  1. Identify the relay’s public egress IP:

    curl ifconfig.me
    
  2. In the Exchange admin center (or security.microsoft.com → Exchange), go to:

    • Mail flow → Connectors
  3. Open the existing inbound connector (e.g. “Inbound from <Relay Name>”) or create a new Partner Organization connector.

  4. Under “How to identify email sent from your email server,” add the relay’s public IP to the list of trusted sending IP ranges.

  5. Confirm the connector status is On.

Important: This IP must also be added separately to any Exchange transport rules that maintain their own sender-IP allow lists (e.g. a rule such as “Block Unknown Sender IP”). The connector IP list and a transport rule’s exception list are independent — being trusted by the connector does not automatically exempt a sender from transport rules.


Step 4 — Add the Public IP to the SPF Record
#

  1. Locate the current SPF TXT record for the domain.

  2. Add an ip4: entry for the relay’s public IP (ip4 mechanisms do not count against the 10 DNS-lookup SPF limit).

  3. Publish the updated record through your DNS provider, e.g.:

    v=spf1 ip4:<relay-public-ip> include:spf.protection.outlook.com -all
    
  4. Confirm the record is live after DNS propagation (e.g. via dig TXT example.com or an online SPF checker).


Step 5 — Update Internal DNS for the Relay Hostname
#

  1. In the internal DNS zone, update (or create) the A record for mail.example.com to point to the new Ubuntu 24.04 relay server’s IP address.
  2. Confirm resolution from another host on the network.
  3. Allow time for DNS cache/TTL expiry across the environment, then re-test from a client on a different subnet if possible.

Step 6 — Verifying the Relay End to End
#

From the relay server, send a test message using an accepted-domain sender address (not the bare VM hostname):

echo "Test message" | mail -s "relay test $(date)" -r someone@example.com recipient@example.com

Watch the mail log for the outcome:

sudo tail -f /var/log/mail.log

Confirm final delivery in the recipient’s Inbox (not Junk), and review the message’s Authentication-Results header to confirm spf=pass.


Troubleshooting Reference
#

Check and Restart the Postfix Service
#

sudo systemctl status postfix
sudo postfix check
sudo systemctl restart postfix
sudo systemctl reload postfix   # reloads config without dropping active connections

Note: postconf reads main.cf from disk, but running smtpd processes only pick up changes after a reload/restart. Always reload after editing main.cf before re-testing.

Tail and Search the Mail Log
#

sudo tail -f /var/log/mail.log

# Search for a specific message or recipient
grep "recipient@example.com" /var/log/mail.log

# Find rejections or deferrals
grep -E "status=(bounced|deferred)" /var/log/mail.log
grep "Relay access denied" /var/log/mail.log

Check the Mail Queue
#

mailq
sudo postqueue -f   # force flush/retry of queued mail

Add or Adjust Allowed (Whitelisted) Networks in main.cf
#

Access to relay through this server is controlled primarily by mynetworks. To add a trusted host or subnet:

sudo nano /etc/postfix/main.cf
# Single host (recommended – most restrictive):
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128, 10.x.x.x/32

# Full subnet (broader – use only when justified):
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128, 10.x.x.0/16
sudo postfix check
sudo systemctl reload postfix
postconf mynetworks   # confirm the running value

Common symptom: 554 5.7.1 Relay access denied in the log means the sending host’s IP is not covered by mynetworks (or the config change has not been reloaded yet).

Configure the Host Firewall (UFW)
#

mynetworks in main.cf controls which hosts Postfix will relay for, but it does not stop connections from reaching the box in the first place. UFW adds an independent, network-level layer restricting who can even reach port 25 (and SSH) on this server.

sudo apt install ufw -y

Set a default-deny posture:

sudo ufw default deny incoming
sudo ufw default allow outgoing

Important: Allow SSH before enabling UFW, or remote access will be lost immediately on enable.

sudo ufw allow 22/tcp
# Better: restrict SSH to a specific management subnet, e.g.
# sudo ufw allow from 10.x.x.0/24 to any port 22

Allow port 25 only from the same subnets permitted in mynetworks:

sudo ufw allow from 10.x.x.0/16 to any port 25
sudo ufw allow from 10.y.y.0/16 to any port 25

Enable and verify:

sudo ufw enable
sudo ufw status verbose

Test the Relay from a Windows Client (PowerShell)
#

Confirm port 25 is reachable from a client on the network:

Test-NetConnection -ComputerName mail.example.com -Port 25

Cross-Check Microsoft 365-Side Controls
#

If Postfix reports successful delivery (status=sent) but the message does not arrive, the issue is on the Microsoft 365 side, not the relay. Check, in order:

  • Exchange admin center → Mail flow → Message trace — search by Message-ID or sender/recipient and time
  • Quarantine (security.microsoft.com → Review → Quarantine) — check the quarantine reason (transport rule vs. spam filter)
  • Mail flow → Rules — confirm the relay’s public IP is included in any sender-IP allow/deny transport rules
  • The message’s Authentication-Results header — confirms whether SPF/DKIM/DMARC passed or failed for that specific message

Quick Reference — Where a Relay IP Must Be Updated
#

When adding a new relay server or changing this server’s IP, all three of the following must be updated together:

LocationPurpose
Exchange Online connectorGrants the IP trusted, unauthenticated relay access into the M365 tenant
Exchange transport rule exception listExempts the IP from any organization-wide sender-IP blocking rules
SPF TXT recordAuthorizes the IP as a legitimate sender for the domain, preventing Junk filtering
Kolton Corr
Author
Kolton Corr
A little bit about you