Skip to content
Shell & TerminalDeep Dive Published Updated 8 min readViews unavailable

Bash ulimit and Process Resource Limits: Diagnose the Running Shell

Inspect and set Bash resource limits safely, distinguish soft from hard ceilings, and trace limits inherited by launched processes.

When a program cannot open another file, create a core dump, or allocate a requested resource, the limit may come from the process environment rather than the application configuration. Bash’s ulimit builtin inspects and changes resource limits associated with the current shell. The resulting limits matter because a child process inherits the applicable process limits when it is launched.

The useful operational model has three parts: the shell reports a current soft limit and a hard ceiling; a change to the current shell affects work launched afterward; and operating-system service managers or container boundaries may establish a different limit before the shell ever starts. A successful interactive test therefore does not prove that a CI runner, system service, SSH session, or container has the same budget.

Inspect limits in the process that fails

Start by collecting the current shell’s limits with ulimit -a, then inspect the specific resource suspected of failing. In Bash, -S selects the soft limit and -H selects the hard limit. The shell’s manual documents which resource each option controls and the units used by the platform. Do not compare values from different operating systems as if every resource used the same unit.

#!/usr/bin/env bash
set -u

printf 'open files soft: '
ulimit -S -n
printf 'open files hard: '
ulimit -H -n

printf 'all limits for this Bash process:\n'
ulimit -a

This command records the shell’s view without changing it. Capture the output in the same context as the failing workload and include user identity, parent service, container or job limits, and the program’s own error. A limit report from a different terminal is only a comparison point because the process ancestry and launch configuration can differ.

For descriptor failures, distinguish a per-process open-file limit from system-wide exhaustion, filesystem limits, a service-specific cap, and a leak. Raising the shell limit will not cure an application that opens one descriptor per request and never closes it. Check whether the count rises over time, whether descriptors are duplicated or inherited intentionally, and which resource is actually exhausted before changing a ceiling.

Understand soft limits and hard ceilings

The soft limit is the value enforced for ordinary operation. The hard limit is the ceiling to which an unprivileged process can generally raise its own soft limit. A process can lower its limits; raising a hard limit is privileged and is usually better configured at the service or session boundary that launches the process. Exact system rules and available resources are platform-dependent.

When neither soft nor hard is explicitly selected, Bash may attempt to affect both. In automation, request the intended scope explicitly. If a job only needs a larger soft descriptor allowance, set the soft value while respecting the inherited hard cap. Handle an unlimited representation deliberately rather than forcing every reported value into arithmetic.

#!/usr/bin/env bash
set -u

desired=4096
hard=$(ulimit -H -n) || exit 1

if [[ $hard == unlimited ]] || (( hard >= desired )); then
    ulimit -S -n "$desired" || {
        printf 'could not raise the soft open-file limit\n' >&2
        exit 1
    }
else
    printf 'hard open-file ceiling %s is below requested %s\n' "$hard" "$desired" >&2
    exit 1
fi

printf 'effective soft open-file limit: '
ulimit -S -n

The example changes only the current Bash process’s soft open-file limit. It deliberately refuses to claim success when the inherited hard ceiling is too low. The arithmetic check assumes a numeric finite hard value; platforms that print a different sentinel should be handled according to their Bash and operating-system documentation before enabling that branch in a portable script.

Do not add sudo as an automatic retry. A privileged command in a deployment script can change the trust boundary, and a new privileged child may not update the invoking shell’s limits in the way an operator expects. Configure the service manager, login session, or container runtime that owns the process budget, then launch a fresh workload to confirm the effective value.

Limits belong to process launch context

Resource limits are process attributes, not global Bash preferences. A change made in one shell does not retroactively change a sibling process, a parent process, or a service that has already started. It normally applies to future work launched from that shell and is inherited by descendants, subject to operating-system rules and any later changes those processes make.

This explains why setting ulimit in an interactive profile may appear to fix a manually launched command while a scheduled task still fails. The scheduler may not read the same startup files, may use a different account, or may start the shell with a smaller inherited hard cap. Put policy at the owner of the execution context: service configuration, container configuration, user session limits, or an explicit job wrapper.

In containerized work, both container runtime settings and the host’s process limits can matter. The value visible inside the container is the relevant diagnostic for the program, but it does not identify which layer imposed the ceiling. Inspect the container definition and the host service configuration, then test the actual entrypoint. Avoid assuming that a shell startup file executes for an ENTRYPOINT that launches a binary directly.

On systems using a service manager, a directive such as a file-descriptor limit can be more appropriate than a profile edit. The exact syntax, accepted units, privilege model, and reload behavior are manager-specific. On macOS, Linux distributions, BSD systems, and container orchestrators, limits are configured through different mechanisms. Consult that platform’s official service or session documentation and verify the effective child process after applying the change.

Separate limit failures from application failures

The message “too many open files” can result from a per-process descriptor ceiling, but diagnosis should include the process’s open descriptor count and the system’s available resources. A program may inherit descriptors it did not expect, hold sockets longer than intended, or create temporary files that it never closes. If the application has a supported diagnostic mode, use it to identify descriptor ownership rather than merely increasing the limit.

A higher cap can hide a leak for longer and allow one workload to consume resources needed by other processes. Establish an evidence-backed target from peak concurrency, per-worker descriptor use, and operational headroom. Add monitoring for exhaustion and test behavior at the intended cap. If a service fails when it reaches the limit, its error handling should close partially opened resources and fail in a controlled way.

Other ulimit resources need their own interpretation. Core-file size can affect post-crash diagnostics; maximum user processes can interact with worker creation; file size can constrain generated artifacts; and address-space limits interact with runtime and platform behavior. The option letters and units are not a universal abstraction. Read the installed Bash manual and platform documentation before encoding a specific resource value.

Check whether a resource is actually governed by ulimit on the target system before treating it as a tunable. A program can fail because the kernel refuses an operation for another reason, because a cgroup or container policy imposes a separate ceiling, or because a library has its own pool limit. Correlate the shell’s reported value with the failing system call or application diagnostic and the host’s resource accounting. Raising one limit without identifying the enforcing layer can leave the failure unchanged while making capacity planning less predictable.

Test the limit change without guessing

Use a disposable process to test whether a proposed soft limit is accepted. Report the value before and after, run a representative workload, and inspect the child process rather than only the parent shell. For descriptor limits, create a bounded test that opens a known number of files, handles errors, and closes every descriptor. Never run an unbounded resource-exhaustion test on a production host.

When validating a service, restart or re-create it through its normal manager after a configuration change. Inspect the resulting process tree and confirm the value from the service’s own execution context. Compare a direct binary launch with the actual production entrypoint only when doing so is safe and useful; their environments and ancestry can differ substantially.

Record the intended limit and its owner in deployment configuration. A comment in a user’s .bashrc is not durable documentation for a system service. Keep limits under version control where practical, include an observable verification step, and state which operating-system versions the policy supports.

Operational checklist

Capture ulimit output from the process context that fails. Identify the exact resource, compare the soft value with the hard ceiling, determine which parent or manager supplied those limits, and inspect application behavior for leaks. Change only the needed limit and configure it at the correct launch boundary. Re-test through the real service or job entrypoint and verify the child process’s effective value.

Bash ulimit is a useful diagnostic and a narrow per-process control, not a universal system configuration tool. The difference between an interactive shell and a managed process is often the decisive clue. Once ownership and inheritance are understood, resource-limit changes can be made deliberately and verified without masking application defects.

Related:

Sources:

Comments