Troubleshooting Troubleshooting Intermediate

How to Fix the Docker Too Many Open Files Error on Ubuntu

Diagnose which file descriptor limit you actually hit — container, daemon or kernel — then raise it properly, and work out whether the real problem is a leak rather than a limit.

A terminal showing an accept4 too many open files error in a container log

Tested with

OperatingSystem
Ubuntu 24.04 LTS
Kernel
6.8
Docker
27.x
Systemd
255

Before you start

  • Root or sudo access on the Docker host
  • The name of the container that is failing
On this page

The error appears in a few shapes depending on what hit the limit:

text
accept tcp [::]:3000: accept4: too many open files
Error: EMFILE: too many open files, open '/app/uploads/tmp-91a2'
OSError: [Errno 24] Too many open files
dockerd: failed to start containerd: ... too many open files

All of them mean one process reached its file descriptor limit. Sockets, pipes and inotify instances all count as descriptors, not just files on disk — which is why a service that opens no files at all can still hit this.

Work out which limit before changing anything. Raising the wrong one wastes an outage.

Find the process and its actual limit

bash
docker inspect --format '{{.State.Pid}}' my-app

With the PID, read the limit the kernel is applying:

bash
grep 'open files' /proc/1234/limits
Expected output
bash
Max open files            1024                 1048576              files

The first column is the soft limit — the one that produces the error. Now count what the process actually has open:

bash
sudo ls /proc/1234/fd | wc -l

If that number is at or just under the soft limit, the limit is too low. If it is far below, something else is failing and this is not your problem.

You can also read it from inside:

bash
docker exec my-app sh -c 'ulimit -n; ls /proc/self/fd | wc -l'

Where the limit comes from

Understanding the chain saves you from raising the wrong thing twice.

Every process has a soft and a hard RLIMIT_NOFILE. The soft limit is what the process gets and what produces EMFILE; the hard limit is the ceiling it may raise itself to without privileges. A process inherits both from its parent.

For a container the parent chain is systemd, then dockerd, then the container runtime, then your application’s PID 1. Anything you set in /etc/security/limits.conf applies to login sessions and never reaches that chain, which is why editing it appears to do nothing.

Above all of that sits fs.file-max, the kernel-wide ceiling. On a modern kernel it is effectively unbounded and is almost never the constraint.

There is also a reason not to set the limit absurdly high. Some programs allocate arrays sized by RLIMIT_NOFILE at startup, and older code loops from 0 to the limit closing descriptors before exec. With a soft limit of 1,048,576 that turns into measurable startup delay and memory use. A value in the tens of thousands is enough for almost every workload and avoids the pathology.

Raise the limit for one service

The narrow fix, applied per service in Compose:

compose.yaml
services:
  app:
    image: ghcr.io/example/app:1.4.2
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
bash
docker compose up -d app
docker compose exec app sh -c 'ulimit -n'
Expected output
bash
65535

The equivalent for a one-off container is docker run --ulimit nofile=65535:65535.

A limit change only applies to a newly created process. Restarting the container is required; docker compose restart is enough, but recreating with up -d is what picks up the file change.

Raise the default for every container

If several services need it, set the daemon default instead of repeating yourself:

/etc/docker/daemon.json
{
  "default-ulimits": {
    "nofile": { "Name": "nofile", "Soft": 65535, "Hard": 65535 }
  }
}
bash
sudo systemctl restart docker

That restarts containers unless you have "live-restore": true set. Plan for it.

If the Docker daemon itself is the process running out — the failed to start containerd variant — the limit belongs to its systemd unit:

bash
sudo systemctl edit docker.service
/etc/systemd/system/docker.service.d/override.conf
[Service]
LimitNOFILE=1048576
bash
sudo systemctl daemon-reload
sudo systemctl restart docker

Recent Docker packages already ship LimitNOFILE=infinity, so check before adding an override that lowers it:

bash
systemctl show docker.service -p LimitNOFILE

Check the kernel-wide ceiling

Rarely the constraint, but worth ruling out in one command:

bash
cat /proc/sys/fs/file-nr
Expected output
bash
9856	0	9223372036854775807

Allocated, free, maximum. On a modern kernel the maximum is effectively unbounded, so if the first number is nowhere near the third, fs.file-max is not your problem.

When it is a leak, not a limit

Raising the limit is the right fix when the workload legitimately needs more descriptors — a proxy holding thousands of keep-alive connections, for example. It is the wrong fix when the count only ever goes up.

Sample it:

bash
for i in $(seq 1 10); do
  printf '%s  %s\n' "$(date +%T)" "$(sudo ls /proc/1234/fd | wc -l)"
  sleep 60
done

A count that climbs monotonically under steady traffic is a leak. Find out what kind:

bash
sudo ls -l /proc/1234/fd | awk '{print $NF}' | sed 's/[0-9]*$//' | sort | uniq -c | sort -rn | head
Expected output
bash
   4812 socket:[
     37 /app/logs/app.log
      9 anon_inode:[eventpoll]
      6 pipe:[

Thousands of sockets points at HTTP clients created per request instead of reused, or connections never closed on error paths. Thousands of the same file path points at a handle opened in a loop. Either way the fix is in the application, and a higher limit only moves the failure later.

Two more things narrow it down. ss -tanp | grep <pid> shows the state of those sockets — a pile in CLOSE_WAIT means your process is not calling close after the peer hung up, which is a code bug rather than a tuning problem. And comparing the count before and after a period of no traffic tells you whether the descriptors are ever released at all.

Raise the limit anyway as a stopgap so you are not firefighting, but open the bug. A leak with a higher ceiling fails at 3 a.m. next month instead of this afternoon, and the diagnosis is harder when you have forgotten the context.

The inotify variant

Different resource, similar-looking error:

text
Error: ENOSPC: System limit for number of file watchers reached
inotify_add_watch: No space left on device

Watchers are counted per user across the whole host, and dev servers, log shippers and file-sync tools consume them quickly. Check and raise:

bash
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances
/etc/sysctl.d/60-inotify.conf
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 512
bash
sudo sysctl --system

Persist it in /etc/sysctl.d/. A value set with sysctl -w is gone after reboot, and this failure is annoying enough to debug twice.

Verify

After any change, confirm the running process — not the config file — has the new limit:

bash
docker inspect --format '{{.State.Pid}}' my-app | xargs -I{} grep 'open files' /proc/{}/limits

Then leave a check in monitoring. Descriptor usage as a percentage of the soft limit is a leading indicator: it climbs for hours before anything breaks, which is enough warning to act during working hours rather than at 3 a.m.

Frequently asked questions

Why does ulimit -n on the host show a different value than inside the container?

Containers inherit limits from the Docker daemon's process, not from your login shell. The daemon runs under systemd with its own `LimitNOFILE`, and `docker run --ulimit` or `default-ulimits` overrides that per container.

Is it safe to set nofile to a very large number?

Setting a soft limit in the tens of thousands is normal. Setting it to 1048576 or higher can hurt: some programs allocate arrays sized by the limit or loop over every possible descriptor on startup, which turns a large limit into slow startup and high memory use.

Do I need to restart the container after changing the limit?

Yes. Resource limits are applied when the process is created and cannot be changed for a running process from outside. `docker compose up -d` after editing the file recreates the container.

What is the difference between the soft and hard limit?

The soft limit is what the process gets; the hard limit is the ceiling it may raise itself to. Set both. A soft limit alone is enough for most applications, but some raise their own at startup and need the headroom.

The error mentions inotify instead of open files. Is that the same fix?

No. Watchers are a separate kernel resource controlled by `fs.inotify.max_user_watches` and `fs.inotify.max_user_instances`, counted per user across the whole host. `ulimit -n` has no effect on it.

Sources

  1. Docker Compose ulimits Docker Primary source Accessed 28 May 2026
  2. dockerd reference — default-ulimits Docker Primary source Accessed 28 May 2026
  3. systemd.exec — LimitNOFILE systemd Primary source Accessed 28 May 2026
  4. inotify(7) Linux man-pages project Primary source Accessed 28 May 2026
4 min read

Part of a learning path

  • Security intermediate

    How to Secure a VPS That Runs Docker Containers

    SSH hardening, a default-deny firewall, and the fix for the problem that catches almost everyone — Docker publishing container ports straight past UFW into the public internet.

    4 min
  • Docker intermediate

    How to Back Up Docker Volumes Automatically

    A nightly backup for a self-hosted Docker host: database dumps taken with the right tools, file volumes snapshotted with restic, a systemd timer to run it, and a restore test that proves it works.

    4 min AI-assisted
  • n8n intermediate

    How to Deploy n8n on an Ubuntu VPS with Docker

    A production n8n install on Ubuntu 24.04 using Docker Compose, PostgreSQL and Caddy for automatic TLS — with the encryption key, webhook URL and backup steps most quickstarts leave out.

    5 min