On Linux, even when processes run as the same user, the configuration path for resource limits differs between a shell started from a PAM login session and a service started by systemd.
/etc/security/limits.conf and /etc/security/limits.d/*.conf are applied to login sessions by pam_limits, and the settings take effect in newly started sessions rather than existing sessions. Services started directly by systemd, on the other hand, do not pass through a PAM login session, so service-specific limits are configured with Unit settings such as LimitNOFILE=.
In this article, on an RHEL 8-compatible Linux system, we configure the following limits for the regular test user limitdemo.
- Login shell
nofile: soft 2048, hard 4096 - Login shell
nproc: soft 64, hard 128 - systemd service
nofile: soft/hard 8192
Finally, we verify that different nofile limits are actually applied to the login shell and systemd service processes even though they run as the same user.
Step 1: Create a test user
Section titled “Step 1: Create a test user”To avoid affecting existing users, prepare a dedicated regular user. If the user already exists, creation is skipped.
sudo id -u limitdemo >/dev/null 2>&1 || sudo useradd --create-home --shell /bin/bash limitdemoOn Linux, nproc corresponds to RLIMIT_NPROC, which limits the number of processes, or more precisely threads, per real user ID. Because this limit does not apply in cases such as processes with a real user ID of 0, use a regular user for verification.
Step 2: Configure nofile and nproc in limits.d
Section titled “Step 2: Configure nofile and nproc in limits.d”Create a user-specific configuration under /etc/security/limits.d/. The soft limit is the effective limit used during normal operation, while the hard limit is the maximum value to which an unprivileged process can raise the soft limit.
sudo tee /etc/security/limits.d/90-limitdemo.conf >/dev/null <<'EOF'limitdemo soft nofile 2048limitdemo hard nofile 4096limitdemo soft nproc 64limitdemo hard nproc 128EOFsudo chmod 0644 /etc/security/limits.d/90-limitdemo.confnofile corresponds to the number of file descriptors a process can open, while nproc corresponds to the number of processes for a user. Settings in limits.conf format are applied per login, so they do not change the limits of shells that already existed before the configuration was applied.
Step 3: Verify the limits in a new login session
Section titled “Step 3: Verify the limits in a new login session”Use su - to start a new login session for limitdemo, then check the soft and hard limits from Bash within that session.
sudo su - limitdemo -c 'printf "nofile_soft=%s\nnofile_hard=%s\nnproc_soft=%s\nnproc_hard=%s\n" "$(ulimit -Sn)" "$(ulimit -Hn)" "$(ulimit -Su)" "$(ulimit -Hu)"'If nofile_soft=2048, nofile_hard=4096, nproc_soft=64, and nproc_hard=128 are displayed, the settings have been applied to the PAM login session.
If the values do not change, check the PAM configuration for the login path you are using and confirm that pam_limits.so is called as a session module. pam_limits sets resource limits during PAM session processing.
Step 4: Configure LimitNOFILE for a systemd service
Section titled “Step 4: Configure LimitNOFILE for a systemd service”Next, create a service that runs as the same limitdemo user. Here, sleep is used to keep the process running, and LimitNOFILE=8192 is specified.
sudo tee /etc/systemd/system/limitdemo.service >/dev/null <<'EOF'[Unit]Description=Resource limit example service
[Service]Type=simpleUser=limitdemoExecStart=/usr/bin/sleep 1dLimitNOFILE=8192
[Install]WantedBy=multi-user.targetEOFsudo systemctl daemon-reloadsudo systemctl start limitdemo.serviceWith systemd, resource limits for service processes can be specified through Unit settings such as LimitNOFILE=. Changing the limits.conf configuration does not define the limits for services started directly by systemd.
Step 5: Verify nofile for the running service
Section titled “Step 5: Verify nofile for the running service”Instead of only displaying the Unit file, obtain the PID of the service that is actually running and check /proc/PID/limits, which is exposed by the kernel.
pid=$(sudo systemctl show --property=MainPID --value limitdemo.service)test "$pid" -gt 0sudo awk '$1=="Max" && $2=="open" && $3=="files" {printf "service_nofile_soft=%s\nservice_nofile_hard=%s\n",$4,$5}' "/proc/$pid/limits"If service_nofile_soft=8192 and service_nofile_hard=8192 are displayed, LimitNOFILE=8192 has been applied to the running service process.
Step 6: Compare the effective values for the shell and service
Section titled “Step 6: Compare the effective values for the shell and service”Finally, obtain the values from the PAM login session and the systemd service at the same time and verify that they match the intended values and differ from each other.
shell_soft=$(sudo su - limitdemo -c 'ulimit -Sn')shell_hard=$(sudo su - limitdemo -c 'ulimit -Hn')pid=$(sudo systemctl show --property=MainPID --value limitdemo.service)service_soft=$(sudo awk '$1=="Max" && $2=="open" && $3=="files" {print $4}' "/proc/$pid/limits")service_hard=$(sudo awk '$1=="Max" && $2=="open" && $3=="files" {print $5}' "/proc/$pid/limits")printf 'shell_nofile=%s/%s\n' "$shell_soft" "$shell_hard"printf 'service_nofile=%s/%s\n' "$service_soft" "$service_hard"test "$shell_soft" = 2048test "$shell_hard" = 4096test "$service_soft" = 8192test "$service_hard" = 8192test "$shell_soft" != "$service_soft"If the shell configured through limits.d uses 2048/4096 and the service configured through systemd uses 8192/8192, this confirms that independent limits can be set for the same user depending on the execution context.
If you want to limit the total number of tasks for an entire service, note that LimitNPROC= is based on RLIMIT_NPROC per real user ID, so other processes belonging to the same user are also counted. If the goal is per-service control, systemd’s cgroup-based TasksMax= is more appropriate.