File Descriptor Limits in Linux: ulimit, Systemd, and EMFILE Fixes
A file descriptor limit caps the number of open files, network sockets, and pipes a Linux process or system can maintain simultaneously. Hitting default boundaries triggers EMFILE errors and halts concurrent agent workers, web servers, and database pools. Resolving these bottlenecks requires configuring ulimit, PAM limits, systemd service overrides, and kernel sysctl values.
What Is a File Descriptor Limit in Linux and How Does It Work?
When a Linux process exhausts its allocated file descriptors, system calls like open(), socket(), pipe(), and accept() fail immediately with the EMFILE error code ("Too many open files"). Unlike physical memory exhaustion, which causes memory swapping or invokes the kernel Out-of-Memory (OOM) killer, file descriptor exhaustion leaves the offending process running in a degraded state. The process remains alive in the process table, but it cannot establish incoming network sockets, read configuration files, or write debug logs to disk.
A file descriptor limit is an operating system restriction that caps the maximum number of open files, network sockets, and pipes a single process or the entire system can maintain simultaneously. In Unix-like operating systems, "everything is a file." When a process opens a regular file on an ext4 or XFS filesystem, connects to a PostgreSQL database over a TCP socket, creates a POSIX inter-process pipe, or initializes an epoll event listener, the Linux kernel assigns an unsigned integer known as a file descriptor to track that input and output stream.
The File Descriptor Architecture
Understanding how Linux tracks file descriptors clarifies why limits exist and where bottlenecks emerge. Inside the kernel, file descriptor resolution spans three distinct layers:
- Per-Process File Descriptor Table: Each running process possesses its own private file descriptor table within its task_struct. The table maps small integer values (0 for standard input, 1 for standard output, 2 for standard error, and ascending integers for newly opened resources) to pointers in the system-wide open file table.
- System-Wide Open File Table: The kernel maintains a global list of open file objects represented by struct file instances. This table tracks the access mode (read, write, append), current file offset, and reference counts across all running processes.
- VFS Inode Table: The file object points to an inode representing the physical file on disk or the underlying network socket buffer in memory.
Because file descriptors consume unswappable kernel memory, Linux enforces limits at both the process level and the kernel level to prevent rogue scripts, runaway connection pools, or fork bombs from starving the operating system.
The Three-Tier Limit Hierarchy
Linux implements file descriptor governance across three hierarchical tiers:
- Per-Process Soft Limit (RLIMIT_NOFILE): The current operational threshold enforced by the kernel for an individual process. Most Linux distributions, including Ubuntu, Debian, CentOS, and Red Hat Enterprise Linux, default to a conservative soft limit of
1,024file descriptors per process. An unprivileged process can dynamically raise its soft limit up to its designated hard limit using the setrlimit() system call. - Per-Process Hard Limit: The absolute ceiling for the soft limit. Once established, an unprivileged user or process can only lower its hard limit. Raising the hard limit requires root privileges or explicit capability grants (CAP_SYS_RESOURCE).
- System-Wide Kernel Ceiling (fs.file-max): A global limit configured in
/proc/sys/fs/file-maxthat bounds the total number of open file allocations across all running processes on the host. Modern Linux distributions set fs.file-max dynamically during boot based on available physical memory, often establishing limits between1,048,576and65,536,000handles on modern servers.
EMFILE vs ENFILE Error Codes
When an application fails to allocate a file descriptor, the returned error code reveals which tier blocked the operation:
- EMFILE (Error Maximum Files per process, numeric value 24): Indicates that the calling process reached its own per-process limit (RLIMIT_NOFILE). Adjacent processes running under other system users continue operating normally.
- ENFILE (Error Network or System File table overflow, numeric value 23): Indicates that the host operating system exhausted the system-wide limit set in
/proc/sys/fs/file-max. When an ENFILE error occurs, every process across the entire server fails when attempting to open files or accept network handshakes.
Application Ceilings and the AI Ingestion Dilemma
File limits do not exist solely at the operating system level; they interact directly with application boundaries and AI agent workflows. Autonomous agents and Retrieval-Augmented Generation (RAG) indexers attempt to batch thousands of documents or extract code trees across local processes. In AI agent workflows, similar ceilings appear at the application boundary: as documented by the Anthropic Help Center, Claude chat sessions accept up to 20 files at up to 500 MB per file, while Claude Projects accept files up to 30 MB each with no fixed file-count cap, bounded only by the model's context window.
Reaching context ceilings during ingestion forces developers to look for alternative paths, while opening thousands of source files locally trips Linux process file descriptor boundaries before data ever reaches an API.
Related guides
- CSV File Size Limits: AI Assistant Upload Caps, Parsing Ceilings, and FixesCSV file size limits vary from strict row boundaries in spreadsheets to memory and token constraints in AI chat...
- Google Gemini PDF Limits: File Sizes, Page Caps, and Parsing FixesThe Gemini PDF limit enforces distinct document ceilings across environments: 100MB and 10 files per prompt in web...
- GitHub File Size Limits Explained for Teams and CopilotGitHub hard-blocks any single file larger than 100 MiB in normal Git history, warns above 50 MiB, and caps browser...
- Airtable Attachment Limits: File Sizes, Base Storage, and WorkaroundsThe Airtable attachment limit framework caps individual file uploads at 5 GB across all subscription tiers, while...
- AWS Bedrock File Size Limits: Document Constraints in Knowledge Bases and Converse APIAWS Bedrock Knowledge Bases enforces a 50 MB per-document limit for file ingestion, while the Bedrock Converse API caps...
- AWS Lambda File Size Limits: Payload, Package, and Ephemeral Storage CapsAn AWS Lambda file size limit encompasses the execution constraints of AWS Lambda functions, specifically the 50MB...
More on this subject: Agent File and Document Workflows (325 guides)
How to Check File Descriptor Limits and Active Usage
Diagnosing file descriptor exhaustion requires inspecting both active limits and real-time consumption across running processes. Because different shells, daemons, and system users run under isolated constraints, querying limits from the correct context is essential.
Checking Process Limits with ulimit
The standard utility for inspecting and modifying resource limits in an interactive shell session is ulimit. By default, ulimit -n inspects the soft file descriptor limit for the current shell:
ulimit -n
To explicitly inspect the soft limit, pass the -Sn flag:
ulimit -Sn
To inspect the hard ceiling for the current user, pass the -Hn flag:
ulimit -Hn
When the command outputs 1024, the current shell session is restricted to that default allocation until you raise the ceiling.
Inspecting Running Processes via /proc
When troubleshooting background daemons or multi-threaded applications, checking ulimit in your personal SSH session can be misleading. System services frequently run with completely different resource boundaries.
To inspect the exact limits assigned to a live process by process ID (PID), query its /proc entry:
cat /proc/<PID>/limits | grep "Max open files"
The output displays three columns: the soft limit, the hard limit, and the unit of measurement:
Limit Soft Limit Hard Limit Units
Max open files 1024 4096 files
To determine how many file descriptors a running process currently holds open, list the entries in its /proc/<PID>/fd virtual directory:
ls -1 /proc/<PID>/fd | wc -l
Comparing the count of entries in /proc/<PID>/fd against the soft limit in /proc/<PID>/limits reveals whether a process is approaching its exhaustion ceiling.
Inspecting System-Wide File Allocations
To evaluate overall host capacity, query the virtual files provided by the kernel under /proc/sys/fs/:
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr
The file-nr virtual file returns three space-delimited integers:
- Allocated File Handles: The number of file handles the Linux kernel has currently allocated.
- Free Allocated Handles: The number of allocated file handles that have been released and are ready for reuse (in modern Linux 2.6 and newer kernels, this figure is typically 0 because the kernel frees unused structures immediately).
- Maximum File Handles: The maximum number of file handles the kernel will permit system-wide (identical to the value in file-max).
For example, an output of 14240 0 2097152 indicates that 14,240 file handles are currently active out of a global capacity of 2,097,152.
Identifying Top File Descriptor Consumers
When a server exhibits high file descriptor counts, run this one-line shell pipeline to identify the top ten processes holding the highest descriptor counts:
for pid in /proc/[0-9]*; do
p="${pid##*/}"
count=$(ls -1 "$pid/fd" 2>/dev/null | wc -l)
if [ "$count" -gt 0 ]; then
echo "$count $p $(cat "$pid/comm" 2>/dev/null)"
fi
done | sort -rn | head -n 10
Once you identify the offending PID, inspect the specific resources it holds using lsof:
lsof -p <PID>
This reveals whether the process is legitimately handling client connections or leaking file handles due to unclosed input and output streams.
Diagnostic Command Reference
The following comparison table summarizes essential inspection commands, their operational scope, and output interpretations:
Configuring Persistent Limits Across Shells, PAM, and Systemd
Increasing file descriptor limits requires configuring the specific subsystem that governs the target workload. A common pitfall in Linux systems administration is running ulimit -n 65536 in a terminal and expecting background services to inherit the change.
A temporary ulimit -n invocation modifies only the current shell process and its child processes, resetting the moment the shell session terminates. Establishing permanent, reboot-resilient limits requires configuring PAM limits for user sessions, systemd unit directives for services, and sysctl parameters for the kernel.
Ephemeral Changes for Interactive Shells
To raise the limit for a temporary task, batch script, or immediate debugging session, invoke ulimit followed by the desired integer:
ulimit -n 65536
An unprivileged user can only raise their soft limit up to the value of their hard limit (ulimit -Hn). If an unprivileged user attempts to specify a value higher than the hard limit, the shell rejects the command with an error message. The superuser (root) can set both soft and hard limits to any value up to the kernel ceiling defined in fs.nr_open.
Permanent Limits for User Sessions via PAM
For interactive user logins, SSH sessions, and cron tasks managed by Pluggable Authentication Modules (PAM), persistent limits are configured in /etc/security/limits.conf or drop-in files under /etc/security/limits.d/.
Add configuration directives using the standard format <domain> <type> <item> <value>:
* soft nofile 65536
* hard nofile 65536
root soft nofile 65536
root hard nofile 65536
appuser soft nofile 1048576
appuser hard nofile 1048576
Two critical details must be observed when editing limits.conf:
- The Wildcard Does Not Cover Root: The asterisk wildcard matches all standard user accounts, but PAM explicitly excludes the root account from wildcard expansion. Always declare separate entries for root.
- PAM Module Activation: Verify that PAM sessions load the pam_limits.so library. In Debian and Ubuntu systems, verify that
/etc/pam.d/common-sessionand/etc/pam.d/common-session-noninteractivecontain:
session required pam_limits.so
Overriding Limits for Systemd Services
The most frequent configuration gap in modern Linux administration involves system services. Systemd does not use PAM for services it spawns. Consequently, background services like Nginx, Docker, PostgreSQL, Redis, and custom Python or Node.js workers completely ignore /etc/security/limits.conf.
Modern systemd releases default DefaultLimitNOFILE to 524288:1048576 (524,288 soft and 1,048,576 hard), but older releases or customized distributions can constrain services to 1,024 descriptors.
To adjust the file descriptor limit for any systemd service, set the property directly using systemctl:
sudo systemctl set-property custom-worker.service LimitNOFILE=65536
Alternatively, create a drop-in configuration file directly on disk:
sudo mkdir -p /etc/systemd/system/custom-worker.service.d
printf '[%%s]
LimitNOFILE=65536
' "Service" | sudo tee /etc/systemd/system/custom-worker.service.d/override.conf
You can specify a single integer to set soft and hard limits equally, or specify two colon-delimited values (LimitNOFILE=65536:524288) to establish separate soft and hard boundaries. To grant a high-throughput network proxy or database the maximum allowable descriptors, use LimitNOFILE=infinity, which sets the limit to the value defined in fs.nr_open.
Apply the override and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart custom-worker.service
cat /proc/$(pgrep -o custom-worker)/limits | grep "Max open files"
To adjust the default file descriptor limit for all systemd services globally, edit /etc/systemd/system.conf and declare:
DefaultLimitNOFILE=65536:524288
Raising Kernel Boundaries with sysctl
The Linux kernel enforces two global parameters under /proc/sys/fs/:
fs.file-max: The system-wide ceiling on open file handles.fs.nr_open: The maximum value any individual process can assign to its soft or hard RLIMIT_NOFILE. If you configureLimitNOFILE=2000000but fs.nr_open is set to1048576, systemd or the shell will reject the allocation.
To permanently increase both values across system reboots, create a sysctl configuration file at /etc/sysctl.d/99-file-limits.conf:
fs.file-max = 2097152
fs.nr_open = 2097152
Apply the changes immediately without restarting the host:
sudo sysctl --system
Configuration Matrix Across Subsystems
The following comparison table highlights which configuration file governs each operational environment:
Scale Agent File Ingestion Without Linux File Descriptor Limits
Connect Claude, Cursor, and autonomous agents to Fast.io workspaces over MCP. Store multi-gigabyte corpora, index documents for hybrid search, and eliminate local EMFILE bottlenecks. Monthly plans start with a 14-day free trial.
Diagnosing and Resolving EMFILE Errors in Runtimes and Containers
While system administrators often resolve EMFILE errors by raising configuration ceilings, software developers frequently encounter EMFILE due to application architecture defects. In runtime environments like Node.js and Python, EMFILE errors are frequently a symptom of unbounded concurrency or resource leaks rather than insufficient operating system capacity.
Node.js and JavaScript Runtime Failures
In Node.js applications, file operations execute asynchronously. A common antipattern involves reading a large directory tree by launching unthrottled asynchronous promises:
const fs = require('fs/promises');
async function processDirectory(fileList) {
return Promise.all(fileList.map(filePath => fs.readFile(filePath, 'utf-8')));
}
If fileList contains thousands of files and the default soft limit is 1,024, the Node.js event loop initiates file opening operations until the process exhausts its descriptors, failing with Error: EMFILE: too many open files.
To fix this issue without raising system limits, throttle concurrent operations using batching:
const fs = require('fs/promises');
async function processWithBatching(fileList, concurrency = 64) {
const results = [];
for (let i = 0; i < fileList.length; i += concurrency) {
const chunk = fileList.slice(i, i + concurrency);
const chunkResults = await Promise.all(
chunk.map(file => fs.readFile(file, 'utf-8'))
);
results.push(...chunkResults);
}
return results;
}
Similarly, tools that watch filesystem changes (such as Chokidar or Webpack file watchers) allocate an inotify file descriptor per watched directory. On massive repositories, raising /proc/sys/fs/inotify/max_user_watches alongside LimitNOFILE prevents watcher crashes.
Python Asyncio and Multiprocessing Leaks
In Python, an OSError with errno 24 ("Too many open files") commonly occurs when workers open files or HTTP client sessions without proper context managers:
def load_corpus(paths):
records = []
for path in paths:
f = open(path, 'r')
records.append(f.read())
return records
If an exception occurs or if Python's garbage collector delays cleanup, file descriptors remain allocated. Always use context managers (with open(path) as f:) for deterministic closing of descriptors upon block exit.
In high-concurrency asyncio applications, bound simultaneous network requests using asyncio.Semaphore:
import asyncio
import aiohttp
async def fetch_urls(urls, max_concurrency=100):
semaphore = asyncio.Semaphore(max_concurrency)
async with aiohttp.ClientSession() as session:
async def fetch(url):
async with semaphore:
async with session.get(url) as response:
return await response.text()
return await asyncio.gather(*(fetch(u) for u in urls))
Docker and Containerized Agent Limits
Containers run in isolated namespaces, but they do not automatically inherit host PAM configurations. By default, a container inherits its file descriptor boundaries from the container engine daemon.
To set file descriptor limits on an individual Docker container, pass the --ulimit flag at runtime:
docker run -d --name agent-worker --ulimit nofile=65536:65536 python:3.11-slim
In Docker Compose configurations, define limits within compose.yaml:
services:
agent-runner:
image: custom-agent:latest
ulimits:
nofile:
soft: 65536
hard: 65536
restart: on-failure
To configure global container limits across all Docker workloads on a host, define default ulimits in /etc/docker/daemon.json:
{
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 65536,
"Soft": 65536
}
}
}
After updating daemon.json, execute sudo systemctl restart docker to apply the baseline limits to all newly initialized containers.
Architectural Strategies: Offloading File I/O to Intelligent Cloud Workspaces
While raising operating system limits and batching local file reads stabilizes single-node services, relying on local filesystem handles presents major architectural limitations when scaling autonomous agents and data extraction pipelines.
When background agents process extensive technical manuals, legal binders, or code repositories, maintaining thousands of open file descriptors on local Linux servers creates memory bloat, high kernel overhead, and file synchronization collisions. Instead of holding thousands of open file handles across ephemeral container disks, modern architectures decouple file storage from compute with cloud workspaces.
Decoupling File I/O with Fast.io Workspaces
In an intelligent workspace architecture, document collections and project assets reside in org-owned Fast.io workspaces. Rather than requiring local agent processes to mirror, unpack, and open local files on an ext4 volume, the workspace is the persistent collaboration layer for both human teams and AI agents.
Teams can ingest documents through several native paths:
- Direct Uploads: Ingest files directly via the browser or API, using chunked upload mechanics that support large file ingestion up to plan limits (
25 GBon Starter,50 GBon Business, and100 GBon Enterprise). - Cloud Sync: Cloud Sync connects Google Drive, Dropbox, Box, and OneDrive folders to a workspace, one-way or two-way, on a schedule or on demand.
- URL Import: Ingest files directly from public web addresses and cloud storage links without drawing down local network bandwidth or allocating local server file descriptors.
Native Intelligence Mode and MCP Tooling
Once documents arrive in a workspace, enabling Intelligence Mode indexes the content automatically across full-text, semantic vectors, and metadata values. This eliminates the need for teams to manage independent vector databases, chunking workers, or local file indexing loops.
Autonomous assistants and coding agents interact with the workspace via the remote Model Context Protocol (MCP) server over Streamable HTTP:
- General Assistants and Workflow Agents: Connect Claude Desktop, Claude Cowork, OpenClaw, or custom agents via
https://mcp.fast.io/mcp/tools. - IDE and Coding Agents: Connect Claude Code, Cursor, VS Code, and terminal agents via
https://mcp.fast.io/mcp/code. - ChatGPT and Codex Custom Servers: Connect via
https://mcp.fast.io/mcp/operations(or install the Fast.io plugin directly from the ChatGPT plugin directory).
Using the MCP storage search action, an agent queries documents by meaning and retrieves only the precise citations needed to answer a prompt. This completely bypasses local Linux EMFILE bottlenecks: an agent analyzing a repository of 50,000 files requires only a single HTTP connection to the MCP endpoint rather than 50,000 open local file descriptors.
Collaborative Capabilities for Agentic Teams
Intelligent cloud workspaces provide capabilities that local Linux filesystems cannot deliver:
- Metadata Views: Teams can transform unstructured files into queryable spreadsheets with typed schemas at /product/document-data-extraction/. AI automatically extracts fields like contract counterparties, renewal dates, and line item totals from PDFs, images, and office documents without manual OCR rules.
- Collaborative Notes: Notes kept beside the files for people and agents use version-checked saves to prevent conflicting edits between human collaborators and automated workers.
- Per-File Version History: Every modification creates a discrete version, allowing teams to audit changes, compare diffs, and restore prior states across concurrent multi-agent operations.
- Detailed Activity Log: An organization-wide activity log captures changes made across workspaces and shares, recording uploads, edits, moves, and deletions without tracking read-only views or downloads.
- Ownership Transfer: Autonomous agents can create an organization, configure workspaces and branded shares, and transfer full organization ownership to a human stakeholder while retaining administrative access.
Monthly plans start with a 14-day free trial, which requires a credit card. Teams can scale across Starter ($9.99/mo with 3 seats and 250 GB storage), Business ($49.99/mo with 10 seats and 5 TB storage), and Enterprise ($199.99/mo with 30 seats and 25 TB storage) tiers. Explore persistent workspace storage for agentic teams at /storage-for-agents/ and review setup steps at https://mcp.fast.io/docs.
Sources
References used to verify factual claims in this guide.
-
Claude chat sessions accept up to 20 files at up to 500 MB per file, while Claude Projects accept files up to 30 MB each with no fixed file-count cap, bounded only by the model's context window.
Frequently Asked Questions
How do I check the file descriptor limit in Linux?
To check the file descriptor limit for your current shell session, run ulimit -n for the soft limit or ulimit -Hn for the hard limit. To inspect the limits of any running process, run cat /proc/<PID>/limits | grep 'Max open files' substituting the target process ID. To view the system-wide global limit enforced across all processes, run cat /proc/sys/fs/file-max.
What causes the Too many open files error?
The Too many open files error (EMFILE) occurs when a process attempts to open a new file, network socket, pipe, or event listener after already reaching its allocated per-process file descriptor limit. Common causes include unclosed database connection pools, unmanaged file handle leaks in application code, high concurrent traffic on web servers, or batch processing large directories simultaneously without concurrency throttling.
How do I permanently increase ulimit open files limit?
To permanently increase the ulimit open files limit for user sessions, add soft and hard nofile entries to /etc/security/limits.conf or a drop-in file in /etc/security/limits.d/. Ensure that /etc/pam.d/common-session includes session required pam_limits.so. For system services, configure LimitNOFILE inside a systemd drop-in override file using systemctl edit.
What is the difference between soft and hard file descriptor limits?
A soft file descriptor limit is the active threshold enforced by the Linux kernel during standard operations; exceeding it triggers an EMFILE error. A hard file descriptor limit acts as the ceiling for the soft limit. An unprivileged user can dynamically increase their soft limit up to their assigned hard limit, but only the root user can increase the hard limit.
Why does systemd ignore limits defined in /etc/security/limits.conf?
Systemd does not run user-space services through PAM (Pluggable Authentication Modules) sessions, meaning directives configured in /etc/security/limits.conf are completely bypassed. To set file descriptor limits for a systemd service, you must define the LimitNOFILE directive within the service unit file or create an override using systemctl edit <service_name>.
What is the difference between EMFILE and ENFILE error codes?
EMFILE (errno 24) indicates that an individual process has exceeded its personal file descriptor limit (RLIMIT_NOFILE), while adjacent processes on the system remain unaffected. ENFILE (errno 23) indicates that the entire operating system has exhausted the global file handle allocation table defined in /proc/sys/fs/file-max, causing all file and socket operations across the server to fail.
How do I set file descriptor limits inside Docker containers?
To set file descriptor limits on a single Docker container, use the --ulimit nofile=<soft>:<hard> runtime flag, such as docker run --ulimit nofile=65536:65536. In Docker Compose, define ulimits.nofile.soft and ulimits.nofile.hard under the service definition in compose.yaml. To set host-wide container defaults, configure default-ulimits in /etc/docker/daemon.json.
How can AI agents search large document corpora without exhausting OS file descriptors?
Instead of holding thousands of open file handles on local disks, AI agents can store document collections in cloud workspaces like Fast.io. With Intelligence Mode enabled, documents are indexed for hybrid search upon upload. Assistants connect through a remote Model Context Protocol (MCP) server over Streamable HTTP, querying only the necessary passages and eliminating local file descriptor bottlenecks.
Related Resources
Scale Agent File Ingestion Without Linux File Descriptor Limits
Connect Claude, Cursor, and autonomous agents to Fast.io workspaces over MCP. Store multi-gigabyte corpora, index documents for hybrid search, and eliminate local EMFILE bottlenecks. Monthly plans start with a 14-day free trial.