Andrew Mercer
on this page

Postfix isn't one program. It's a set of small, single-purpose daemons supervised by a master process. They talk to each other over local sockets, and each does one part of the mail-handling pipeline with the least privilege it can. Understanding this shape makes the rest of the configuration, and every log line, much easier to reason about.

The daemon pipeline

   network (25/587/465)               local submission (sendmail, mail, cron)
          │                                      │
   ┌──────▼───────┐                       ┌──────▼──────┐
   │ postscreen   │ (optional)            │  postdrop → │
   │   → smtpd    │                       │   maildrop/ │
   └──────┬───────┘                       │   → pickup  │
          │                               └──────┬──────┘
          └───────────────┬──────────────────────┘
                   ┌──────▼──────┐   trivial-rewrite (address
                   │  cleanup    │◄─ rewriting & routing lookups)
                   │ header/body │
                   │ checks,     │── milters (DKIM, rspamd…)
                   │ canonical/  │
                   │ virtual     │
                   └──────┬──────┘
                          ▼  incoming/ → active/
                   ┌─────────────┐
                   │    qmgr     │  queue manager: schedules delivery,
                   │             │  retries deferred mail
                   └──────┬──────┘
        ┌─────────┬───────┼─────────┬──────────┐
        ▼         ▼       ▼         ▼          ▼
     smtp(8)   lmtp(8)  local(8)  virtual(8)  pipe(8)
   (internet / (Dovecot) (system  (virtual    (scripts:
    relayhost)           users)   mailboxes)  autoreply…)
                                    │
                         bounce / defer / trace → DSNs back to the sender
Daemon Job
master The supervisor. Starts the others on demand per master.cf and enforces process limits
postscreen Optional front door on port 25: cheaply filters bots (DNSBL scoring, pregreet and protocol tests) before handing clean connections to smtpd
smtpd Speaks SMTP to incoming connections and enforces smtpd_*_restrictions
pickup Collects locally submitted mail from maildrop/
cleanup Normalizes the message, adds missing headers, applies header_checks/body_checks, canonical and virtual alias rewriting, and milters, then writes it to the queue
trivial-rewrite Address rewriting and deciding which transport and next hop a recipient uses
qmgr The scheduler. Moves messages into active/, picks a delivery agent, and handles retries and backoff
smtp / lmtp Outbound delivery over SMTP (internet or relayhost) or LMTP (to Dovecot)
local / virtual Built-in delivery to system users / virtual mailboxes
pipe Hands a message to an external program
bounce, defer, trace Generate delivery status notifications
anvil Connection and rate counters used for rate limiting
tlsmgr, scache, proxymap TLS session cache and randomness, connection cache, shared lookup-table connections

The queue directories

Every message exists as a file under /var/spool/postfix/ (queue_directory) at some point, moving between subdirectories as its status changes:

Directory Meaning
maildrop Locally submitted mail waiting for pickup
incoming Being written or just written by cleanup
active Messages qmgr is currently delivering (kept small and in memory)
deferred Temporary failure; waiting to retry with exponential backoff
hold Held indefinitely by postsuper -h or a HOLD action; nothing happens until released
corrupt Unreadable queue files
bounce, defer, trace Per-message delivery status logs used to build DSNs

Never edit or move these files by hand. Use postqueue and postsuper (see Administration and Queue Management).

The two configuration files

  • main.cf holds global parameters (name = value): identity, networks, restrictions, lookup tables, TLS. Almost everything in these guides lives here.
  • master.cf holds one line per service: which daemons listen where (smtp inet, submission inet, smtps inet), process limits, chroot, and per-service -o parameter overrides. You touch it to enable submission on 587/465, add a pipe service, or add a content filter.

Use postconf rather than an editor for single changes. It validates parameter names:

postconf -n                       # only settings that differ from defaults
postconf -d mydomain              # the compiled-in default
postconf mydestination            # current effective value
postconf -e 'myhostname = mail.domain.tld'   # set a value (edits main.cf)
postconf -M                       # master.cf services
postconf -P                       # master.cf -o overrides
postfix check                     # sanity-check config and file permissions
postfix reload                    # apply main.cf/master.cf changes without dropping connections

compatibility_level in main.cf controls which generation of safer defaults applies. A fresh install sets it to the current version. When upgrading old configs, Postfix logs which legacy default it's still using. Read those warnings, then raise the level.

Lookup tables

A large number of settings, such as virtual_alias_maps, alias_maps, smtp_sasl_password_maps, transport_maps and virtual_mailbox_maps, take a lookup table reference in the form type:name:

Type What it is
hash: / btree: Berkeley DB files compiled from a text source with postmap (or postalias for aliases)
lmdb: LMDB files compiled the same way. The replacement for hash:/btree:
cdb: Constant DB, very fast read-only files. Also a hash: replacement for static maps
texthash: Reads the text file directly at startup; no compile step. Good for small maps, but needs postfix reload after edits
regexp: / pcre: Pattern tables (header checks, address rewriting). Not compiled
inline:{ key=value, … } A tiny table written directly in main.cf
static:value Always returns the same value
mysql:, pgsql:, ldap:, sqlite: Live queries defined in a small .cf file
proxy:<type>:… Route lookups through proxymap so all processes share pooled connections (and chrooted daemons can reach the files)
unionmap:{…} / pipemap:{…} Combine tables (first match wins / chain results)

The compile step is the single most common "why isn't my change taking effect?" mistake. For any compiled table type, edit the source, then:

postmap lmdb:/etc/postfix/virtual     # or plain `postmap /etc/postfix/virtual` for the default type
postalias /etc/aliases
postfix reload                        # needed for some maps; harmless otherwise

If you forget, Postfix keeps using the old .db/.lmdb and logs database … is older than source file ….

Berkeley DB is going away

Several distributions are removing Berkeley DB from their Postfix builds, which removes hash: and btree:. Check what your system defaults to and migrate before it's forced on you:

postconf default_database_type        # hash, lmdb or cdb
postconf -m                           # which table types this build supports

Migration is mechanical: change hash: to lmdb: (or cdb:) in main.cf, re-run postmap/postalias on each source file, and postfix reload. Postfix's NON_BERKELEYDB_README covers the details, including a compatibility setting that silently redirects hash: lookups during the transition. Examples in these guides use hash: for familiarity. Substitute your default_database_type.

Core identity settings

Setting Meaning
myhostname This machine's FQDN, used in the EHLO greeting and Received: headers. It should match the sending IP's PTR record
mydomain The parent domain, derived from myhostname by default
myorigin The domain appended to unqualified addresses from this host (what turns root into [email protected])
mydestination Domains delivered locally to system accounts
mynetworks Client IP ranges trusted to relay without authentication. Keep it minimal
inet_interfaces Interfaces Postfix listens on: all, loopback-only, or specific addresses
inet_protocols ipv4, ipv6 or all. Set ipv4 on hosts without working IPv6 to avoid timeouts
relay_domains Domains this host accepts and forwards elsewhere (e.g. as a gateway for an internal server)
relayhost Send all non-local mail through this smart host instead of using MX lookups

Every other guide in this section is a combination or extension of these fundamentals for a specific goal: relaying outbound, hosting many domains, encrypting sessions, and so on.