Deep Engineering
Intermediate·Published·20 MIN

Too many open files: whose limit it is, why it is not shared, and what counts as a descriptor

"Too many open files" is usually fixed by a restart and a config edit, without anyone asking whose limit ran out. The lesson takes the mechanism apart: two limits instead of one, inheritance at startup, and the fact that a descriptor is not a file but any open object — every network connection included.

Full technical treatment

TL;DR

A descriptor is a number a process uses to refer to something it has open, and the limit bounds how many such numbers one process may hold. There are two limits and they do different jobs: the kernel checks the soft one at the moment a process opens another descriptor, while the hard one bounds how high the process may raise its own soft limit. Both belong to a process and pass to its children.

Hence the consequence that matters: "I raised the limit" and "the service got it" are different statements. A process raises its own soft limit with no restart and no new config, but it starts with whatever limit the thing that started it had: measured — the parent set itself 128 and the child started at 128. That is why ulimit -n in one shell changes nothing for a service started from somewhere else. And the limit counts occupied numbers rather than files: with a limit of 64, 61 opens succeeded, because three numbers came with the process at birth.

Beyond that is what separates knowing from having read. The error is called EMFILE and speaks of a process's limit; measured: the name EMFILE, the text "Too many open files". The machine has a ceiling of its own, and exhausting it raises ENFILE — a different error, fixed in a different place. A descriptor is not only a file: at the same limit a socket gave 61, a pipe 60 (two ends, two numbers) and an epoll instance 61, so every connection a database client or an HTTP pool holds takes a number too. And lowering the hard limit is irreversible: an unprivileged process may lower its own ceiling but not raise it back — getrlimit(2) calls the lowering irreversible, and in the run an attempt to raise the hard limit above its current value was refused.

Where to start
Before this lesson it is enough to understand
  • a program opens files and makes network connections;
  • a service under load holds many of those connections at the same time;
  • somebody starts the service: a shell, a service manager or a container.
You do not need to know in advance
  • RLIMIT_NOFILE, EMFILE, ENFILE, setrlimit;
  • what a soft and a hard limit are and how they differ;
  • epoll, inotify, the CLOSE_WAIT state.

What is actually being asked

The ladder usually runs like this:

  1. "What does 'Too many open files' mean?" — the warm-up.
  2. "Whose limit is it — the process's, the user's, the machine's?" — where the substance starts.
  3. "Soft and hard limit — what is the difference?" — whether you know that a process can change one of them itself.
  4. "What counts as a descriptor?" — whether you name sockets and epoll rather than only files.
  5. "Why did raising the limit in a shell not help?" — a question about inheritance.
  6. "How do you tell a descriptor leak from an honest shortage?" — a question about what to watch.

The numbers come from running bench/rlimits/descriptors.py and bench/rlimits/practice.py. There is not a single timing here — only descriptor counts and error names — so everything reproduces on any Linux machine except the values of the limits themselves, which are each machine's own.

Base: a descriptor is a number, not a file

Before talking about limits it is worth naming, in ordinary words, what exactly is being counted.

A descriptor is a small whole number. When a process opens a file, connects to a database or creates a pipe between itself and a subprocess, the kernel sets up the object it needs and hands the process a number. From then on the process works with the number rather than with the object: it asks to read, to write and to close by that number.

Every process has its own numbering. The table of numbers belongs to the process: number 7 in one process and number 7 in another point at completely different things. That is why what is open is counted per process rather than per machine.

Three numbers are taken before the first line of your code. They are 0, 1 and 2 — input, output and the error stream; the process receives them at birth. You did not open them, but they are occupied exactly like everything else.

And here is the question the rest of the lesson answers: how many numbers is a process allowed to hold at once, and where does that allowance come from? There is always an allowance — no table is infinite. Every process has its own, and it reaches the process not from its own code but from whoever started it. When the numbers run out, the kernel refuses to open the next one, and that is where the familiar line about too many open files appears in the log.

That is already enough to answer the basic interview question. What follows is about why there are in fact two allowances, what other than files spends numbers, and why a limit raised in a shell so often never reaches the service.

Mechanism 1: two limits, and it is not duplication

language contractBehaviour described in getrlimit(2). It does not depend on the distribution or the kernel version.

getrlimit(2) defines the pair like this:

The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may set only its soft limit to a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard limit.

getrlimit(2)

Three consequences, and all three get asked about.

The soft limit is the one checked. The kernel consults it at the moment a process opens another descriptor: not enough left, refusal. When someone says "the descriptor limit is a thousand", they almost always mean this one.

The hard limit bounds the raising, not the opening. How many descriptors a process opens is decided by the soft limit; how high the process may raise its own soft limit is decided by the hard one. And it may do that raising itself, without a restart. This is the most underused fix there is: a service that has hit its soft limit often needs neither a new config nor a restart, just one call at startup.

The hard limit only goes down. Lower it and it stays lowered unless the process holds CAP_SYS_RESOURCE; that is a property of the process, not of a setting.

The run shows all of it:

1. TWO LIMITS, AND ONLY ONE OF THEM IS ENFORCED
-----------------------------------------------
  soft limit (the one the kernel enforces)     20000
  hard limit (the ceiling for the soft one)    20000
  descriptors this process holds right now     3
  after lowering the soft limit to 256         256
  raised back to the hard limit, no restart    20000
  raising the hard limit                       refused: ValueError
measured observationbench/rlimits/descriptors.py, Linux 6.18.44. The 20000 is a property of this machine; yours will differ. What reproduces is the rest: the soft limit rises to the hard one, and the hard one does not rise above itself.

Note the third line: the process already holds three descriptors — input, output and the error stream. They count too — and they are exactly why the next section arrives at 61 rather than 64.

Mechanism 2: EMFILE is about a process, not a machine

getrlimit(2) says plainly what happens when the limit is reached:

This specifies a value one greater than the maximum file descriptor number that can be opened by this process. Attempts (open(2), pipe(2), dup(2), etc.) to exceed this limit yield the error EMFILE.

getrlimit(2)

The key words are this process. There is a system-wide ceiling as well, but it raises a different error, and open(2) separates them explicitly: EMFILE is "the per-process limit on the number of open file descriptors has been reached", ENFILE is "the system-wide limit on the total number of open files has been reached".

In practice that is the first thing to establish from a log: which of the two letters. EMFILE means one process is at fault and the cure is at process level. ENFILE means a shared resource of the machine ran out: proc(5) describes it as /proc/sys/fs/file-max, the file that "defines a system-wide limit on the number of open files for all processes". Editing one service's limit will not help there.

Now the exhaustion itself. A child sets its soft limit to 64 and opens files until the kernel refuses:

2. WHAT HITTING THE LIMIT LOOKS LIKE
------------------------------------
  soft limit set by the child                  64
  descriptors the child already had            3
  files it managed to open                     61
  error name                                   EMFILE
  error message                                Too many open files
measured observationbench/rlimits/descriptors.py. A limit of 64, three descriptors held from birth, 61 opens — the arithmetic adds up and reproduces.

61, not 64. Three descriptors were already there, and they count against the limit. At 64 that looks like nitpicking; it stops looking like it when the subject is a connection pool, because the limit counts everything open, not everything you opened.

Mechanism 3: a descriptor is not a file

The word "file" in the name misleads. What counts is any object with a number in the process's descriptor table, and that can be checked by opening different things until refusal:

3. IT IS NOT ONLY FILES THAT COUNT
----------------------------------
  file: opened before the refusal              61 — EMFILE: Too many open files
  socket: opened before the refusal            61 — EMFILE: Too many open files
  pipe: opened before the refusal              60 — EMFILE: Too many open files
  epoll: opened before the refusal             61 — EMFILE: Too many open files
measured observationbench/rlimits/descriptors.py with a soft limit of 64. A pipe gives 60 rather than 61 because it is created in pairs: two ends, two descriptors.

A socket, a pipe end, an epoll instance — each takes a number. Hence the practical list of what eats the limit in a real service: database connections, HTTP pool connections, listening sockets, accepted connections, inotify watches, timers, open log files.

And one separate line about pipes: 60 rather than 61. There are 61 free numbers, an odd count, and a pipe takes two at once: thirty pipes occupy sixty numbers, and the single number left is not enough for a thirty-first. A detail, but it explains why the count of open objects and the count of occupied numbers diverge: a process that spawns subprocesses with captured output spends numbers in pairs.

Mechanism 4: the limit is inherited, hence "I raised it and nothing changed"

The practical mistake that makes a limit "not work" is usually one: it was raised for the wrong process. The limit belongs to a process but passes to its children, and execve(2) states this the other way round — "All process attributes are preserved during an execve(), except the following" — with resource limits absent from the list of exceptions.

4. THE LIMIT IS PER PROCESS, AND IT IS INHERITED
------------------------------------------------
  soft limit set in the parent                 128
  soft limit the child starts with             128
  soft limit restored in the parent            20000
measured observationbench/rlimits/descriptors.py. The parent set itself 128, the child started at 128 — and restoring the parent's limit afterwards changed nothing for the child.

Hence the rule worth answering with: the limit is set on whoever starts the process. If a service manager starts the service, the limit belongs in the service unit; if a container does, in the container's settings. ulimit -n in your shell changes the limit only for what you start from that shell and means nothing for a process started another way.

Check it where the kernel keeps it: /proc/<pid>/limits shows the limits in force for that process. It is the only answer that does not depend on what the configs say.

Deeper: a leak or an honest shortage

language contractThe layout of /proc/<pid>/fd and /proc/<pid>/limits is described in proc(5); it does not depend on the distribution.

Both look identical — EMFILE in the log — and their cures are opposite. The difference shows in one observation: does the number of open descriptors grow monotonically?

You count it where the rest of this lesson looks: /proc/<pid>/fd holds an entry per open descriptor, and their count is the current consumption. From there, two cases.

The count grows and does not fall when load drops: a leak. Something is not being closed — a response, a connection, a file. A higher limit only postpones it.

The count sits on a plateau near the limit and falls with the load: an honest shortage. That many connections are genuinely needed. Then the limit is raised — and raised for whoever starts the process.

There is a third case that gets mistaken for a leak: sockets in CLOSE_WAIT. The peer closed the connection and the application did not, so the descriptor stays taken until close is called. It will not free itself: this is a leak whose cause happens to be visible from outside.

How to answer in an interview

Short answer: EMFILE is a limit on the number of descriptors held by one process, and what it counts is not files but every open object — sockets, pipes, epoll — plus the three the process received at birth. There are two limits: the kernel checks the soft one when a descriptor is opened, while the hard one bounds how high the process may raise its own soft limit — and the process can do that raising itself, without a restart.

That is enough for a correct answer. What follows is what you add when the interviewer digs.

If the interviewer digs deeper

Three things separate a good answer. First, you distinguish EMFILE from ENFILE: one is about the process, the other about the machine, and they are fixed in different places. Second, you talk about inheritance and know why ulimit -n in somebody's shell changed nothing: the limit comes from whoever starts the process. Third, you name how to tell a leak from a shortage — watch the count in /proc/<pid>/fd over time, not the fact of the error.

Next they ask

Next they ask

Why does an application hit the limit when it opens a dozen files?

Short answer

Because files are not the only thing that uses up descriptors. Measured: a socket, a pipe end and an epoll instance take a number exactly as a file does. In a service, the bulk of descriptors are connections: to the database, to neighbouring services, and those accepted from clients.

So you count them not by reading the code but by reading /proc/<pid>/fd. What is not in the code shows up there: pools inside client libraries, file watches, timers.

Next they ask

Can the limit be raised without restarting the service?

Short answer

The soft one, yes — that is exactly what the first block of the run shows: the process lowered its soft limit and raised it back to the hard one, restarting nothing. If the program makes that call at startup, "raise the limit" stops being an operation with downtime.

Above the hard limit, no: the attempt in the run was refused. And if somebody has lowered the hard limit, raising it back in the same process is not possible either — that is described as irreversible.

Next they ask

How does EMFILE differ from ENFILE, and why does it matter?

Short answer

EMFILE is the limit of this process; ENFILE is the system-wide ceiling on open files. Different errors, different ceilings, different cures: in the first case you change the process's limit, in the second a system-wide setting, and changing the wrong one achieves nothing.

The practical value of the distinction is that it is available immediately: the error name is already in the log. If it says ENFILE, your service may not be at fault at all — it was simply the one that came up short.

Next they ask

Does raising the limit to the maximum and forgetting it work?

Short answer

A limit protects not only against you but against your neighbours: it bounds the cost of somebody's mistake. Raised to the ceiling, it trades one service's EMFILE for the whole machine's ENFILE: the system-wide ceiling in /proc/sys/fs/file-max has not gone anywhere, and whoever runs into it will not be the one at fault.

The useful move is different: raise the limit deliberately to a value matching the expected number of connections, and watch the count in /proc/<pid>/fd. Then EMFILE stops being a surprise and a rising count becomes an early signal.

Common misconceptions

Claim

'Too many open files' means the machine ran out of descriptors

Actually

It is a limit of the process: EMFILE is "the per-process limit on the number of open file descriptors has been reached". A system-wide shortage raises a different error, ENFILE, fixed in a different place. The first thing to establish from a log is which of the two letters it is.

Claim

there is one limit and an administrator changes it

Actually

There are two, and they do different jobs. The kernel checks the soft one at the moment a process opens another descriptor; the hard one bounds how high the process may raise its own soft limit. Raising the soft one up to the hard one, a process does itself and without a restart — measured in the first block of the run. An administrator is needed only when the hard limit is too low.

Claim

with a limit of 64 you can open 64 files

Actually

Measured: 61. Three descriptors come with the process — input, output and the error stream — and they count against the limit. The limit counts occupied numbers, not the open calls you made.

Claim

only files use up descriptors

Actually

Measured at the same limit: a socket — 61, a pipe — 60, an epoll instance — 61. In a service the bulk of descriptors goes to connections, and a pipe takes two numbers at once, because it has two ends.

Claim

I raised the limit with ulimit -n, so the service has it

Actually

Only if the service was started from that very shell: the limit is inherited from whoever starts the process. Measured: the parent set itself 128 and the child started at 128. A service started by a service manager knows nothing of your ulimit; check it in /proc/<pid>/limits.

Claim

raise the limit higher and the problem is solved

Actually

Solved if it was an honest shortage: the count sits on a plateau and falls with the load. If it climbs monotonically and never returns, it is a leak, and a higher limit only postpones it. The two are distinguished not by the error text but by the count in /proc/<pid>/fd over time.

Claim

the hard limit can be lowered and then restored

Actually

It cannot: lowering the hard limit is described as irreversible for an unprivileged process. In the run, an attempt to raise it above its current value was refused. The ceiling comes back only with a new process that inherits it from a parent.

Practice

Two exercises. Answer first, then check against the real output: in both, the correct answer comes from a script's committed output rather than being written by hand.

Practice · predict the output

A child sets its soft limit to 64 and opens files until the kernel refuses. Three things are printed: the error name, its text, and the soft limit another child starts with after the parent has set itself 128. What does this code print?
name, message, opened, held = probe(SOFT)
print(name)
print(message)
print(inherited_soft(128))

Practice · estimate

A process sets its soft limit to 64 descriptors and opens files until it is refused. How many files does it manage to open?
files

Knowledge check

Question 1 of 6

A service log says 'Too many open files'. What does that tell you about the limit?

Sources & further reading

4 SOURCES

  1. getrlimit(2), Linux man-pages 6.7Official documentation. Where the two limits come from and who may change them: "The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may set only its soft limit to a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard limit". And what RLIMIT_NOFILE bounds: "This specifies a value one greater than the maximum file descriptor number that can be opened by this process. Attempts (open(2), pipe(2), dup(2), etc.) to exceed this limit yield the error EMFILE".https://man7.org/linux/man-pages/man2/getrlimit.2.html
  2. open(2), Linux man-pages 6.7Official documentation. The distinction half the lesson rests on: EMFILE is "The per-process limit on the number of open file descriptors has been reached", ENFILE is "The system-wide limit on the total number of open files has been reached". Different errors, different ceilings, different cures.https://man7.org/linux/man-pages/man2/open.2.html
  3. execve(2), Linux man-pages 6.7Official documentation. Why a child inherits the limit — the rule is stated the other way round: "All process attributes are preserved during an execve(), except the following", and resource limits are not in the list of exceptions. Hence the practical rule of the lesson: the limit is set on whoever starts the service, not on the service.https://man7.org/linux/man-pages/man2/execve.2.html
  4. proc(5), Linux man-pages 6.7Official documentation. Where to look instead of guessing. The count of open descriptors: "This is a subdirectory containing one entry for each file which the process has open, named by its file descriptor". The limits in force for a process: "This file displays the soft limit, hard limit, and units of measurement for each of the process's resource limits". And the system-wide ceiling behind ENFILE: "This file defines a system-wide limit on the number of open files for all processes. System calls that fail when encountering this limit fail with the error ENFILE".https://man7.org/linux/man-pages/man5/proc.5.html