Postfix as a Relay, with Milters
Introduction¶
A relay (or smarthost) Postfix accepts mail from trusted sources and forwards it toward the internet — signing it, applying policy, and optionally sending through an upstream provider. This is the box your applications, MFPs, and monitoring point their SMTP at.
Relay for internal networks¶
# main.cf
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
# who may relay THROUGH us
mynetworks = 127.0.0.0/8 10.0.0.0/8 [::1]/128
# do NOT set mynetworks too wide; an open relay gets abused within hours
# we relay outbound for our own domains only (no local delivery here)
mydestination =
relay_domains =
Relay through a smarthost (with auth)¶
relayhost = [smtp.provider.example]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = static:apikey:SECRET # or hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
smtp_tls_wrappermode = no # STARTTLS on 587
postmap /etc/postfix/sasl_passwd # if using a hash: map; chmod 600 the source
The [brackets] disable MX lookup for the smarthost name.
Restriction stages¶
Postfix evaluates restrictions per SMTP phase; each returns OK/REJECT/DUNNO and you stop at the first decisive result:
smtpd_client_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unknown_client_hostname
smtpd_helo_required = yes
smtpd_helo_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_invalid_helo_hostname
reject_non_fqdn_helo_hostname
smtpd_sender_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_non_fqdn_sender
reject_unknown_sender_domain
smtpd_recipient_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unauth_destination # THE anti-open-relay rule — keep it
reject_non_fqdn_recipient
reject_unknown_recipient_domain
smtpd_relay_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unauth_destination
reject_unauth_destination (in smtpd_relay_restrictions, enabled by default in
modern Postfix) is what stops you being an open relay. Never remove it.
postscreen handles pre-queue connection filtering (zombies, pipelining) — see
the postscreen post.
Milters — the filter chain¶
Milters (mail filters) inspect/modify mail during the SMTP session. Order matters: sign after content filters, verify before.
# main.cf
smtpd_milters =
unix:/run/opendkim/opendkim.sock,
unix:/run/opendmarc/opendmarc.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock # sign locally-submitted mail too
milter_default_action = accept # if a milter is down, don't lose mail
milter_protocol = 6
OpenDKIM (sign outbound, verify inbound)¶
# /etc/opendkim.conf
Mode sv # sign + verify
Socket local:/run/opendkim/opendkim.sock
KeyTable refile:/etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
InternalHosts /etc/opendkim/TrustedHosts # which senders get signed
Selector mail
Publish the public key at mail._domainkey.example.com (TXT). See
SPF, DKIM and DMARC end to end.
OpenDMARC (verify inbound alignment)¶
# /etc/opendmarc.conf
Socket local:/run/opendmarc/opendmarc.sock
AuthservID mail.example.com
TrustedAuthservIDs mail.example.com
RejectFailures false # add an Authentication-Results header; don't reject at MTA
HistoryFile /var/run/opendmarc/opendmarc.dat
SpamAssassin / rspamd¶
spamass-milter or rspamd's milter mode — add before OpenDMARC in the chain for
inbound scanning. For a pure outbound relay you usually don't scan.
Verification and troubleshooting¶
postconf -n # non-default settings (the useful view)
postfix check
postqueue -p; mailq
echo test | sendmail -v [email protected] # verbose delivery trace
tail -f /var/log/mail.log
opendkim-testkey -d example.com -s mail -vvv
- "Relay access denied" for a legitimate sender — their IP isn't in
mynetworksand they're not SASL-authenticated. Add the network or require auth. - You became an open relay —
smtpd_relay_restrictions/smtpd_recipient_restrictionsmissingreject_unauth_destination, ormynetworksis0.0.0.0/0. Fix immediately; you'll be blacklisted fast. - DKIM signature not added — the milter socket path/permissions (postfix must
read it —
UserID opendkim:postfixor socket group), the sender isn't inInternalHosts/SigningTable, ornon_smtpd_miltersmissing for local submissions. - Mail deferred to a smarthost: "SASL authentication failed" — wrong
credentials in
sasl_passwd, forgotpostmap, orsmtp_sasl_security_optionsdisallows the mechanism the provider offers (noplaintextblocks LOGIN/PLAIN which most providers need over TLS — removenoplaintext). - Milter timeout / mail piling up — a milter process is slow or dead;
milter_default_action = acceptprevents mail loss but you lose filtering. Check the milter's own logs and socket. reject_unknown_client_hostnameblocking valid senders — strict rDNS requirement; many legit small senders fail it. Usereject_unknown_reverse_client_hostname(looser) or drop it and rely on DNSBLs/postscreen.- TLS to smarthost fails —
smtp_tls_security_level = encrypt+ the smarthost's cert chain;maydowngrades silently,verifyrequires a valid cert.
Related tools and reading¶
- On-site: SMTP test, SPF checker, DKIM checker, DMARC checker, Blacklist Checker.
- Related posts: Postfix as a null client, postscreen and spam defense.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Web & Mail Servers
Running BIND 9 as an Authoritative Server
BIND as an authoritative server: zone files, secured AXFR with TSIG, SOA serial rules, and DNSSEC inline-signing. Here's a working master/slave setup.
October 24, 2026Varnish Cache in Front of a Site
Varnish is a caching HTTP accelerator configured in VCL. Here's the request lifecycle, why you strip cookies, how bans work, and grace mode for serving stale during backend trouble.
October 24, 2026Let's Encrypt with Certbot, Including Renewal That Doesn't Break
Getting the first cert is easy; keeping renewal working for years is the part people get wrong. Webroot vs DNS-01, deploy hooks, and the failure modes.
October 24, 2026