15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started
09.10.2024

Managing System Resources with the `ulimit` Command on Linux

The `ulimit` command is a built-in shell utility on Unix and Linux systems that enforces per-process and per-user resource limits, preventing any single process or user from exhausting system resources such as CPU time, memory, open file descriptors, and process count. It operates at the kernel level through the `setrlimit()` system call, making it one of the most direct and low-overhead mechanisms available to system administrators for resource governance.

For any server running production workloads — whether a high-traffic web application, a database engine, or a containerized microservice stack — misconfigured or absent `ulimit` settings are a leading cause of cascading failures, runaway processes, and full system outages. Getting these limits right is not optional; it is foundational infrastructure hygiene.

How `ulimit` Works Under the Hood

When a shell process calls `ulimit`, it invokes the `getrlimit()` and `setrlimit()` system calls defined in the POSIX standard. Each limit is represented as a pair of values: a soft limit and a hard limit. These are stored per-process in the kernel's process descriptor and are inherited by child processes at `fork()` time.

This inheritance model is critical to understand. If you set `ulimit` values in a shell session, every process spawned from that shell — including daemons launched via init scripts — inherits those limits. Conversely, limits set in `/etc/security/limits.conf` apply at PAM login time, not at runtime, which means they only take effect for new login sessions, not for already-running services.

Soft Limits vs. Hard Limits

PropertySoft LimitHard Limit
Who can raise itAny unprivileged user (up to the hard limit)Only root (`CAP_SYS_RESOURCE`)
Who can lower itAny userAny user (irreversible without root)
EnforcementEnforced by the kernelActs as the ceiling for the soft limit
Typical use caseDay-to-day operational boundaryAbsolute maximum for security policy
Flag in `ulimit``-S``-H`

A common operational mistake is setting the hard limit equal to the soft limit. This removes all flexibility for a process to temporarily raise its own limits, which some applications (like certain JVM implementations and database engines) do legitimately during startup.

Complete Reference: `ulimit` Resource Flags

FlagResourceUnitCommon Production Value
`-t`CPU timeSeconds`unlimited` for daemons
`-f`Maximum file size512-byte blocks`unlimited` or specific cap
`-d`Data segment (heap) sizeKB`unlimited` for Java apps
`-s`Stack sizeKB`8192` (default)
`-c`Core dump file size512-byte blocks`0` (disabled in prod)
`-m`Maximum resident set sizeKBRarely enforced (use cgroups)
`-v`Virtual memory (address space)KB`unlimited` for most services
`-n`Open file descriptorsCount`65536` or higher for busy servers
`-u`Maximum user processesCount`4096`–`65536` depending on role
`-l`Locked memory (mlock)KBHigh for Redis, Elasticsearch
`-i`Pending signalsCountSystem default usually sufficient
`-q`POSIX message queue bytesBytesSystem default
`-r`Real-time scheduling priorityPriority`0` unless RT workloads
`-e`Maximum scheduling priority (nice)Nice valueSystem default

Practical `ulimit` Usage with Real-World Context

Viewing Current Limits

“`bash

ulimit -a # All soft limits for the current shell

ulimit -aH # All hard limits for the current shell

“`

To inspect limits for a specific running process (PID), read directly from the proc filesystem — this is the authoritative source and bypasses shell-level reporting:

“`bash

cat /proc/<PID>/limits

“`

This is invaluable when troubleshooting a service that was started by systemd or an init script, where shell-level `ulimit -a` will not reflect the process's actual limits.

Setting Soft and Hard Limits

“`bash

Set soft limit for open file descriptors

ulimit -Sn 65536

Set hard limit for open file descriptors

ulimit -Hn 131072

Set both simultaneously (soft = hard = value)

ulimit -n 65536

“`

Disabling Core Dumps in Production

“`bash

ulimit -c 0

“`

Core dumps can consume gigabytes of disk space within seconds when a high-memory process crashes. Disabling them in production is standard practice unless you are actively debugging. For development environments, set a dedicated path using `sysctl kernel.core_pattern` alongside a non-zero core limit.

Restricting CPU Time for Untrusted Processes

“`bash

ulimit -t 30

“`

This sends `SIGXCPU` to the process when it reaches the soft CPU time limit, and `SIGKILL` at the hard limit. This is particularly useful in shared hosting environments or when running user-submitted scripts.

Raising the Open File Descriptor Limit for High-Concurrency Services

Nginx, HAProxy, PostgreSQL, and Redis all require a high number of open file descriptors under load. The system-wide default of 1024 is dangerously low for production:

“`bash

ulimit -n 65536

“`

However, this only affects the current shell session. For persistent configuration, use the methods described in the next section.

Making `ulimit` Settings Persistent

Method 1: `/etc/security/limits.conf`

This is the standard PAM-based approach for user-level persistent limits:

“`

/etc/security/limits.conf

<domain> <type> <item> <value>

  • soft nofile 65536
  • hard nofile 131072

nginx soft nproc 4096

nginx hard nproc 8192

postgres soft nofile 65536

postgres hard nofile 65536

postgres soft memlock unlimited

postgres hard memlock unlimited

“`

The wildcard `*` applies to all users but does not apply to root. Root requires an explicit entry:

“`

root soft nofile 65536

root hard nofile 131072

“`

Ensure the PAM module is loaded. Verify `/etc/pam.d/common-session` (Debian/Ubuntu) or `/etc/pam.d/system-auth` (RHEL/CentOS) contains:

“`

session required pam_limits.so

“`

Method 2: `/etc/security/limits.d/` Drop-in Files

For cleaner management, especially in configuration management systems like Ansible or Puppet, place service-specific limit files in the drop-in directory:

“`bash

/etc/security/limits.d/99-nginx.conf

nginx soft nofile 65536

nginx hard nofile 131072

“`

Files in this directory are processed after `limits.conf` and override it, making them ideal for application-specific tuning without modifying the base configuration.

Method 3: systemd Service Units (The Modern Standard)

For services managed by systemd — which is the majority of modern Linux distributions — `limits.conf` is not applied by default. systemd manages its own resource limits per service unit:

“`ini

/etc/systemd/system/nginx.service.d/limits.conf

[Service]

LimitNOFILE=65536

LimitNPROC=4096

LimitCORE=0

LimitMEMLOCK=infinity

“`

After editing, reload and restart:

“`bash

systemctl daemon-reload

systemctl restart nginx

“`

Verify the applied limits:

“`bash

cat /proc/$(systemctl show -p MainPID nginx | cut -d= -f2)/limits

“`

This is the most reliable method for production services and should be the default approach on any system running systemd (Ubuntu 16.04+, CentOS 7+, Debian 8+).

Method 4: Shell Profile Files

For user-session limits that apply interactively, add `ulimit` commands to `/etc/profile` (system-wide) or `~/.bashrc` / `~/.profile` (per-user). This approach is appropriate for developer workstations but is unsuitable for daemon processes.

Role-Based `ulimit` Configuration Profiles

Different server roles demand fundamentally different resource limit profiles. Applying generic defaults across all server types is a common source of subtle, hard-to-diagnose failures.

Web Server (Nginx / Apache)

“`

nofile: 65536–131072 # High concurrency requires many open sockets + files

nproc: 4096 # Worker processes + threads

core: 0 # Disable core dumps in production

“`

Relational Database (PostgreSQL / MySQL)

“`

nofile: 65536 # Many concurrent connections = many file descriptors

memlock: unlimited # Required for shared memory and huge pages

nproc: 4096

stack: 8192 KB

core: 0

“`

Java Application Server (Tomcat / Spring Boot)

“`

nofile: 65536

nproc: 65536 # JVM thread-per-connection models spawn many threads

data: unlimited # JVM heap is allocated from the data segment

stack: 512 KB # Reduce stack size to fit more threads in memory

“`

Redis / In-Memory Data Store

“`

nofile: 65536

memlock: unlimited # Prevents swapping of memory-mapped data

“`

Critical Pitfalls and Edge Cases

The `nproc` limit counts threads, not just processes. On Linux, threads are implemented as lightweight processes (`clone()` with shared memory). A Java application with 500 threads counts as 500 against the `nproc` limit. This surprises many administrators who set conservative `nproc` values and then wonder why their JVM crashes with `OutOfMemoryError: unable to create new native thread`.

`ulimit -v` limits virtual address space, not physical RAM. Many administrators set `-v` thinking they are capping memory usage. In reality, they are capping the virtual address space, which includes memory-mapped files, shared libraries, and JVM metaspace. Setting this too low will cause `mmap()` failures and cryptic application errors.

`ulimit` does not apply retroactively. Changing limits in `limits.conf` or a systemd unit file does not affect already-running processes. You must restart the service for new limits to take effect.

Container environments bypass `ulimit` in unexpected ways. In Docker, `ulimit` defaults are set at the daemon level (`/etc/docker/daemon.json`) and can be overridden per container with `–ulimit`. However, the container's limits are bounded by the host kernel's limits. Setting `nofile=1048576` in a container while the host has `nofile=65536` will silently fall back to the host limit.

The `nofile` system-wide ceiling is separate from per-process limits. The kernel parameter `fs.file-max` (set via `sysctl`) controls the total number of file descriptors across the entire system. Even if per-process `nofile` is set high, hitting `fs.file-max` will cause `ENFILE` errors system-wide. Check and tune both:

“`bash

sysctl fs.file-max

sysctl -w fs.file-max=2097152

“`

`ulimit` vs. cgroups: Choosing the Right Tool

Capability`ulimit` / `setrlimit`cgroups v2
ScopePer-process (inherited by children)Per-group of processes
Memory limitingVirtual address space only (`-v`)Actual RSS + swap enforcement
CPU throttlingCPU time budget (`-t`)CPU bandwidth controller (precise %)
I/O limitingNot supportedBlock I/O weight and rate limits
Network limitingNot supportedRequires tc + cgroup integration
PersistenceVia PAM or systemdVia systemd slices or cgroupfs
Container compatibilityLimitedNative (Docker, Kubernetes use cgroups)
GranularityCoarseFine-grained

`ulimit` remains the right tool for quick, per-session limits, file descriptor caps, and core dump control. For comprehensive resource isolation — especially in multi-tenant environments or containerized workloads — cgroups v2 is the superior mechanism. On a well-configured VPS Hosting or Dedicated Server environment, both mechanisms are typically used in combination: `ulimit` for per-process guardrails and cgroups for aggregate resource budgets.

Monitoring and Validating Resource Limits

Proactive monitoring prevents limit-related failures from becoming production incidents.

Check current file descriptor usage system-wide:

“`bash

cat /proc/sys/fs/file-nr

Output: <allocated> <unused> <max>

“`

Find processes approaching their `nofile` limit:

“`bash

for pid in /proc/[0-9]*; do

pid_num=${pid##*/}

limit=$(awk '/Max open files/{print $4}' /proc/$pid_num/limits 2>/dev/null)

current=$(ls /proc/$pid_num/fd 2>/dev/null | wc -l)

[ -n "$limit" ] && [ "$limit" != "unlimited" ] &&

awk -v c=$current -v l=$limit -v p=$pid_num

'BEGIN{if(c/l>0.8) printf "PID %s: %d/%d (%.0f%%)n",p,c,l,c/l*100}'

done

“`

Tools for ongoing monitoring:

  • `lsof -u <username>` — list all open files for a user
  • `ss -s` — socket statistics (correlates with `nofile` pressure)
  • `htop` with process tree view — visualize process counts per user
  • `sar -v` — historical file descriptor and inode usage via sysstat
  • Prometheus `node_exporter` — exposes `node_filefd_allocated` and `node_filefd_maximum` metrics for alerting

For environments running VPS with cPanel or other control panels, many of these limits are pre-configured by the panel installer, but they frequently need upward adjustment as traffic grows. Always verify the actual limits against `/proc/<PID>/limits` rather than trusting panel documentation.

Security Implications of `ulimit`

Resource limits are also a security control. Without them, a compromised or buggy process can execute a fork bomb (`:(){ :|:& };:`), exhausting all available process slots and rendering the system unresponsive. A conservative `nproc` limit per user is the primary mitigation:

“`

  • hard nproc 4096

“`

Similarly, disabling core dumps (`-c 0`) prevents sensitive memory contents — including encryption keys, passwords, and session tokens — from being written to disk in a world-readable file.

For shared hosting environments or any server where multiple users have shell access, `ulimit` is a mandatory security layer. On Shared Web Hosting infrastructure, these limits are typically enforced at the platform level, but administrators running their own multi-user VPS should configure them explicitly.

If your server handles SSL termination or certificate management, ensure the process handling TLS (e.g., Nginx, HAProxy) has sufficient `nofile` limits, as each TLS connection requires multiple file descriptors. Pair this with properly configured SSL Certificates to avoid certificate-related connection failures compounding resource issues.

For mail server deployments, Postfix and Dovecot are particularly sensitive to `nofile` limits, as each concurrent email connection and mailbox access consumes file descriptors. If you are running your own mail infrastructure rather than using managed Email Hosting, tuning `nofile` to at least 65536 for the mail user is non-negotiable on any moderately loaded server.

Decision Matrix: What to Configure and Where

ScenarioRecommended MethodKey Parameters
Interactive user sessions`/etc/security/limits.conf``nofile`, `nproc`, `core`
systemd-managed servicesystemd unit `[Service]` section`LimitNOFILE`, `LimitNPROC`, `LimitCORE`
Docker container`–ulimit` flag or `daemon.json``nofile`, `nproc`
One-time shell testing`ulimit` command directlyAny flag
Multi-tenant shared server`limits.conf` + PAM enforcement`nproc`, `nofile`, `fsize`, `cpu`
Kubernetes podPod security context + cgroupsManaged by kubelet
Application-specific tuning`limits.d/` drop-in fileService-specific parameters

Technical Key-Takeaway Checklist

  • Always verify applied limits via `/proc/<PID>/limits`, not shell-level `ulimit -a`, for running services.
  • For systemd services, configure limits in the unit file using `Limit*` directives — `limits.conf` is not read by systemd by default.
  • Set `nofile` to at minimum `65536` for any service handling network connections; `131072` or higher for high-concurrency workloads.
  • Never set the hard limit equal to the soft limit unless you have a specific security requirement — applications need headroom to self-adjust.
  • Disable core dumps (`LimitCORE=0`) in production; enable them with a controlled path in staging.
  • The `nproc` limit counts threads on Linux — account for this when configuring JVM or Go runtime applications.
  • Tune `fs.file-max` via `sysctl` alongside per-process `nofile` limits to avoid system-wide `ENFILE` exhaustion.
  • In containerized environments, host kernel limits are the hard ceiling — container-level `ulimit` settings cannot exceed them.
  • Use cgroups v2 for memory and I/O enforcement; use `ulimit` for file descriptor caps, process counts, and core dump control.
  • After any limit change in `limits.conf` or systemd unit files, restart the affected service and verify with `/proc/<PID>/limits`.

FAQ

Does `ulimit` apply to root processes?

The wildcard `*` in `/etc/security/limits.conf` explicitly excludes root. Root processes also bypass hard limit enforcement for most resource types — root can raise its own hard limits. To apply limits to root, add an explicit `root` entry in `limits.conf`, though many system services running as root will ignore PAM-applied limits if started outside a login session.

Why does my `limits.conf` change have no effect on a running service?

`limits.conf` is applied by PAM at login time. Services started by systemd, SysVinit, or Upstart do not go through PAM and therefore do not inherit `limits.conf` settings. Configure limits directly in the systemd unit file using `LimitNOFILE` and related directives, then run `systemctl daemon-reload && systemctl restart <service>`.

What is the maximum value I can set for `nofile`?

The per-process maximum is bounded by the kernel's `fs.nr_open` parameter (default: 1,048,576 on most kernels). The system-wide total is bounded by `fs.file-max`. You can raise `fs.nr_open` via `sysctl`, but values above 1,048,576 require kernel recompilation on older kernels. Practically, 524,288 or 1,048,576 covers virtually all production use cases.

How do I check if a process has hit its `ulimit` boundary?

Check the kernel log with `dmesg | grep -i "ulimit|RLIMIT|too many open|cannot allocate"`. Application logs will typically show `EMFILE` (too many open files), `ENOMEM` (memory allocation failure), or `EAGAIN` (resource temporarily unavailable). Cross-reference with `/proc/<PID>/limits` and current descriptor count via `ls /proc/<PID>/fd | wc -l`.

Is `ulimit` sufficient for resource isolation in a multi-tenant environment?

No. `ulimit` provides per-process and per-user guardrails but does not enforce memory bandwidth, disk I/O, or network throughput limits. For true multi-tenant isolation, combine `ulimit` with cgroups v2 resource controllers, and consider namespace isolation (user namespaces, PID namespaces) for stronger security boundaries. On managed infrastructure, these controls are typically layered at the hypervisor and container runtime level.

15%

Save 15% on All Hosting Services

Test your skills and get Discount on any hosting plan

Use code:

Skills
Get Started