Server

Postfix postscreen and Pre-Queue Spam Defense

By Prabath Thalangama· October 24, 2026· 4 min read
#postfix#email#anti-spam

Introduction

postscreen sits in front of Postfix's smtpd and evaluates each connecting client before a full SMTP conversation starts. Most spam comes from compromised machines running crude SMTP engines; postscreen catches them with cheap tests, so your real smtpd processes only handle plausible mail.

How it works

postscreen runs on port 25; smtpd moves to a pass port. A connecting IP is:

  • On the temporary allowlist (passed a check recently) → handed straight to smtpd.
  • On the denylist → rejected.
  • Otherwise → run the tests, then allow or reject.

master.cf

smtp      inet  n  -  y  -  1  postscreen
smtpd     pass  -  -  y  -  -  smtpd
dnsblog   unix  -  -  y  -  0  dnsblog
tlsproxy  unix  -  -  y  -  0  tlsproxy

main.cf

postscreen_access_list = permit_mynetworks, cidr:/etc/postfix/postscreen_access.cidr

# DNSBLs with weights; act on the sum
postscreen_dnsbl_sites =
    zen.spamhaus.org*3
    bl.spameatingmonkey.net*2
    b.barracudacentral.org*2
    bl.spamcop.net*1
    dnsbl.sorbs.net*1
    list.dnswl.org=127.0.[0..255].0*-2       # WHITELIST — negative weight
    list.dnswl.org=127.0.[0..255].1*-3
    list.dnswl.org=127.0.[0..255].2*-4
postscreen_dnsbl_threshold = 3
postscreen_dnsbl_action = enforce             # drop (or: ignore while testing, warn)

# Greet delay + protocol tests (pre-handshake — no false positives from real MTAs)
postscreen_greet_action = enforce
postscreen_greet_wait = ${stress?{2}:{6}}s

# Deep tests — these hold the connection; run AFTER greet passes
postscreen_pipelining_enable = yes
postscreen_pipelining_action = enforce
postscreen_non_smtp_command_enable = yes
postscreen_non_smtp_command_action = enforce
postscreen_bare_newline_enable = yes
postscreen_bare_newline_action = enforce

# Cache the results
postscreen_cache_map = btree:$data_directory/postscreen_cache
postscreen_bare_newline_ttl = 30d
postscreen_dnsbl_ttl = 1h

The tests

  • DNSBL — query several blocklists, sum the weights, reject if ≥ threshold. Weighting means one aggressive list can't blackhole legit mail alone; DNSWL (whitelist) subtracts. Spamhaus ZEN needs a registered account / paid feed for anything beyond low volume now — check current terms.
  • Greet delay (greet_wait) — postscreen speaks its 220 banner slowly. Botnet engines that don't wait and start blasting commands (or hang up) fail. Real MTAs wait.
  • Pipelining — sending commands before the server's response, pre-greeting. RFC-violating; botnets do it.
  • Non-SMTP command — sending HTTP/other garbage.
  • Bare newline — \n without \r. Real MTAs send \r\n.

The greet/deep tests are pre-SMTP — a real mail server never fails them, so there's essentially no false-positive risk from enforcing them (unlike content filtering).

Interaction with greylisting

postscreen's deferrals are a form of greylisting for unknown senders — the first connection gets a temp-fail while tests run, the retry (which real MTAs do) gets through and is now allowlisted. This delays first mail from new senders by minutes. Usually acceptable; tune *_ttl so it's a one-time cost per sender.

Verification and troubleshooting

postconf -M | grep -E 'postscreen|smtpd|dnsblog|tlsproxy'
grep postscreen /var/log/mail.log | tail -50
# per-connection outcome:
#   "PASS NEW [1.2.3.4]"   "DNSBL rank 5 for [1.2.3.4]"   "PREGREET 12 after 0.1 from [1.2.3.4]"
postconf postscreen_dnsbl_threshold postscreen_dnsbl_action
  • Legit senders rejected ("DNSBL rank N") — a blocklist is too aggressive or the sender is genuinely listed (they need to delist). Lower that list's weight, raise postscreen_dnsbl_threshold, or set postscreen_dnsbl_action = ignore while you tune (it logs but doesn't act). Add trusted partners to postscreen_access_list.
  • PREGREET rejecting a real MTA — very rare; some misconfigured senders or monitoring probes. Whitelist them, or reduce greet_wait.
  • First mail from every new correspondent delayed ~5 min — the test/greylist effect. Expected; ensure the cache TTLs are long so it's one-time.
  • Spamhaus queries returning 127.255.255.x / errors — you're over the free query limit (using a public resolver at volume) — you need a data feed subscription or a rsync'd local copy. Remove the list or subscribe.
  • postscreen cache errors / not persisting — postscreen_cache_map path permissions, or btree vs lmdb availability. postconf -m for supported map types.
  • TLS clients failing — postscreen hands TLS off to tlsproxy; if that's not in master.cf or misconfigured, STARTTLS breaks. Ensure the tlsproxy entry exists.
  • Everything still getting through — actions set to ignore/warn not enforce, or permit_mynetworks is too broad so nothing is tested.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.