Why do I get “too many open files” and zombie processes?

Find the process leaking file descriptors with ss and lsof, then raise the limit with ulimit and make it persist across logins.

LinuxProcessesnetworkdiskportstroubleshootingnetstatMedium risk
Find the process that is actually hoarding descriptors before you touch any limit — list its sockets first, then confirm with an open-file audit.

What this covers

“Too many open files” and zombie processes usually travel together, because both come from one misbehaving service: it opens descriptors without closing them and spawns children without reaping them. That is why blindly raising the limit only buys time. This guide walks you from identifying the offending process to raising the ceiling, and then to making the change permanent so it is still there after the next reboot or login.

Symptom

?Logs and terminal sessions fill up with “Too many open files”; new connections are refused while old ones hang; cron jobs and scripts that used to work start failing. Alongside it, process listings show children stuck in state Z (zombie) whose count only grows, because their parent never collects the exit status. CPU and memory look normal, which is what makes it confusing.

See which process is holding all the sockets

Start by counting instead of guessing. The socket view lists every listening and established TCP/UDP endpoint together with the process that owns it, so a service sitting on hundreds of connections stands out immediately. That pattern points at one program leaking file descriptors, which is a very different problem from a machine-wide shortage. You are done with this step when you can name the single process responsible for most of the sockets, and you have its PID written down.

LinuxInspect open sockets with ssMedium riskStep 1

See which services listen on which ports and who is connected — the modern netstat replacement.

Command
$ sudo ss -tulpn

Confirm a descriptor leak with open-but-deleted files

A process that opens a file, unlinks it and never closes it keeps that descriptor alive forever — the file shows up with a link count of zero. If the same PID keeps appearing in this list, you have found a genuine leak rather than a limit that is simply set too low, and raising the ceiling would only postpone the failure. Expect a password prompt: inspecting another user's descriptors needs root.

When df says full but du disagrees, open-but-deleted files are the usual culprit; lsof +L1 lists them with the process holding each.

Command
$ sudo lsof +L1

Raise the descriptor ceiling in the shell that starts the service

A process inherits its limit from the shell that forks it, so the change has to be made in that shell before the service starts. Use the shell built-in that reads and writes the limit — setting it to a larger number removes the immediate 'too many open files' errors for anything launched afterwards. Print the value again in the same session to confirm it stuck; a higher number than before means the step worked, and it will be lost the moment you log out.

ulimit -n shows or changes the file-descriptor limit of the current shell, fixing 'Too many open files' errors for servers started from that shell.

Command
$ ulimit -n 4096
▾ Undo
Undo
$ ulimit -n 1024

Make the new limit survive a logout

The value you just set disappears with the shell. Editing the PAM limits file in place gives every new login session the higher ceiling, which is what you want on a server. Because an in-place edit writes straight to the file, keep a backup copy first so a typo does not lock you out of logging in. Confirm by opening a completely new session and printing the limit there; still remember that an already-running service must be restarted to pick it up.

Apply search-and-replace across one or many files on Linux with sed -i, keeping backups with -i.bak.

Command
$ sed -i 's/old-domain.com/new-domain.com/g' *.conf
▾ Undo
Undo
$ git checkout -- *.conf  (or restore from the .bak created by sed -i.bak)
!Raising a limit too high is not free — every extra descriptor is kernel memory, and a system-wide ceiling still applies above it. An in-place edit of the PAM limits file writes straight to disk, so back it up first and keep a spare root shell open; a syntax error there can block new logins. And never “fix” a leak by restarting the service on a timer and calling it done.

How to confirm it worked

Re-run the socket listing after the change and watch the offending process: its connection count stays below the new ceiling and stops climbing. New logins report the higher limit, the error stops appearing in the log, and the zombie entries disappear once their parent process has been restarted.

Undo everything

Every command above can be reverted. Run these in order to undo all of it.

Undo everything
$ git checkout -- *.conf  (or restore from the .bak created by sed -i.bak)
ulimit -n 1024

Review before running — some steps may not be independent.

Sources

  • lsof(8) - Linux manual page
    Ubuntu Manpages · technical · 2026-09-17
    Evidence: The +L1 option selects files with a link count less than one, i.e. open files that have been unlinked (deleted).
  • Sed — ArchWiki
    Arch Linux · community · 2026-09-17
    Evidence: Examples of GNU sed -i in-place editing and -i.bak backup extension usage
  • getrlimit(2)/setrlimit(2) - get/set resource limits
    man7.org Linux man-pages · technical · 2026-09-17
    Evidence: RLIMIT_NOFILE specifies a value one greater than the maximum file descriptor number that can be opened by this process; limits have a soft value (enforced) and a hard value (ceiling for the soft).
  • ss(8) — Arch Manual Pages
    Arch Linux · community · 2026-09-17
    Evidence: Same options documented for ss, another utility to investigate sockets.
  • ss(8) — Debian bookworm
    Debian Manpages · community · 2026-09-17
    Evidence: Documents -t/--tcp, -u/--udp, -l/--listening, -p/--processes and -n/--numeric for the socket statistics tool.
  • sed(1P) — POSIX manual page
    man7.org · technical · 2026-09-17
    Evidence: Documents the s/re/replacement/flags substitution, /g global flag, and address ranges

Last verified: 2026-09-22

Related guides