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:
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.
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
| Cause | What it looks like | First thing to check |
|---|---|---|
| Version-specific memory leak (BDAT handler, inline: table, FD leak) | RSS grows on any daemon type, no correlation with load | postconf -h mail_version against known-fixed releases |
| OpenSSL TLS session cache leak | RSS grows on smtp or smtpd with heavy TLS traffic; plaintext paths stable | openssl version and TLS log volume |
| qmgr scaling with large queue | qmgr RSS is high, active or deferred queue is enormous | Active and deferred queue file counts |
| Anvil connection table growth | anvil RSS grows with diverse client connection volume | Connection count per client IP in logs |
| Transient large-message processing | Short-lived spike on cleanup or smtp, resolves on process exit | Queue for unusually large messages |
| Misattributed memory from filter processes | Monitoring shows high RSS under Postfix user but individual daemons are small | Process 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
Identify which daemon is growing. Run the RSS check and note the process name and elapsed time (
etimescolumn). 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.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.
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.
Check the OpenSSL version for TLS session cache leaks. Postfix calls
SSL_SESSION_free()to avoid leaking TLS session objects and usesSSL_SESS_CACHE_NO_INTERNAL_STOREto 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.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.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 -rnto identify the actual memory consumers.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Per-process RSS (qmgr, smtpd, smtp, cleanup) | Direct indicator of leak versus normal footprint | Monotonic growth over hours independent of load |
| OOM killer events in kernel log | Confirms memory pressure has crossed system threshold | Any Postfix process name in dmesg OOM output |
| Active queue file count | qmgr RSS scales with active queue size | Sustained count above 10000 with rising qmgr RSS |
| Deferred queue file count | Large deferred queue inflates qmgr in-memory state | Growth exceeding drain rate over 4 or more hours |
| Process elapsed time (etimes) | Short-lived daemons should not persist beyond one session | smtpd or smtp alive for hours with growing RSS |
| TLS session establishment rate | Session cache is a known leak vector with older OpenSSL | High TLS rate correlated with RSS growth |
| System-wide available memory | Global pressure triggers OOM regardless of per-process attribution | Available memory trending toward zero |
| default_process_limit utilization | Total process count caps aggregate memory exposure | Process 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 -hon matching queue IDs. Held messages stay in the queue but are not delivered until released withpostsuper -r. - Reduce
qmgr_message_recipient_limitto 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 deferredremoves 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_timeoutorsmtp_tls_session_cache_timeoutto 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_limitis 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.
Related guides
- Postfix active queue saturation: hitting qmgr_message_active_limit
- Postfix backscatter storm: bounces to forged senders and blocklisting
- Postfix IP blocklisted: deliverability collapse and sender reputation
- Postfix bounce rate spike: 5xx failures, bad address lists, and reputation risk
- Postfix check warnings: configuration drift and permission problems
- Postfix connection refused: blocked port 25 and rejected outbound delivery
- Postfix connection timed out: delivery deferrals to unreachable destinations
- Postfix content_filter backpressure: incoming queue growth when Amavis or Rspamd slows
- Postfix deferred queue growing: why mail piles up and how to drain it
- Postfix destination concurrency limit: tuning per-destination delivery
- Postfix queue partition disk full: /var/spool/postfix out of space
- Postfix DNS resolver failure: when a broken resolver defers mail to everyone






