Lesson 002 · Linux MTA Operations Learning Path

Install Postfix on RHEL 9 or 10 and Build a Safe SMTP Baseline

· Published · 9 min read

Labelled Linux MTA course diagram highlighting RHEL package installation Postfix processes configuration queue validation and rollback

The first Postfix milestone is intentionally small: a packaged, patched service that accepts only controlled local test mail and whose configuration can be explained. Public exposure, virtual domains, SASL and milters come later. This prevents a half-built server from becoming an open relay while the operator learns how the master process, smtpd, cleanup, queue manager and delivery agents cooperate.

Prerequisites and inherited lab checkpoint

Complete Lesson 1 and retain its architecture contract, relay matrix and baseline listeners. The host must have a stable name, synchronized clock, approved repositories, SELinux enforcing and console access. Use mail1.example.test; do not substitute a public domain until the full policy and abuse controls are ready.

If Sendmail is present on RHEL 9, identify why before changing the system MTA. RHEL 10 removes Sendmail from the distribution and recommends Postfix. A service replacement requires an application inventory because local jobs may depend on the sendmail command interface even when no network listener is intended.

Install and prepare the required components

sudo dnf info postfix
sudo dnf install -y postfix s-nail
rpm -q postfix s-nail
rpm -V postfix
postconf mail_version
postconf -m
sudo systemctl enable --now postfix
  • Install from approved RHEL repositories and retain package versions. Do not compile a random Internet build to obtain one feature without accepting its patch and support ownership.
  • postconf -m lists map types supported by the installed build. RHEL 10 removed Berkeley DB support and uses LMDB by default, so do not copy hash: assumptions from another platform.
  • s-nail provides a simple local test client. It does not replace an SMTP transcript for network policy testing.
  • Starting the package before firewall exposure is acceptable only because the lesson keeps the service local and verifies inet_interfaces before opening a zone.

Create a minimal local-only main.cf baseline

Back up configuration with metadata, generate a parameter inventory, and use postconf -e for the small initial baseline. The package file contains many documented defaults; acceptance should be based on postconf -n, which shows non-default settings actually interpreted by Postfix.

sudo install -d -m 0700 /root/mta-change-backups/lesson-02
sudo cp -a /etc/postfix/main.cf /etc/postfix/master.cf /root/mta-change-backups/lesson-02/
sudo postconf -e 'myhostname = mail1.example.test'
sudo postconf -e 'mydomain = example.test'
sudo postconf -e 'myorigin = $myhostname'
sudo postconf -e 'inet_interfaces = loopback-only'
sudo postconf -e 'inet_protocols = all'
sudo postconf -e 'mydestination = $myhostname, localhost.$mydomain, localhost'
sudo postconf -e 'mynetworks = 127.0.0.0/8, [::1]/128'
sudo postconf -e 'relay_domains ='
sudo postconf -e 'smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination'
sudo postfix check
sudo systemctl reload postfix

mynetworks is deliberately loopback-only. Do not insert a whole office, cloud or container subnet merely because clients receive “relay denied.” Those clients need an authenticated submission design or a narrowly controlled application relay path.

File or commandResponsibilityOperator rule
/etc/postfix/main.cfGlobal and service-default parametersChange one reviewed behavior at a time; validate before reload
/etc/postfix/master.cfServices, listeners and per-service overridesKeep submission-specific AUTH and TLS policy here
postconf -nEffective non-default configurationCapture before and after every change
postfix checkBasic configuration and permission checksRequired but not an end-to-end acceptance test
Mail log and queue IDRuntime decision pathCorrelate behavior; do not infer success from active service state

Verify the working path

sudo postfix check
sudo postconf -n
sudo postconf -M
sudo ss -lntp | grep ':25 '
sudo systemctl status postfix --no-pager
printf 'Lesson 2 local delivery test\n' | mail -s 'Postfix baseline' root
sudo postqueue -p
sudo journalctl -u postfix --since '-10 minutes' --no-pager
  • The listener must be bound only to loopback at this checkpoint. If it is reachable on a public address, stop and correct inet_interfaces.
  • postfix check can detect some syntax, ownership and directory problems. It cannot prove relay restrictions, DNS behavior or successful mailbox access.
  • The local message should produce a queue ID and a terminal delivery status. On current RHEL logging may be in the journal or a configured mail log; identify the authoritative source.
  • An empty queue after a test is meaningful only when logs show why the message left it. It could have delivered, bounced or been removed.

Production decisions before continuing

DecisionChoose deliberatelyEvidence to retain
Package sourceUse supported RHEL packages unless a documented requirement and patch owner justify another source.Repository, version and update owner
Network exposureRemain loopback-only until DNS, firewall, recipient and relay policies are built.Socket and external negative connection test
Protocol familyUse both IPv4 and IPv6 only when DNS, routing, firewall and reputation are managed for both.A/AAAA, PTR and route evidence
Local destinationKeep system-host delivery separate from later virtual mailbox domains.Explicit mydestination and virtual domain inventory
Configuration deploymentUse versioned, secret-safe configuration and staged validation.Diff, syntax output, change record and rollback copy

Understand what the packaged service starts

The Postfix master process reads master.cf and starts restricted services as needed. An inbound smtpd process handles the SMTP conversation. The cleanup service normalizes content and creates a queue file; the queue manager schedules delivery; delivery agents such as smtp, local or later Dovecot LMTP handle the next hop. Process separation limits privilege and failure scope, but unsafe policy can still make the whole service relay mail incorrectly.

main.cf parameters can expand other parameter values. Quote shell commands so Bash does not expand Postfix dollar expressions before writing them. Always inspect postconf -n after scripted changes. A copied configuration can also reference unavailable map types, paths or services, which is why capability discovery precedes activation.

The baseline does not claim production readiness. It creates a known-good control point for later lessons. Public port 25, virtual recipients, TLS identities, authentication and filtering each receive separate positive and negative tests.

Understand the component before configuring it

LayerQuestion to answerEvidence
masterWhich services may start and with which overrides?postconf -M, process list and listeners
smtpdWhich SMTP commands and envelopes are accepted?Transcript, restriction decision and queue ID
cleanupHow is a message prepared for the queue?Cleanup log and queue file creation
qmgrWhich delivery is scheduled next?Active/deferred queue and delivery log
delivery agentWas the next hop local, SMTP or another transport?Agent-specific status and enhanced code

Build it step by step

  1. Inventory first. Record release, package sources, current MTA alternative, listeners and application dependencies.
  2. Install supported packages. Verify package ownership and supported map types before referencing paths or databases.
  3. Back up with permissions. Keep the original main and master configuration in a root-only checkpoint.
  4. Set identity and boundary. Use reserved lab names, loopback listeners and explicit trusted networks.
  5. Validate offline. Run postfix check and inspect the effective diff before reload.
  6. Test locally. Submit one identifiable message, retain its queue ID and follow its terminal state.
  7. Prove denial by design. Confirm there is no external listener and no broadened relay trust before continuing.

Operate and inspect the component

postconf -d myhostname mydestination mynetworks
postconf -h myhostname
postconf -M
postconf -F
postconf -m
postfix status
postqueue -p
postcat -q QUEUE_ID
journalctl -u postfix --since '-15 minutes' --no-pager
  • postconf -d shows defaults; postconf -n shows explicitly set non-defaults; postconf -h prints a value without the parameter name.
  • postcat can expose full headers and body. Use it only for an authorized queue ID and redact evidence before sharing.
  • Never paste a placeholder such as QUEUE_ID into a destructive queue command. Resolve and review the exact identifier first.
  • Use a reload for validated configuration that supports it. Restart only when required and after understanding connection and queue impact.

Evidence and acceptance criteria

EvidenceHealthy resultFailure meaning
PackagesExpected signed RHEL package and version installedUnsupported source or incomplete transaction
Effective configurationOnly reviewed non-default parameters appearHidden drift or copied configuration remains
ListenerTCP/25 is loopback-onlyPremature exposure or wrong service owns the port
Local testQueue ID reaches a documented terminal resultSubmission, queue or local delivery is unresolved
Relay boundaryOnly loopback is trusted and no external listener existsThe baseline may relay or expose unfinished policy

A service can be active and still be wrong

An operator installs Postfix and sees active (running), then opens port 25. A copied main.cf trusts an entire private cloud network and declares a business domain in both local and virtual destination classes. Some messages relay unexpectedly while others loop. The process health check never detects either policy error.

The controlled recovery closes external exposure, restores the reviewed loopback baseline, runs postfix check, compares postconf -n, and sends one local trace message. Domain classes and relay trust are then introduced in their dedicated lessons with negative tests. Service state becomes one piece of evidence, not the definition of health.

Troubleshooting by symptom

SymptomInspect firstDefensible next action
Postfix fails to startpostfix check, journal and port ownershipCorrect syntax, permissions or conflict; do not disable SELinux
Wrong process owns port 25ss -lntp, unit state and MTA inventoryResolve the approved MTA transition before killing processes
Local test remains queuedQueue record, next-hop agent and terminal log lineRepair the responsible delivery layer and retry one item
Configuration value looks alteredShell expansion, postconf -n and backup diffRestore the intended literal value and use safe quoting
Copied map reports unsupported typepostconf -m and RHEL releaseConvert to a supported map such as LMDB and rebuild it

Unsafe operations and recovery boundaries

  • Unsafe: broadening mynetworks to silence relay errors can authorize unknown workloads to send through the host.
  • Unsafe: using postfix start-fg or manual daemons alongside systemd can create conflicting process ownership and misleading state.
  • Unsafe: replacing configuration without preserving ownership, mode and a validated rollback can prevent both startup and recovery.
  • Unsafe: reading arbitrary queued mail with postcat can disclose customer content. Access requires an operational reason and controlled evidence handling.

Rewritten knowledge checks

What does <code>postfix check</code> prove?
It performs configuration and permission checks; it does not prove end-to-end SMTP policy or delivery.
Why inspect <code>postconf -m</code>?
It shows lookup-table types supported by the installed Postfix build, preventing use of unavailable backends.
Why start with loopback-only?
It allows controlled learning and local verification without exposing incomplete recipient and relay policy.
What is the purpose of the cleanup service?
It processes accepted message content and creates the queue representation used by later Postfix services.
What does the queue manager do?
It schedules queued messages for the appropriate delivery agent and manages active/deferred flow.
Why use <code>postconf -n</code> after a change?
It shows the effective non-default configuration and exposes shell-expansion or unintended-setting errors.
Is an empty queue proof of delivery?
No. Logs must show whether the message delivered, bounced, expired or was removed.
When should Postfix be restarted instead of reloaded?
Only when the documented change requires a restart, after impact and rollback are understood.
Why retain local application dependencies during an MTA replacement?
Applications may rely on the sendmail-compatible submission interface even without a network SMTP requirement.
What is the acceptance boundary for Lesson 2?
Supported packages, explainable effective configuration, loopback-only listener, traced local message and no expanded relay trust.

Cumulative lab checkpoint

  1. Capture the pre-install MTA, listener, package and system baseline.
  2. Install Postfix and s-nail from approved repositories; record versions and supported map types.
  3. Back up configuration and apply the local-only baseline exactly once.
  4. Validate, reload and prove TCP/25 binds only to loopback.
  5. Submit a uniquely labelled local message and correlate its queue ID through the journal.
  6. Compare effective configuration to the architecture contract and preserve the checkpoint for Lesson 3.

Primary references

Advertisement