Postfix daemons are disposable. Each smtpd, smtp, cleanup, or local process handles one session or message, then exits, keeping per-process RSS bounded at roughly 1 to 5 MB. When RSS climbs steadily on a qmgr process or a delivery agent that should have been recycled hours ago, you are looking at either a memory leak or a queue structure grown beyond normal operating size.

The one daemon that is legitimately long-lived is qmgr(8). It holds active queue entries, per-destination concurrency state, and recipient lists in memory. Its RSS scales with queue depth. But sustained RSS growth on short-lived daemons, or qmgr growth that does not recede after the queue drains, points at a leak heading toward the OOM killer.

The critical diagnostic question is whether growth correlates with load (queue size, message volume, TLS session count) or marches upward independent of it. The latter is a leak. The former is a capacity signal.

What this means

Postfix’s modular architecture isolates memory pressure to individual processes. A leaking smtpd does not directly starve qmgr. But under global memory pressure, the OOM killer does not respect process boundaries. It selects the largest RSS consumer, which is often the leaking daemon, and kills it. If that daemon is qmgr, mail flow stops until master respawns it.

Two distinct failure modes produce the same symptom of rising RSS:

  1. A genuine memory leak in Postfix code or a linked library. OpenSSL is the classic offender. The process allocates memory that is never freed, regardless of workload. Over hours or days, RSS climbs until the OOM killer intervenes.

  2. Queue-driven qmgr growth. The queue manager holds recipient lists and destination state in memory up to qmgr_message_recipient_limit (default 20000). A deferred queue with tens of thousands of entries inflates qmgr RSS substantially. This is a capacity signal, not a bug.

flowchart TD
    A["RSS rising on Postfix daemon"] --> B{"Which daemon?"}
    B -->|"qmgr"| C["Check queue depth vs RSS"]
    B -->|"smtp / smtpd"| D["Check process age"]
    C --> E{"Queue over 10K messages?"}
    E -->|"Yes"| F["Queue-driven: drain queue"]
    E -->|"No"| G["Suspected leak"]
    D --> H{"Same PID for hours?"}
    H -->|"No"| M["Likely transient: recheck under load"]
    H -->|"Yes"| G
    G --> I["Verify Postfix and OpenSSL version"]
    I --> J["Correlate with OOM kills in dmesg"]

Common causes

CauseWhat it looks likeFirst thing to check
Version-specific memory leak (BDAT handler, inline: table, FD leak)RSS grows on any daemon type, no correlation with loadpostconf -h mail_version against known-fixed releases
OpenSSL TLS session cache leakRSS grows on smtp or smtpd with heavy TLS traffic; plaintext paths stableopenssl version and TLS log volume
qmgr scaling with large queueqmgr RSS is high, active or deferred queue is enormousActive and deferred queue file counts
Anvil connection table growthanvil RSS grows with diverse client connection volumeConnection count per client IP in logs
Transient large-message processingShort-lived spike on cleanup or smtp, resolves on process exitQueue for unusually large messages
Misattributed memory from filter processesMonitoring shows high RSS under Postfix user but individual daemons are smallProcess ownership of Amavis, Rspamd, ClamAV children

Quick checks

These commands are safe and read-only. Run them from the Postfix host.

# RSS of all Postfix daemons, sorted by memory, with elapsed time
ps -eo pid,rss,etimes,comm | grep -E '(qmgr|smtp|smtpd|local|cleanup|anvil)' | sort -k2 -rn
# Check for OOM kills of Postfix processes in kernel log
dmesg -T | grep -iE 'out of memory|oom-kill|killed process' | grep -i postfix
# Postfix version (compare against known-fixed releases below)
postconf -h mail_version
# Queue manager memory-related limits
postconf -h qmgr_message_active_limit qmgr_message_recipient_limit default_process_limit
# Active and deferred queue sizes (uses configured spool path)
SPOOL=$(postconf -h queue_directory)
echo "active: $(find "$SPOOL/active" -type f | wc -l)"
echo "deferred: $(find "$SPOOL/deferred" -type f | wc -l)"
# Queue depth by destination (if qshape is installed; package postfix-perl-scripts on some distros)
qshape deferred | head -20
# Memory map summary of the largest Postfix process
pmap $(ps -eo rss,pid,comm | grep -E '(qmgr|smtp|smtpd)' | sort -rn | head -1 | awk '{print $2}') 2>/dev/null | tail -10
# OpenSSL version linked at runtime
openssl version
# Check for qmgr memory or allocation errors in mail log
# Note: log path is /var/log/mail.log on Debian/Ubuntu, /var/log/maillog on RHEL/CentOS
grep -iE 'error|fatal|panic|memory|out of mem' /var/log/mail.log | grep -i qmgr | tail -20

How to diagnose it

  1. Identify which daemon is growing. Run the RSS check and note the process name and elapsed time (etimes column). A qmgr with 80 MB RSS and a queue of 50000 messages is a capacity problem. An smtpd with 200 MB RSS alive for 4 hours is a leak.

  2. Determine if growth is load-correlated. Sample RSS and queue depth at two points, 15 to 30 minutes apart. If RSS tracks with queue size and falls when the queue drains, you have queue-driven growth. If RSS rises while the queue is stable or empty, you have a leak.

  3. Check your Postfix version against known-fixed releases. Several memory-related bugs were fixed in specific point releases:

    • Postfix 3.5.10 fixed a memory leak triggered by inline: table syntax errors in main.cf or master.cf. The bug was introduced in Postfix 3.4.
    • Postfix 3.8.6 fixed a BDAT command handler that could read message_size_limit bytes into memory per message. Introduced in Postfix 3.4; particularly dangerous on systems accepting inbound mail from untrusted sources.
    • Postfix 3.11.2 fixed a file descriptor leak after fork() failure (defect present since 1998), an unchecked null pointer after an out-of-memory condition in a library dependency, a buffer over-read when an enhanced status code has no trailing text, and an uninitialized pointer dereference in proxymap after a protocol error.
  4. Check the OpenSSL version for TLS session cache leaks. Postfix calls SSL_SESSION_free() to avoid leaking TLS session objects and uses SSL_SESS_CACHE_NO_INTERNAL_STORE to prevent OpenSSL from retaining sessions in its internal cache. Older OpenSSL versions, particularly in mutual TLS configurations, have known session handling leaks. If your leak correlates with TLS traffic volume, check OpenSSL changelogs for your version.

  5. Correlate with kernel OOM events. Run dmesg -T | grep -iE 'oom-kill|killed process' and look for Postfix process names. The kernel logs the killed PID and its RSS at time of kill. If Postfix processes appear here, the leak has already progressed to system-level memory pressure. The OOM killer may have been killing and respawning daemons for some time before you noticed.

  6. Rule out misattributed memory. If monitoring shows the Postfix service account consuming excessive memory but individual daemon RSS values are all small, the attribution may be wrong. Content filters (Amavis, Rspamd), ClamAV, or SpamAssassin children often run under the same user or are managed by Postfix’s master process. Check ps -eo pid,rss,user,comm | sort -k2 -rn to identify the actual memory consumers.

Metrics and signals to monitor

SignalWhy it mattersWarning sign
Per-process RSS (qmgr, smtpd, smtp, cleanup)Direct indicator of leak versus normal footprintMonotonic growth over hours independent of load
OOM killer events in kernel logConfirms memory pressure has crossed system thresholdAny Postfix process name in dmesg OOM output
Active queue file countqmgr RSS scales with active queue sizeSustained count above 10000 with rising qmgr RSS
Deferred queue file countLarge deferred queue inflates qmgr in-memory stateGrowth exceeding drain rate over 4 or more hours
Process elapsed time (etimes)Short-lived daemons should not persist beyond one sessionsmtpd or smtp alive for hours with growing RSS
TLS session establishment rateSession cache is a known leak vector with older OpenSSLHigh TLS rate correlated with RSS growth
System-wide available memoryGlobal pressure triggers OOM regardless of per-process attributionAvailable memory trending toward zero
default_process_limit utilizationTotal process count caps aggregate memory exposureProcess count near limit with RSS growth across many daemons

Fixes

Upgrade Postfix to a patched release

If your version matches a known-affected range, upgrading is the definitive fix. The memory-related fixes in 3.5.10, 3.8.6, and 3.11.2 address specific code paths that allocate without freeing or dereference uninitialized pointers under memory pressure.

# Check current version
postconf -h mail_version

Review the release notes for your branch and plan an upgrade. Changing qmgr limits requires a full restart, not a reload, so schedule a maintenance window.

Drain the queue to relieve qmgr memory

If qmgr RSS is high because the queue is enormous, draining the queue is the fix, not a code change:

  • Identify the dominant deferred destination and address the root cause. See Postfix deferred queue growing.
  • Temporarily hold mail for a problem destination using postsuper -h on matching queue IDs. Held messages stay in the queue but are not delivered until released with postsuper -r.
  • Reduce qmgr_message_recipient_limit to cap the in-memory recipient list. This causes message splitting but bounds qmgr memory. Requires a full restart, not a reload.
  • If the deferred queue contains only spam or backscatter, postsuper -d ALL deferred removes all deferred entries. This is destructive and cannot be undone. Verify the contents before running it. See Postfix deferred queue growing for safer selective deletion.

Reduce default_process_limit to cap total memory exposure

On memory-constrained systems, lowering default_process_limit (default 100) reduces the total number of concurrent Postfix processes. Each process carries its own RSS, so fewer concurrent processes means lower aggregate memory. The tradeoff is reduced delivery and reception concurrency.

# Check current limit
postconf -h default_process_limit

# Set a lower value (example: modifies main.cf)
postconf -e 'default_process_limit = 50'
postfix reload

Process limit changes take effect for new connections after reload. Existing processes continue until they complete or time out.

Address OpenSSL TLS session leaks

If the leak correlates with TLS traffic and your OpenSSL version has known session-handling issues:

  • Upgrade OpenSSL to a patched release.
  • Reduce smtpd_tls_session_cache_timeout or smtp_tls_session_cache_timeout to expire cached sessions faster, reducing cache size.
  • TCP Fast Open combined with OpenSSL has been reported to cause a memory leak in the SSL_connect state machine on some versions. If your kernel has TFO enabled and you see TLS-correlated growth, test with TFO disabled.

Restart the affected daemon as a temporary mitigation

Postfix normally recycles short-lived daemons automatically after each session. If a specific process is leaking and has not been recycled, a postfix reload forces master to re-read configuration and restart child daemons as they complete.

For qmgr specifically, which is long-lived and holds queue state in memory, a reload may not fully reset it. A full stop and start clears qmgr memory but causes a brief queue scan delay on restart as qmgr re-reads all queue directories:

# Full restart (brief downtime, qmgr rescans all queues)
postfix stop && sleep 2 && postfix start

This is a stopgap, not a fix. If the leak is in code or a linked library, RSS will climb again after restart.

Prevention

  • Monitor per-process RSS with trend detection. Static thresholds miss slow leaks. Track RSS over time and alert on sustained growth that does not correlate with queue depth or connection rate. A daemon whose RSS has not returned to baseline after 24 hours is likely leaking.
  • Keep Postfix on a current stable release. Memory-related fixes are distributed across point releases. Running an older 3.x branch means carrying known leak bugs indefinitely.
  • Record OpenSSL version alongside Postfix version. TLS session cache behavior depends on the linked OpenSSL version. Track both in your configuration inventory so you can cross-reference when a TLS-related leak is suspected.
  • Set queue depth alerts early. A deferred queue growing past 10000 messages is both a delivery problem and a memory problem for qmgr. Alert before it reaches that size.
  • Size memory for worst-case queue depth. If your qmgr_message_recipient_limit is 20000, ensure the system has enough memory for qmgr to hold that many recipient entries. The per-entry byte overhead is not documented in Postfix tuning guides. Monitor actual qmgr RSS at known queue depths to calibrate your capacity planning.

How Netdata helps

  • Netdata collects per-process RSS for every Postfix daemon at per-second resolution, making slow leaks visible hours before the OOM killer intervenes.
  • Anomaly detection flags sustained RSS growth that does not correlate with queue depth or connection rate, separating a genuine leak from a capacity spike without manual threshold tuning.
  • Queue depth metrics (active, deferred, maildrop) displayed alongside qmgr RSS show whether memory growth is queue-driven or independent of load.
  • Kernel OOM killer events correlated with Postfix process RSS in the same dashboard confirm whether memory pressure has crossed the system threshold.
  • System-wide memory utilization charts contextualize Postfix RSS against total available memory, showing how much headroom remains before OOM conditions.
  • TLS connection rate metrics help identify whether session cache growth is contributing to memory pressure on smtpd or smtp processes.