Postfix logs fatal: socket: Too many open files or begins silently dropping connections when per-process file descriptor limits are too low. The default soft limit on many distributions is 1024, which is adequate for a development mail server but insufficient for any production MTA handling concurrent SMTP sessions and active queue processing simultaneously.

The most common operator mistake is editing /etc/security/limits.conf, restarting Postfix, and finding the limit unchanged. On systemd-managed distributions, services do not read PAM limits. The effective file descriptor limit for a systemd service comes from the unit file’s LimitNOFILE directive, the global DefaultLimitNOFILE in /etc/systemd/system.conf, or the kernel’s compiled-in default. Editing only one layer leaves the limit silently unchanged.

Where the limit comes from

Three layers can set the per-process file descriptor limit for Postfix. Which one wins depends on how the process was started.

LayerMechanismApplies when
Kernelfs.file-max (system-wide total, not per-process)Always; caps total open FDs across all processes
systemdLimitNOFILE in unit file or DefaultLimitNOFILE in system.confPostfix started by systemd (the default on modern distros)
PAM/etc/security/limits.conf with nofile entriesPostfix started from a login shell or legacy init script

systemd overrides PAM for services it manages. When systemd starts the Postfix master process, it applies the LimitNOFILE value from the unit file (or the DefaultLimitNOFILE from /etc/systemd/system.conf if the unit file does not specify one). It does not consult /etc/security/limits.conf at all. The master process then passes its limits to all child daemons it spawns: smtpd, smtp, cleanup, qmgr, bounce, and so on.

The kernel’s fs.file-max is a separate constraint. It caps the total number of open file descriptors across all processes system-wide. Even if you set LimitNOFILE=1000000 on the Postfix unit, the system can still run out of file descriptors globally if fs.file-max is too low.

flowchart TD
    A["Postfix master starts"] --> B{"Launched by systemd?"}
    B -->|"Yes (modern distros)"| C["Unit LimitNOFILE or
DefaultLimitNOFILE from system.conf"] B -->|"No (init script / shell)"| D["PAM limits.conf or
inherited shell ulimit"] C --> E["Per-process RLIMIT_NOFILE
set at exec time"] D --> E F["Kernel fs.file-max"] --> G["System-wide total FD cap
across all processes"] E --> H["Effective limit for
master + all child daemons"] G -.->|"bounded by kernel total"| H

A compile-time constraint also exists but is irrelevant on modern systems. Postfix versions before 2.4 required recompilation with a larger FD_SETSIZE to support more than 1024 file descriptors per process. Postfix 2.4 and later use scalable I/O multiplexing (epoll on Linux 2.6+, kqueue on BSD) and are not constrained by FD_SETSIZE. If you are running Postfix 3.x on any modern Linux kernel, the runtime limit is the only constraint.

Prerequisites

  • Postfix running on a systemd-managed Linux distribution (CentOS/RHEL 7+, Debian 8+, Ubuntu 16.04+)
  • root or sudo access
  • Knowledge of your current default_process_limit (check with postconf -h default_process_limit)

Procedure

Step 1: Check the current limit

Confirm what limit the Postfix master process actually has before changing anything.

# Check the master process file descriptor limit
MASTER_PID=$(cat /var/spool/postfix/pid/master.pid)
cat /proc/$MASTER_PID/limits | grep "Max open files"

# Alternative using prlimit
prlimit -p $MASTER_PID --nofile

The output shows both a soft limit and a hard limit. The soft limit is what the kernel enforces at runtime. Postfix does not raise its own limit programmatically, so the soft limit is the operative constraint. A soft limit of 1024 is the default on many distributions and is almost certainly too low for production.

Step 2: Raise the limit via systemd drop-in

Use systemctl edit to create a drop-in override rather than editing the packaged unit file directly. Package updates (RPM or DEB) can overwrite /usr/lib/systemd/system/postfix.service, silently reverting your changes. Drop-in overrides in /etc/systemd/system/postfix.service.d/ survive package updates.

# Create or edit the drop-in override
systemctl edit postfix.service

In the editor, add the following:

[Service]
LimitNOFILE=65536

This sets both the soft and hard limits to 65536. If you want different soft and hard values, use the colon syntax: LimitNOFILE=1024:65536.

Avoid LimitNOFILE=infinity. On older systemd versions, infinity resolves to 65535, not a truly unlimited value. Set an explicit numeric limit regardless of version.

After saving the drop-in:

# Reload systemd to pick up the override
systemctl daemon-reload

# Restart Postfix (reload is not enough; LimitNOFILE is applied at process start)
# WARNING: this drops all active SMTP connections
systemctl restart postfix

A postfix reload is not sufficient here. LimitNOFILE is set by systemd at exec time when the master process starts. Only a full restart causes systemd to re-exec the process with the new limit.

Step 3: Raise the kernel-wide cap if needed

Check whether the system-wide limit is adequate for your total FD budget across all processes, not just Postfix.

# Check current system-wide limit
sysctl fs.file-max

# Check current system-wide usage
cat /proc/sys/fs/file-nr
# Output columns: allocated  unused-allocated  system-max

If fs.file-max is too low relative to your expected total usage, raise it persistently:

echo "fs.file-max = 2097152" > /etc/sysctl.d/99-postfix-fd.conf
sysctl -p /etc/sysctl.d/99-postfix-fd.conf

This change takes effect immediately and survives reboots. No Postfix restart is needed for kernel parameter changes, but the per-process limit still needs to come from step 2.

Step 4: Handle non-systemd Postfix installations

If Postfix is not managed by systemd (legacy init script, manual start from shell, or container without systemd), use PAM limits instead.

Edit /etc/security/limits.conf:

postfix  soft  nofile  65536
postfix  hard  nofile  65536

Then restart Postfix from a session that has loaded the new PAM limits. This typically means logging out and back in, or starting a new login shell before running postfix start. The root user starting Postfix must also have the limit applied; add entries for both root and postfix if root starts the master process.

In containers, the limit is usually inherited from the container runtime or the host’s default cgroup settings. Check with cat /proc/1/limits | grep "Max open files" inside the container. You may need to set the limit in the container runtime (Docker’s --ulimit nofile=65536:65536 or the equivalent in your orchestrator).

Verifying it works

After restarting Postfix, confirm the limit propagated to both the master and child processes.

# Verify master process limit
MASTER_PID=$(cat /var/spool/postfix/pid/master.pid)
cat /proc/$MASTER_PID/limits | grep "Max open files"

# Verify a child smtpd process (child inherits master's limits)
# Note: smtpd processes only exist when there are active or recent connections
SMTPD_PID=$(pgrep -x smtpd | head -1)
if [ -n "$SMTPD_PID" ]; then
  cat /proc/$SMTPD_PID/limits | grep "Max open files"
else
  echo "No smtpd process currently running"
fi

Check current FD usage to confirm you have headroom:

# Count open FDs per Postfix process, sorted by usage
for pid in $(pgrep -f postfix); do
  count=$(ls /proc/$pid/fd 2>/dev/null | wc -l)
  name=$(cat /proc/$pid/comm 2>/dev/null)
  echo "$count $name (pid $pid)"
done | sort -rn

# Total FDs across all Postfix processes
for pid in $(pgrep -f postfix); do
  ls /proc/$pid/fd 2>/dev/null | wc -l
done | awk '{s+=$1} END {print s}'

Sizing file descriptor limits

File descriptor consumption in Postfix comes from multiple sources per daemon process. Each smtpd or smtp process typically consumes:

  • 1 FD per accepted client connection
  • 1+ FDs for queue file access during message processing
  • Variable FDs for lookup table connections (LDAP, MySQL, PostgreSQL, hash maps)

The master process also holds 1 FD per listening socket (port 25, 587, etc.) and 1 FD per child process for IPC.

The sizing heuristic is:

LimitNOFILE >= (default_process_limit * FDs_per_process) + headroom_for_lookups + queue_files

With default_process_limit at its default of 100, and assuming 3-5 FDs per process plus lookup table connections, a minimum of 4096 is reasonable for low-to-moderate volume sites. For high-volume relays with 500+ concurrent processes or heavy use of SQL/LDAP lookup tables, 65536 is a common production setting.

# Check your process limit
postconf -h default_process_limit

# Check per-service maxproc settings in master.cf
postconf -M | awk '{print $1, $2, $7}'  # service, type, maxproc

Factor in the active queue depth. Messages in the active queue are being actively processed, and each may hold file descriptors open during delivery. If your active queue routinely runs at thousands of messages, ensure your FD limit accounts for that concurrent access, not just the process count.

Common pitfalls

Editing the packaged unit file directly. Files under /usr/lib/systemd/system/ are owned by the package manager and will be overwritten on the next Postfix package update. Always use systemctl edit postfix.service to create a drop-in under /etc/systemd/system/postfix.service.d/.

Using LimitNOFILE=infinity. The effective limit from infinity varies by systemd version and may resolve to 65535 on older releases. Set an explicit numeric limit instead.

Only raising the soft limit. If the hard limit remains at 1024 and a subprocess tries to raise its soft limit, it cannot exceed the hard limit. systemd’s LimitNOFILE=65536 sets both soft and hard to the same value, which is the simplest approach.

Confusing fork failures with FD exhaustion. On systems using cgroup v2 (the default on most modern distributions), the cgroup’s pids.max limit can cause fork failures that produce similar symptoms: “unable to fork” in logs, new connections refused, processes not spawning. If you raise LimitNOFILE and the problem persists, check the cgroup PID limit:

# Check the current cgroup PID limit for Postfix (cgroup v2)
cat /sys/fs/cgroup/system.slice/postfix.service/pids.max 2>/dev/null
# Check process/thread counts
cat /proc/$(cat /var/spool/postfix/pid/master.pid)/status | grep Pid

If pids.max is set low, raise it in the systemd unit:

[Service]
LimitNOFILE=65536
TasksMax=infinity

Forgetting systemctl daemon-reload. After creating or modifying a drop-in, systemd must reload its configuration. Without daemon-reload, the restart uses the old limit.

Restarting only the master, not the children. When you restart Postfix via systemd, the master process and all children restart. If you manually kill and restart only the master, old child processes may retain their old limits. Always use systemctl restart postfix for a clean restart.

Monitoring FD utilization

If you run Netdata alongside Postfix, the relevant correlations are:

  • Per-process FD counts. Netdata collects open FD counts per process. Watch smtpd and smtp trends relative to the soft limit to catch growth before it becomes an outage.
  • System-wide FD utilization. Netdata tracks fs.file-nr against fs.file-max, which tells you whether the kernel-wide cap is the binding constraint rather than the per-process limit.
  • Queue depth correlation. Cross-referencing FD usage spikes against active and deferred queue sizes distinguishes backlog-driven growth from connection-volume-driven growth.
  • Process counts vs. maxproc. If smtpd process counts approach the per-service limit, FD pressure follows predictably since each process holds multiple descriptors.