Andrew Mercer
on this page

Confirm you are not an open relay

This is the single highest-priority check for any internet-facing Postfix server (see the DNS/routing fundamentals doc for why an open relay is such a serious problem). Critically, test from outside your own trusted network — connecting from inside mynetworks will always succeed and tells you nothing about how the server behaves to the rest of the internet.

Practical ways to test: - Third-party open-relay testing services (search for "test mail server open relay" — the specific services referenced in the original notes are no longer reliably available, so use a currently-operating checker rather than a specific decade-old URL). - Manually, from an external host/network you control, via telnet — attempt to send mail through your server, from an external sender, to an external (non-local) recipient:

telnet your-server.example.com 25
EHLO test.example.org
MAIL FROM:<someone@completely-unrelated-domain.com>
RCPT TO:<someone-else@another-unrelated-domain.com>

If that RCPT TO gets accepted (250), and you're not authenticated and not connecting from a trusted network, you are an open relay — go fix smtpd_recipient_restrictions/smtpd_relay_restrictions immediately (see the relaying and virtual-domains docs for what a proper restriction chain looks like, anchored on reject_unauth_destination).

Greylisting

Covered conceptually in the email-fundamentals anti-spam doc — here's the Postfix-side mechanics, via postgrey:

sudo yum install postgrey      # or: sudo apt install postgrey

Postgrey runs as its own daemon with a Unix socket that's wired into Postfix's smtpd_recipient_restrictions as a policy service (typically check_policy_service unix:postgrey/socket, added to the restriction chain). Once running, you'll see log pairs like:

postfix/smtpd[31801]: connect from mail.example.net[203.0.113.92]
postgrey[31483]: action=greylist, reason=new, client_name=mail.example.net, client_address=203.0.113.92, sender=someone@example.net, recipient=you@yourdomain.com
postfix/smtpd[31801]: NOQUEUE: reject: RCPT from mail.example.net[203.0.113.92]: 450 4.2.0 <you@yourdomain.com>: Recipient address rejected: Greylisted

That 450 4.2.0 temporary rejection is expected and correct behavior on first contact — a legitimate sender's MTA retries later and gets through.

Whitelisting known-good senders

Some legitimate senders use large, rotating pools of outbound IPs for a single message (retrying from a different IP than the original attempt), which breaks greylisting's core assumption. For known senders like this, whitelist explicitly:

# /etc/postfix/postgrey_whitelist_clients.local
smtp.knownsender.example.com
service postgrey restart

If whitelisting doesn't seem to take effect, verify postgrey's startup options actually reference the whitelist file — this needs to be passed as a startup argument, not just have the file exist:

# postgrey init/service config
options="--unix=/var/spool/postfix/postgrey/socket --whitelist-clients=/etc/postfix/postgrey_whitelist_clients.local"

Header/body content filtering

For blocking a specific, identifiable pattern of unwanted mail (e.g. a spam campaign using a consistent subject line) without the overhead of a full content-scanning engine:

# /etc/postfix/main.cf
header_checks = regexp:/etc/postfix/header_checks
mime_header_checks = regexp:/etc/postfix/mime_header_checks
# /etc/postfix/header_checks
/^Subject:.*SALE OFF*/           REJECT **SPAM** matched sale-spam pattern
/^Subject:.*UPS notification/    REJECT **SPAM** matched UPS-notification-spam pattern
service postfix restart
tail -F /var/log/maillog

A matching message produces a log line like:

reject: header Subject: SALE OFF: Pharmacy store! from ...: 5.7.1 **SPAM** matched sale-spam pattern

and the sender receives a bounce containing your custom rejection text.

This approach is a blunt, reactive instrument — good for stamping out one specific, currently-active spam pattern quickly, bad as a general anti-spam strategy (it does nothing against anything that doesn't match your specific regexes, and needs manual upkeep as patterns change). For general-purpose filtering, a real content-analysis engine (SpamAssassin, rspamd) wired in as a content filter is the appropriate tool — header_checks is worth keeping as a fast, targeted escape valve alongside it, not a replacement for it.

General hardening checklist

Beyond the specific mechanisms above, a reasonable baseline for exposing Postfix to the internet: - [ ] Confirmed not an open relay (test from outside your network, per above) - [ ] smtpd_recipient_restrictions includes reject_unauth_destination and rejects on reject_non_fqdn_sender / reject_non_fqdn_recipient / reject_invalid_hostname at minimum - [ ] Greylisting (or equivalent) enabled for unauthenticated inbound mail - [ ] At least one RBL check in the restriction chain (see the email-fundamentals doc) - [ ] TLS enabled both directions, smtpd_tls_security_level = may at minimum (see the TLS doc) - [ ] SASL authentication required for any relaying beyond mynetworks (permit_sasl_authenticated alongside permit_mynetworks, never a bare permit before reject_unauth_destination) - [ ] SPF/DKIM/DMARC published correctly for every domain this server sends as (see the email-fundamentals doc — this is a DNS-side check, not a Postfix config check, but it's just as important to your actual deliverability) - [ ] Logs actually monitored — see the troubleshooting doc's egrep '(warning|error|fatal|panic):' pattern as a starting point for a regular/automated check, rather than only looking when something's visibly broken