Home / Alt manpages / pam_limits(8)

  • pam_limits(8)
  • Admin command
  • linux

Set Per-User File Descriptor Limits with pam_limits

An application quietly hitting "too many open files" is often a per-user limit, and pam_limits is the PAM module that sets one on login. This example sets a soft limit of 4096 and a hard limit of 8192 on the nofile item, then checks both values in a freshly created session. Allow about fifteen minutes, plus time to open that fresh session.

This guide is for a Linux system using the installed Linux-PAM module; here libpam-modules is version 1.5.3-5ubuntu5.7. You need the target account name and root access to edit /etc/security/limits.d. Keep an existing privileged session open while you test: a broken PAM session configuration can stop logins working at all.

1. Confirm that the module is available

pam_limits is a PAM session module, not a standalone command. Check the module file and package version without elevated privileges:

$ test -r /usr/lib/x86_64-linux-gnu/security/pam_limits.so && echo 'module is readable'
module is readable
$ dpkg-query -W -f='${Package} ${Version}\n' libpam-modules
libpam-modules:amd64 1.5.3-5ubuntu5.7

The module also has to sit in the PAM session stack for the login method you actually care about, so check that file before touching a limit:

$ rg -n 'pam_limits\.so' /etc/pam.d
/etc/pam.d/sshd:40:session     required     pam_limits.so

Your own output will differ: for SSH, check /etc/pam.d/sshd; for a local login, check /etc/pam.d/login. No matching session line there means a limits file alone will not apply to that service. This is a security-sensitive change either way, so review the existing stack and keep a recovery login handy.

2. Choose the account and inspect its current session

Replace APP_USER with a real, non-root account; the name in a limits file is literal, so do not leave the placeholder in place:

$ APP_USER='replace-with-real-user'
$ getent passwd "$APP_USER"
replace-with-real-user:x:1001:1001::/home/replace-with-real-user:/bin/bash

Run these in the account's current shell if you can, as a baseline, though they say nothing about what a new PAM session will actually receive:

$ ulimit -Sn
1048576
$ ulimit -Hn
1048576

These are the soft and hard RLIMIT_NOFILE values. The soft limit is the normal working ceiling; a process can raise it up to the hard limit but no further, and an ordinary user cannot raise the hard limit at all. Existing shells keep their old values even after you edit the configuration.

3. Add one focused limits.d file

Use a separate file rather than rewriting the vendor-managed /etc/security/limits.conf:

$ sudo install -o root -g root -m 0644 /dev/null /etc/security/limits.d/90-app-user-nofile.conf
$ sudo sh -c 'printf "%s\n" "replace-with-real-user soft nofile 4096" "replace-with-real-user hard nofile 8192" > /etc/security/limits.d/90-app-user-nofile.conf'

Substitute the real account in both lines. The four fields are domain, type, item and value: the domain here is the username, soft and hard select the limit type, and nofile counts open file descriptors. Do not reach for * unless you genuinely mean every matching user, since wildcard and group limits never apply to root, and an explicit username is clearer for a test like this anyway.

Check the file before opening a new session:

$ sudo sed -n '1,5p' /etc/security/limits.d/90-app-user-nofile.conf
replace-with-real-user soft nofile 4096
replace-with-real-user hard nofile 8192

Name the file late in the alphabet so it is easy to spot. Linux-PAM reads /etc/security/limits.conf first, then the *.conf files in /etc/security/limits.d in C-locale order, so a later matching rule elsewhere can still override this one. Check the whole directory if the values you see are not what you expect.

4. Start a completely new PAM session

Sign out and back in through the service whose stack you checked, or open a new SSH connection; a new terminal inside the same desktop login will not do. The module sets limits for a session, it does not touch existing processes, and nothing here is global or permanent.

In the new session, check both values:

$ ulimit -Sn
4096
$ ulimit -Hn
8192
$ prlimit --nofile $$
RESOURCE DESCRIPTION SOFT HARD UNITS
NOFILE   max number of open files 4096 8192 files

Exact spacing and wording from prlimit can vary; soft 4096 and hard 8192 is the result you are looking for. If you use sudo -iu APP_USER as a shortcut, remember that tests the sudo PAM service, not necessarily SSH or your display manager, so verify through the real service too.

Checkpoint

New session still showing the old values? Confirm the module sits in that service's session stack, the username matches getent passwd, and the file has four whitespace-separated fields, then look for another nofile rule elsewhere. A shell started from an old session can never demonstrate a new PAM result.

5. Understand the limit's boundary

This setting only reaches processes created in the session and their children. It does nothing for a systemd service that starts independently of PAM: use the service manager's own resource controls for that, and verify the service's limits directly rather than assuming a login limits file reaches it.

nofile is a per-process limit, not a guarantee the kernel can supply an unlimited number of files. The system-wide ceiling in /proc/sys/fs/nr_open also matters when you set a large hard value, and the kernel can reject a value outside its supported range. Pick a number that fits the workload and test it, rather than just copying something big.

Do not add set_all to make unspecified values look tidy. The manual warns it takes unspecified limits from PID 1, and when PID 1 is systemd those are not the kernel defaults. Leave it out unless you have a specific, tested reason to want it.

6. Undo the example safely

Removing the example file is a privileged, state-changing step, so keep a copy first if you might want to compare it later:

$ sudo cp --preserve=all /etc/security/limits.d/90-app-user-nofile.conf /etc/security/limits.d/90-app-user-nofile.conf.bak
$ sudo rm /etc/security/limits.d/90-app-user-nofile.conf

Do not leave that backup with a .conf suffix inside limits.d, or it may get read as another live configuration file. Start another fresh PAM session to confirm the example no longer applies; sessions already open keep their inherited limits until they exit.

If a login starts failing after any PAM edit, use the privileged recovery session you kept open, restore the last known-good file or remove the new limits file, and check the service logs. Never close the only working administrative session while you are still testing PAM.

Done means

  • Checked the install. The installed pam_limits.so and Linux-PAM package version were identified.
  • Confirmed the stack. The target service's PAM session stack contains pam_limits.so.
  • Added the rule. A username-specific nofile rule sits in a readable limits.d file.
  • Verified in a new session. A genuinely new session reports soft 4096 and hard 8192.
  • Know the limits of the limit. Old shells and independently started services are outside this verification.
  • Kept a way back. A recovery session and undo path exist before you touch PAM configuration.