Learn to set up comprehensive user session resource limits using both traditional pam_limits.conf and modern systemd user slices. This tutorial covers memory, CPU, and process limits to prevent resource exhaustion on multi-user systems.
Prerequisites
- Root or sudo access
- Basic understanding of Linux user management
- Familiarity with systemd and PAM concepts
What this solves
User session limits prevent individual users from consuming excessive system resources that could impact other users or crash the entire system. This becomes critical on shared servers, development environments, or production systems where multiple applications run under different user accounts. Without proper resource controls, a single runaway process or poorly configured application can monopolize CPU, memory, or file handles, causing system instability.
This tutorial combines traditional PAM limits with modern systemd user slice management to provide comprehensive resource control. You'll learn to set memory limits, CPU quotas, maximum processes, and file descriptor limits that automatically apply to user sessions and persist across reboots.
Understanding user session limits and resource control mechanisms
Linux provides two primary mechanisms for controlling user resource consumption: PAM limits (configured via /etc/security/limits.conf) and systemd user slices. PAM limits work at the session level and control traditional Unix resources like memory, file descriptors, and process counts. Systemd user slices provide more granular control over modern resources like CPU time and memory cgroups.
PAM limits apply when users log in through SSH, console, or display managers. They use the pam_limits module to enforce restrictions defined in the limits configuration files. These limits include both soft limits (warnings) and hard limits (absolute maximums) for various resources.
Systemd user slices automatically create a dedicated cgroup for each user session, allowing precise control over CPU usage, memory consumption, and I/O bandwidth. User slices persist as long as the user has active processes and can span multiple login sessions.
Step-by-step configuration
Install required packages
Ensure you have the necessary PAM modules and systemd tools installed for resource management.
sudo apt update
sudo apt install -y libpam-modules systemd cgroup-toolsConfigure PAM limits for traditional resources
Set up basic resource limits using the PAM limits module. This configuration applies to all user sessions and controls fundamental Unix resources.
# Memory limits (in KB)
* soft as 2097152
* hard as 4194304
Maximum number of processes
* soft nproc 1024
* hard nproc 2048
Maximum number of open files
* soft nofile 4096
* hard nofile 8192
CPU time limits (in seconds)
* soft cpu 3600
* hard cpu 7200
Core dump size (in KB)
* soft core 0
* hard core 102400
Maximum file size (in KB)
* soft fsize 1048576
* hard fsize 2097152Create user-specific limits
Configure specific limits for individual users or groups that require different resource allocations.
# Database user needs more memory and connections
mysql soft as 8388608
mysql hard as 16777216
mysql soft nproc 2048
mysql hard nproc 4096
mysql soft nofile 16384
mysql hard nofile 32768
Web server user limits
www-data soft as 4194304
www-data hard as 8388608
www-data soft nproc 512
www-data hard nproc 1024
www-data soft nofile 8192
www-data hard nofile 16384
Developer group gets higher limits
@developers soft nproc 4096
@developers hard nproc 8192
@developers soft nofile 8192
@developers hard nofile 16384Configure systemd user slice limits
Create systemd user slice configuration to control modern cgroup resources like memory and CPU quotas.
[Unit]
Description=User Slice Resource Limits
[Slice]
Memory limit per user (2GB)
MemoryAccounting=yes
MemoryLimit=2G
CPU quota (50% of one CPU core)
CPUAccounting=yes
CPUQuota=50%
I/O limits
IOAccounting=yes
IOReadBandwidthMax=/ 50M
IOWriteBandwidthMax=/ 25M
Task limit (maximum processes/threads)
TasksAccounting=yes
TasksMax=1024Create the systemd configuration directory
Ensure the systemd user slice configuration directory exists and has correct permissions.
sudo mkdir -p /etc/systemd/system/user-.slice.d
sudo chown root:root /etc/systemd/system/user-.slice.d
sudo chmod 755 /etc/systemd/system/user-.slice.dConfigure specific user slice overrides
Create individual user slice configurations for users requiring different resource allocations.
sudo mkdir -p /etc/systemd/system/user-1001.slice.d[Unit]
Description=Custom limits for user ID 1001
[Slice]
Higher memory limit for database user
MemoryAccounting=yes
MemoryLimit=8G
More CPU resources
CPUAccounting=yes
CPUQuota=200%
Higher task limit
TasksAccounting=yes
TasksMax=4096Enable PAM limits module
Ensure the PAM limits module is enabled in the system authentication configuration.
sudo grep -q "pam_limits.so" /etc/pam.d/common-session || echo "session required pam_limits.so" | sudo tee -a /etc/pam.d/common-sessionReload systemd configuration
Apply the new systemd user slice configurations without requiring a system reboot.
sudo systemctl daemon-reload
sudo systemctl restart systemd-logindConfigure kernel parameters for resource management
Set kernel parameters that affect resource limits and memory management.
# Increase maximum number of memory map areas
vm.max_map_count = 262144
Adjust overcommit memory settings
vm.overcommit_memory = 1
vm.overcommit_ratio = 80
Increase maximum number of open files system-wide
fs.file-max = 1048576
Increase maximum number of processes
kernel.pid_max = 131072
Control swap usage
vm.swappiness = 10sudo sysctl -p /etc/sysctl.d/99-user-limits.confMonitor and troubleshoot user resource consumption
Install monitoring tools
Install utilities for monitoring user resource consumption and troubleshooting limit issues.
sudo apt install -y htop iotop sysstat procpsMonitor user resource usage
Use various commands to monitor current user resource consumption and verify limits are working.
# Check current user limits
ulimit -a
View user processes and resource usage
ps auxf --sort=-%mem | head -20
Monitor user slice resource usage
sudo systemctl status user-$(id -u).slice
Check cgroup resource usage
sudo cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/memory.usage_in_bytes
sudo cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/memory.limit_in_bytesCreate monitoring script
Develop a script to regularly monitor user resource consumption and alert on limit violations.
#!/bin/bash
Monitor user resource limits
LOG_FILE="/var/log/user-limits.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] User resource monitoring" >> "$LOG_FILE"
Check for users approaching limits
for USER_DIR in /sys/fs/cgroup/user.slice/user-*.slice; do
if [ -d "$USER_DIR" ]; then
USER_ID=$(basename "$USER_DIR" | sed 's/user-\([0-9]*\).slice/\1/')
USER_NAME=$(getent passwd "$USER_ID" | cut -d: -f1)
if [ -n "$USER_NAME" ]; then
# Check memory usage
MEMORY_USAGE=$(cat "$USER_DIR/memory.usage_in_bytes" 2>/dev/null || echo 0)
MEMORY_LIMIT=$(cat "$USER_DIR/memory.limit_in_bytes" 2>/dev/null || echo 0)
if [ "$MEMORY_LIMIT" -gt 0 ] && [ "$MEMORY_USAGE" -gt 0 ]; then
MEMORY_PERCENT=$((MEMORY_USAGE * 100 / MEMORY_LIMIT))
if [ "$MEMORY_PERCENT" -gt 80 ]; then
echo "[$DATE] WARNING: User $USER_NAME using ${MEMORY_PERCENT}% of memory limit" >> "$LOG_FILE"
fi
fi
# Check task count
TASK_CURRENT=$(cat "$USER_DIR/pids.current" 2>/dev/null || echo 0)
TASK_LIMIT=$(cat "$USER_DIR/pids.max" 2>/dev/null || echo 0)
if [ "$TASK_LIMIT" != "max" ] && [ "$TASK_CURRENT" -gt 0 ] && [ "$TASK_LIMIT" -gt 0 ]; then
TASK_PERCENT=$((TASK_CURRENT * 100 / TASK_LIMIT))
if [ "$TASK_PERCENT" -gt 80 ]; then
echo "[$DATE] WARNING: User $USER_NAME using ${TASK_PERCENT}% of task limit" >> "$LOG_FILE"
fi
fi
fi
fi
donesudo chmod 755 /usr/local/bin/monitor-user-limits.shSet up automated monitoring
Create a systemd timer to run the monitoring script periodically.
[Unit]
Description=Monitor User Resource Limits
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/monitor-user-limits.sh
User=root
Group=root[Unit]
Description=Run user limits monitoring every 5 minutes
Requires=monitor-user-limits.service
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now monitor-user-limits.timerVerify your setup
Test that your user session limits are properly configured and functioning as expected.
# Check current user limits
ulimit -a
Test soft limits (should work)
ulimit -n 4096
echo "Soft limit test: $?"
Test hard limits (should fail if exceeded)
ulimit -n 16384
echo "Hard limit test: $?"
View systemd user slice status
sudo systemctl status user-$(id -u).slice
Check cgroup limits
find /sys/fs/cgroup -name "user-$(id -u).slice" -type d 2>/dev/null
Verify PAM limits module is loaded
sudo grep pam_limits /etc/pam.d/common-session
Check monitoring timer
sudo systemctl status monitor-user-limits.timerCreate a test to verify memory limits are enforced by attempting to allocate memory beyond the configured limit. Use the stress tool to simulate resource consumption and observe how limits prevent system overload. The monitoring tools you configured will help track resource usage and identify when users approach their allocated limits.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Limits not applied to SSH sessions | PAM limits module not enabled | Add session required pam_limits.so to /etc/pam.d/sshd |
| Systemd limits not working | User slice configuration not loaded | sudo systemctl daemon-reload && sudo systemctl restart systemd-logind |
| Memory limits not enforced | Cgroup v1 vs v2 compatibility | Check /sys/fs/cgroup structure and use appropriate syntax |
| Process limits too restrictive | Hard limits set too low | Increase limits in /etc/security/limits.conf and reload PAM |
| Application fails to start | File descriptor limits too low | Increase nofile limits for specific user or service |
| Monitoring script not running | Timer not enabled or permissions issue | Check timer status and log file permissions |
Advanced configuration options
Fine-tune your resource management setup with additional configuration options for specific use cases. You can integrate user limits with system performance monitoring tools to create comprehensive resource tracking. For database servers, consider specialized limits that account for connection pooling and query processing requirements.
Enterprise environments may benefit from integrating user limits with centralized logging systems to track resource usage patterns across multiple servers. This helps identify users who consistently approach their limits and may need adjusted quotas or optimization guidance.
Consider implementing graduated limits where users start with conservative resource allocations and can request increases based on demonstrated need. This approach prevents resource waste while accommodating legitimate high-resource applications.
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
DEFAULT_MEMORY_LIMIT="2G"
DEFAULT_CPU_QUOTA="50%"
DEFAULT_MAX_PROCESSES="1024"
# Parse arguments
MEMORY_LIMIT="${1:-$DEFAULT_MEMORY_LIMIT}"
CPU_QUOTA="${2:-$DEFAULT_CPU_QUOTA}"
MAX_PROCESSES="${3:-$DEFAULT_MAX_PROCESSES}"
usage() {
echo "Usage: $0 [memory_limit] [cpu_quota] [max_processes]"
echo " memory_limit: User memory limit (default: 2G)"
echo " cpu_quota: CPU quota per user (default: 50%)"
echo " max_processes: Maximum processes per user (default: 1024)"
echo "Example: $0 4G 75% 2048"
exit 1
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
fi
# Cleanup function
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
# Remove any created files on failure
rm -f /etc/security/limits.d/99-user-limits.conf.tmp
rm -rf /etc/systemd/system/user-.slice.d.tmp
exit 1
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
echo -e "${GREEN}Configuring Linux user session limits with systemd and PAM${NC}"
# Auto-detect distribution
echo -e "${YELLOW}[1/7] Detecting system distribution...${NC}"
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PAM_PACKAGES="libpam-modules systemd"
CGROUP_TOOLS="cgroup-tools"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PAM_PACKAGES="pam systemd"
CGROUP_TOOLS="libcgroup-tools"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PAM_PACKAGES="pam systemd"
CGROUP_TOOLS="libcgroup-tools"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
# Update package repository
echo -e "${YELLOW}[2/7] Updating package repository...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
apt update
fi
# Install required packages
echo -e "${YELLOW}[3/7] Installing required packages...${NC}"
$PKG_INSTALL $PAM_PACKAGES
# Install cgroup tools if available (may not exist on all systems)
$PKG_INSTALL $CGROUP_TOOLS || echo -e "${YELLOW}Warning: cgroup tools not available${NC}"
# Configure PAM limits
echo -e "${YELLOW}[4/7] Configuring PAM limits...${NC}"
cat > /etc/security/limits.d/99-user-limits.conf.tmp << 'EOF'
# User session limits configuration
# Format: <domain> <type> <item> <value>
# Default limits for all users
# Memory limits (in KB) - 2GB soft, 4GB hard
* soft as 2097152
* hard as 4194304
# Maximum number of processes
* soft nproc 1024
* hard nproc 2048
# Maximum number of open files
* soft nofile 4096
* hard nofile 8192
# CPU time limits (in seconds) - 1 hour soft, 2 hours hard
* soft cpu 3600
* hard cpu 7200
# Core dump size (disabled by default)
* soft core 0
* hard core 102400
# Maximum file size (in KB) - 1GB soft, 2GB hard
* soft fsize 1048576
* hard fsize 2097152
# Special service accounts
# Web server limits
www-data soft as 4194304
www-data hard as 8388608
www-data soft nproc 512
www-data hard nproc 1024
www-data soft nofile 8192
www-data hard nofile 16384
# Apache user (RHEL-based systems)
apache soft as 4194304
apache hard as 8388608
apache soft nproc 512
apache hard nproc 1024
apache soft nofile 8192
apache hard nofile 16384
# Nginx user
nginx soft as 4194304
nginx hard as 8388608
nginx soft nproc 512
nginx hard nproc 1024
nginx soft nofile 8192
nginx hard nofile 16384
EOF
# Move config file to final location
mv /etc/security/limits.d/99-user-limits.conf.tmp /etc/security/limits.d/99-user-limits.conf
chmod 644 /etc/security/limits.d/99-user-limits.conf
chown root:root /etc/security/limits.d/99-user-limits.conf
# Create systemd user slice configuration
echo -e "${YELLOW}[5/7] Configuring systemd user slice limits...${NC}"
mkdir -p /etc/systemd/system/user-.slice.d.tmp
cat > /etc/systemd/system/user-.slice.d.tmp/resource-limits.conf << EOF
[Unit]
Description=User Slice Resource Limits
[Slice]
# Memory limit per user
MemoryAccounting=yes
MemoryLimit=${MEMORY_LIMIT}
# CPU quota per user
CPUAccounting=yes
CPUQuota=${CPU_QUOTA}
# I/O limits
IOAccounting=yes
IOReadBandwidthMax=/ 50M
IOWriteBandwidthMax=/ 25M
# Task limit (maximum processes/threads)
TasksAccounting=yes
TasksMax=${MAX_PROCESSES}
EOF
# Move systemd config to final location
mv /etc/systemd/system/user-.slice.d.tmp /etc/systemd/system/user-.slice.d
chmod 755 /etc/systemd/system/user-.slice.d
chmod 644 /etc/systemd/system/user-.slice.d/resource-limits.conf
chown -R root:root /etc/systemd/system/user-.slice.d
# Reload systemd configuration
echo -e "${YELLOW}[6/7] Reloading systemd configuration...${NC}"
systemctl daemon-reload
# Verify configuration
echo -e "${YELLOW}[7/7] Verifying configuration...${NC}"
# Check PAM limits file
if [[ -f /etc/security/limits.d/99-user-limits.conf ]]; then
echo -e "${GREEN}✓ PAM limits configuration created successfully${NC}"
else
echo -e "${RED}✗ PAM limits configuration failed${NC}"
exit 1
fi
# Check systemd user slice configuration
if [[ -f /etc/systemd/system/user-.slice.d/resource-limits.conf ]]; then
echo -e "${GREEN}✓ Systemd user slice configuration created successfully${NC}"
else
echo -e "${RED}✗ Systemd user slice configuration failed${NC}"
exit 1
fi
# Verify systemd can load the configuration
if systemctl cat user-.slice >/dev/null 2>&1; then
echo -e "${GREEN}✓ Systemd configuration is valid${NC}"
else
echo -e "${RED}✗ Systemd configuration validation failed${NC}"
exit 1
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo
echo -e "${YELLOW}Configuration Summary:${NC}"
echo "- Memory limit per user: ${MEMORY_LIMIT}"
echo "- CPU quota per user: ${CPU_QUOTA}"
echo "- Maximum processes per user: ${MAX_PROCESSES}"
echo "- PAM limits configured in: /etc/security/limits.d/99-user-limits.conf"
echo "- Systemd limits configured in: /etc/systemd/system/user-.slice.d/resource-limits.conf"
echo
echo -e "${YELLOW}Next steps:${NC}"
echo "1. User limits will apply to new login sessions"
echo "2. Existing users may need to log out and back in"
echo "3. Test with: ulimit -a (for PAM limits)"
echo "4. Monitor with: systemctl status user-\$UID.slice (for systemd limits)"
echo "5. View current limits: cat /proc/\$PID/limits"
Review the script before running. Execute with: bash install.sh