Config File Literacy: Postfix

How to read main.cf — every key directive explained, with relay, TLS, and queue management.

Postfix config files

Postfix lives in /etc/postfix/. The key files:

/etc/postfix/
├── main.cf          # main configuration file (most settings live here)
├── master.cf        # service definitions (which daemons run, on what ports)
├── virtual          # virtual alias maps (if using virtual_alias_maps)
├── transport        # routing exceptions (if using transport_maps)
└── sasl_passwd      # relay credentials (if using smtp_sasl_password_maps)

main.cf is where you spend most of your time. master.cf defines the process model — enabling submission (port 587), smtps (port 465), or tweaking per-daemon settings are routine master.cf tasks. Leave it at defaults otherwise.

main.cf structure

Each line is a parameter = value pair. Comments start with #. Whitespace continuation (indent next line) is supported for long values. Parameter values can reference other parameters using $parameter_name.

# This is a comment
myhostname = mail.example.com
mydomain = example.com

# Reference another parameter
myorigin = $mydomain    # equivalent to myorigin = example.com

To see the current effective value of any parameter:

postconf myhostname
postconf mydestination
postconf -d relayhost   # -d shows the compiled default

Identity directives

# The hostname this machine will use to introduce itself in SMTP
myhostname = mail.example.com

# The domain appended to unqualified addresses (user → user@mydomain)
mydomain = example.com

# What domain appears in From/envelope addresses originated here
myorigin = $mydomain

# What domains this server will accept mail FOR (deliver locally)
# IMPORTANT: list only the domains you actually host
mydestination = $myhostname, localhost.$mydomain, localhost
mydestination is critical. If your server is a relay-only host (forwarding all mail to another server), set mydestination = (empty) or only include localhost. If you list your real domain here, Postfix will try to deliver mail locally instead of forwarding it.

Network directives

# Which interfaces to listen on for incoming SMTP connections
inet_interfaces = all          # all interfaces
inet_interfaces = loopback-only  # localhost only (relay-from-app server)
inet_interfaces = 10.0.0.5     # specific IP

# Which networks are trusted to relay mail through this server
# (trusted = can send mail without authentication)
mynetworks = 127.0.0.0/8, 10.0.0.0/8

# Or auto-detect from attached networks (less secure)
mynetworks_style = host         # only localhost
mynetworks_style = subnet       # all attached subnets

mynetworks determines who can use this server as a relay without authentication. Be restrictive — only list subnets you control. An open relay (0.0.0.0/0) will be blacklisted within hours of going live.

Relay configuration

# Forward all outbound mail to this smarthost instead of delivering directly
relayhost = [smtp.example.com]:587

# [] brackets mean: do not do MX lookup on this hostname
# Postfix connects directly to smtp.example.com:587
# Without brackets, Postfix would look up MX records for smtp.example.com

# Common relay formats:
relayhost = [smtp.gmail.com]:587           # Gmail relay
relayhost = [10.0.0.2]:25                  # Internal relay by IP
relayhost = smtp.example.com               # MX lookup — less common

TLS directives

TLS for incoming connections (when Postfix receives mail):

# Incoming TLS (smtpd = server)
smtpd_tls_cert_file = /etc/ssl/certs/mail.example.com.crt
smtpd_tls_key_file  = /etc/ssl/private/mail.example.com.key
smtpd_tls_security_level = may   # offer TLS but don't require it
smtpd_tls_loglevel = 1           # log TLS connection info

TLS for outgoing connections (when Postfix sends mail or relays):

# Outgoing TLS (smtp = client)
smtp_tls_security_level = may    # try TLS, fall back to plaintext if unavailable
smtp_tls_security_level = encrypt  # require TLS (for relay to a specific smarthost)
smtp_tls_loglevel = 1
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt

smtpd_ prefixed directives control Postfix acting as a server (receiving mail). smtp_ prefixed directives control Postfix acting as a client (sending mail). This naming distinction is consistent throughout Postfix.

SASL authentication (relayhost with auth)

When relaying through a smarthost that requires authentication:

# Enable SASL auth for outgoing connections
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt   # require TLS for smarthost relay
# smtp_tls_security_level = may     # opportunistic TLS (falls back to plaintext)

Create the credentials file:

# /etc/postfix/sasl_passwd
# Format: [host]:port  username:password
[smtp.example.com]:587  relayuser@example.com:secretpassword
# Hash the file (Postfix uses the .db file, not the plaintext)
postmap /etc/postfix/sasl_passwd

# Secure the plaintext file (it contains a password)
chmod 600 /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd.db

# Reload Postfix
systemctl reload postfix

Local delivery and mailbox

# Local delivery: Maildir format (one file per message, subdirectories)
home_mailbox = Maildir/

# Local delivery: mbox format (single file, less safe)
mail_spool_directory = /var/spool/mail

# If this server is a relay-only host and should not deliver locally:
# Leave home_mailbox unset and set mydestination = empty

Lookup tables — maps

Postfix uses lookup tables (maps) for addresses, routes, and credentials. Most maps need to be compiled with postmap after editing.

# Virtual aliases — rewrite address before delivery
virtual_alias_maps = hash:/etc/postfix/virtual
# /etc/postfix/virtual:
# admin@example.com    alice@example.com
# info@example.com     alice@example.com, bob@example.com

# Canonical maps — rewrite sender/recipient addresses
canonical_maps = hash:/etc/postfix/canonical

# Transport maps — route specific domains differently
transport_maps = hash:/etc/postfix/transport
# /etc/postfix/transport:
# partner.com    smtp:[mail.partner.com]

# After editing any map file:
postmap /etc/postfix/virtual
systemctl reload postfix

Queue management

# View the mail queue
postqueue -p
mailq                # alias for the above

# Flush the queue (attempt immediate delivery of all queued mail)
postqueue -f

# Delete all mail from the queue (use carefully!)
postsuper -d ALL

# Delete deferred mail only
postsuper -d ALL deferred

# Show the content of a queued message
postcat -vq QUEUEID

When mail is stuck in the queue, read the reason in postqueue -p. Common reasons: connection refused (smarthost down), authentication failure (wrong credentials), DNS failure (relayhost hostname not resolving).

Testing configuration

# Syntax check main.cf
postfix check

# View effective config (all parameters, not just changed ones)
postconf -d            # defaults
postconf              # current effective values
postconf relayhost    # one parameter

# Send a test email
echo "test body" | mail -s "test subject" recipient@example.com

# Watch what happens in real time
tail -f /var/log/maillog
# or
journalctl -u postfix -f

Annotated main.cf — relay server

A typical relay server that receives mail from internal apps and forwards to a smarthost:

# /etc/postfix/main.cf — internal relay server

# --- Identity ---
myhostname = relay01.example.com
mydomain = example.com
myorigin = $mydomain

# Do NOT deliver locally — this server is relay-only
mydestination =
local_recipient_maps =
local_transport = error:local delivery disabled

# --- Network ---
# Accept connections only from internal networks
inet_interfaces = all
mynetworks = 127.0.0.0/8, 10.0.0.0/8

# --- Relay ---
# Forward all mail to the corporate mail server
relayhost = [mail.example.com]:25

# --- Outgoing TLS ---
smtp_tls_security_level = may
smtp_tls_loglevel = 1

# --- Queue ---
# Retry more aggressively for transient failures
maximal_queue_lifetime = 1d     # give up after 1 day (default 5d)
bounce_queue_lifetime = 1d

# --- Limits ---
message_size_limit = 52428800   # 50 MB max message size

smtpd_relay_restrictions

smtpd_relay_restrictions is the single most important anti-open-relay setting in Postfix. An open relay will forward mail for anyone, making your server a spam source. The default safe pattern:

# /etc/postfix/main.cf
smtpd_relay_restrictions =
    permit_mynetworks,           # allow hosts in mynetworks (your internal servers)
    permit_sasl_authenticated,   # allow clients that authenticated with SASL
    reject_unauth_destination    # reject everything else (prevents open relay)

# These three lines together mean: only relay mail from trusted networks
# or authenticated clients. All other relay attempts are rejected.
Open relay check: After any change to relay restrictions, test with an external tool: telnet mail.example.com 25 and try to relay mail to an external domain from an untrusted IP. The server must respond with 554 Relay access denied.

Related restriction lists

# smtpd_recipient_restrictions — controls who can receive mail
smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination,
    reject_unknown_recipient_domain   # reject mail to domains we don't host

# smtpd_sender_restrictions — controls who can send mail (envelope From)
smtpd_sender_restrictions =
    permit_mynetworks,
    reject_unknown_sender_domain

Aliases and newaliases

The aliases database maps local email addresses to other addresses, users, files, or programs. This is how root@server mail gets forwarded to a real inbox.

/etc/aliases format

# /etc/aliases
# Format: alias: destination

# Redirect root mail to the ops team mailbox
root:           ops-team@example.com

# Multiple destinations (comma-separated)
postmaster:     alice@example.com, bob@example.com

# Redirect to a local user
webmaster:      alice

# Forward to a file (appended)
archive:        /var/mail/archive

# Pipe to a program
majordomo:      "|/usr/lib/majordomo/wrapper majordomo"

# Catch-all (handle all unknown addresses for a virtual domain)
@example.com:   catchall@example.com

Rebuild the aliases database

# After editing /etc/aliases, rebuild the binary database
newaliases

# Equivalent to:
postmap /etc/aliases

Postfix main.cf directives for aliases

# Point Postfix at the aliases files
alias_maps     = hash:/etc/aliases
alias_database = hash:/etc/aliases

# For virtual alias maps (multiple domains)
virtual_alias_maps = hash:/etc/postfix/virtual
# After editing /etc/postfix/virtual:
postmap /etc/postfix/virtual

Always run newaliases after editing /etc/aliases. Changes to the text file have no effect until the binary database is rebuilt.

master.cf — submission port (587)

master.cf defines which services Postfix runs and how they listen. Port 587 (submission) is the standard port for authenticated mail clients. Unlike port 25 (SMTP), port 587 requires SASL authentication.

# /etc/postfix/master.cf
# Format: service type private unpriv chroot wakeup maxproc command

# Port 25 — SMTP between servers (active by default)
smtp      inet  n       -       n       -       -       smtpd

# Port 587 — Submission from mail clients (uncomment to enable)
submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt      # require TLS (STARTTLS)
  -o smtpd_sasl_auth_enable=yes            # require SASL auth
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

# Port 465 — SMTPS (SSL/TLS wrapper, legacy but still used)
smtps     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
# After editing master.cf, reload Postfix
postfix reload

# Verify the port is listening
ss -tlnp | grep ':587'

Port 587 lines in master.cf are usually present but commented out. Uncomment and configure them rather than creating new entries from scratch.