Learn how to configure systemd user services to automatically start applications without root privileges. This guide covers creating service units, managing lifecycle, and troubleshooting user services for reliable application startup.
Prerequisites
- Non-root user account
- Basic command line knowledge
- systemd-enabled Linux distribution
What this solves
Systemd user services let you run applications automatically at user login without root privileges. Unlike system services that require sudo access, user services run in your user session and start when you log in. This is essential for applications that don't need system-wide access but should start automatically, like development servers, personal automation scripts, or user-specific daemons.
Understanding systemd user services
User services operate differently from system services. They run under your user account, use your user's permissions, and start when you log in rather than at boot time. Each user has their own systemd instance that manages their services independently.
User service files are stored in ~/.config/systemd/user/ or system-wide user templates in /usr/lib/systemd/user/. The systemctl --user command manages these services instead of the regular systemctl command used for system services.
Step-by-step configuration
Create the user service directory
First, create the directory where user service files are stored. This directory might not exist by default.
mkdir -p ~/.config/systemd/user
Create a basic user service unit
Create a service file for a simple web application. This example starts a Python HTTP server that serves files from your home directory.
[Unit]
Description=Personal Web Server
After=network.target
[Service]
Type=exec
ExecStart=/usr/bin/python3 -m http.server 8080
WorkingDirectory=%h/public
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
Create the working directory
Create the directory that the service will use. The %h in the service file expands to your home directory.
mkdir -p ~/public
echo "Hello from user service
" > ~/public/index.html
Reload and enable the service
Tell systemd to reload its configuration and enable your new service. The --user flag is essential for user services.
systemctl --user daemon-reload
systemctl --user enable webserver.service
Start the service
Start your service immediately. It will also start automatically when you log in from now on.
systemctl --user start webserver.service
Advanced service configuration
Create a Node.js application service
This example shows a more complex service that runs a Node.js application with environment variables and specific user permissions.
[Unit]
Description=Node.js Application
After=network.target
[Service]
Type=exec
ExecStart=/usr/bin/node server.js
WorkingDirectory=%h/myapp
Environment=NODE_ENV=production
Environment=PORT=3000
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodeapp
[Install]
WantedBy=default.target
Configure service dependencies
Create a service that depends on other services or system conditions. This Redis service waits for the network and can be required by other services.
[Unit]
Description=User Redis Server
After=network.target
Before=nodeapp.service
[Service]
Type=forking
ExecStart=/usr/bin/redis-server %h/.config/redis/redis.conf --daemonize yes
ExecStop=/usr/bin/redis-cli shutdown
PIDFile=%h/.config/redis/redis.pid
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
Set up environment files
Use environment files to manage configuration separately from the service unit. This keeps sensitive data out of the service file.
DATABASE_URL=postgresql://user:password@localhost/myapp
API_KEY=your_secret_api_key
DEBUG=false
LOG_LEVEL=info
[Unit]
Description=Web Application
After=network.target
[Service]
Type=exec
ExecStart=%h/myapp/bin/webapp
WorkingDirectory=%h/myapp
EnvironmentFile=%h/.config/systemd/user/webapp.env
Restart=always
RestartSec=15
User=%i
[Install]
WantedBy=default.target
Managing user service lifecycle
Check service status
View the current status and recent log entries for your services.
systemctl --user status webserver.service
systemctl --user list-units --type=service
View service logs
User services log to the user journal. Use journalctl with the --user flag to view logs.
journalctl --user -u webserver.service
journalctl --user -u webserver.service -f
Stop and disable services
Stop a running service and prevent it from starting automatically.
systemctl --user stop webserver.service
systemctl --user disable webserver.service
Enable lingering for persistent services
By default, user services only run while you're logged in. Enable lingering to keep services running even after logout.
sudo loginctl enable-linger $USER
Service unit configuration options
Understanding key service unit options helps you configure reliable services. Here are the most important sections and directives:
| Section | Directive | Purpose |
|---|---|---|
| [Unit] | Description | Human-readable service description |
| [Unit] | After | Start after specified units |
| [Unit] | Requires | Hard dependency on other units |
| [Service] | Type | Service startup behavior (exec, forking, oneshot) |
| [Service] | ExecStart | Command to start the service |
| [Service] | WorkingDirectory | Directory to run the command from |
| [Service] | Restart | When to restart (always, on-failure, no) |
| [Service] | RestartSec | Time to wait before restart |
| [Install] | WantedBy | Target that should include this service |
Create a timer-based service
Systemd timers can replace cron jobs for user tasks. Create a service that runs periodically.
[Unit]
Description=Personal Backup Service
[Service]
Type=oneshot
ExecStart=/bin/bash %h/bin/backup.sh
WorkingDirectory=%h
[Unit]
Description=Run backup every day
Requires=backup.service
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer
Enable and start the timer instead of the service directly. The timer will trigger the service according to the schedule.
systemctl --user daemon-reload
systemctl --user enable --now backup.timer
systemctl --user list-timers
Security and resource limits
User services inherit your user's permissions and limitations. You can add additional restrictions using systemd's security features. For comprehensive security hardening, see our guide on systemd service resource limits and security isolation.
Add resource limits
Limit CPU, memory, and other resources to prevent runaway processes.
[Unit]
Description=Resource Limited Service
After=network.target
[Service]
Type=exec
ExecStart=/usr/bin/python3 -m http.server 8081
WorkingDirectory=%h/public
Restart=always
RestartSec=5
Resource limits
CPUQuota=50%
MemoryMax=512M
TasksMax=50
Security restrictions
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=%h/public
NoNewPrivileges=true
[Install]
WantedBy=default.target
Verify your setup
systemctl --user status webserver.service
systemctl --user list-units --type=service --state=running
journalctl --user -u webserver.service --no-pager -n 10
curl http://localhost:8080
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service doesn't start | Invalid unit file syntax | systemctl --user daemon-reload and check systemctl --user status servicename |
| Service stops when you logout | Lingering not enabled | sudo loginctl enable-linger $USER |
| Permission denied errors | Service trying to access restricted files | Check file permissions and service WorkingDirectory |
| Environment variables not working | Variables not properly set in service | Use Environment= or EnvironmentFile= in service unit |
| Service fails to restart | RestartSec too low or resource exhaustion | Increase RestartSec and check resource limits |
| Cannot find executable | Relative path in ExecStart | Use absolute paths for all executables |
Advanced configuration and troubleshooting
Debug service startup issues
When services fail to start, use these commands to diagnose the problem.
systemctl --user status webserver.service -l
journalctl --user -u webserver.service --since "1 hour ago"
systemd-analyze --user verify ~/.config/systemd/user/webserver.service
Test service configurations
Verify your service file syntax before enabling the service.
systemd-analyze --user verify ~/.config/systemd/user/webserver.service
systemctl --user show webserver.service
Monitor service performance
Check resource usage and performance metrics for your user services.
systemctl --user show webserver.service --property=MainPID,CPUUsageNSec,MemoryCurrent
systemd-cgtop --user
Create service templates
Templates let you create multiple instances of the same service with different parameters.
[Unit]
Description=Worker Service %i
After=network.target
[Service]
Type=exec
ExecStart=%h/bin/worker --instance=%i
WorkingDirectory=%h
Restart=always
RestartSec=10
Environment=WORKER_ID=%i
[Install]
WantedBy=default.target
Enable multiple service instances
Start multiple instances of the template service with different parameters.
systemctl --user enable --now worker@1.service
systemctl --user enable --now worker@2.service
systemctl --user status worker@*.service
Integration with system services
User services can interact with system services and resources. For container workloads, consider our guide on configuring container resource limits with Docker and systemd.
Create a service that monitors system resources
This service monitors system metrics and logs alerts to your user journal.
[Unit]
Description=System Monitor
After=network.target
[Service]
Type=exec
ExecStart=/bin/bash -c 'while true; do df -h / | tail -1 | awk "{if(\$5+0>90) print \"Disk usage critical: \" \$5}" | logger -t monitor; sleep 300; done'
Restart=always
RestartSec=30
[Install]
WantedBy=default.target
Next steps
- Configure systemd service resource limits and security isolation
- Configure container resource limits with Docker and systemd
- Configure systemd user service networking and port management
- Monitor systemd user services with Prometheus and Grafana
- Automate systemd user service backup and recovery
Running this in production?
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'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
SERVICE_NAME=""
SERVICE_DESCRIPTION=""
EXEC_START=""
WORKING_DIR=""
SERVICE_TYPE="exec"
RESTART_POLICY="always"
RESTART_SEC="5"
ENVIRONMENT_VARS=""
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Configure systemd user services for application startup"
echo ""
echo "OPTIONS:"
echo " -n, --name NAME Service name (required)"
echo " -d, --description DESC Service description"
echo " -e, --exec COMMAND Command to execute (required)"
echo " -w, --workdir DIR Working directory (defaults to \$HOME)"
echo " -t, --type TYPE Service type (default: exec)"
echo " -r, --restart POLICY Restart policy (default: always)"
echo " -s, --restart-sec SEC Restart delay in seconds (default: 5)"
echo " --env KEY=VALUE Environment variable (can be used multiple times)"
echo " -h, --help Show this help"
echo ""
echo "Examples:"
echo " $0 -n webserver -d 'Personal Web Server' -e '/usr/bin/python3 -m http.server 8080' -w '\$HOME/public'"
echo " $0 -n nodeapp -e '/usr/bin/node server.js' -w '\$HOME/myapp' --env NODE_ENV=production --env PORT=3000"
}
# Cleanup function
cleanup() {
if [ $? -ne 0 ]; then
echo -e "${RED}[ERROR] Script failed. Check the output above for details.${NC}"
fi
}
trap cleanup EXIT
# Log functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name)
SERVICE_NAME="$2"
shift 2
;;
-d|--description)
SERVICE_DESCRIPTION="$2"
shift 2
;;
-e|--exec)
EXEC_START="$2"
shift 2
;;
-w|--workdir)
WORKING_DIR="$2"
shift 2
;;
-t|--type)
SERVICE_TYPE="$2"
shift 2
;;
-r|--restart)
RESTART_POLICY="$2"
shift 2
;;
-s|--restart-sec)
RESTART_SEC="$2"
shift 2
;;
--env)
ENVIRONMENT_VARS="${ENVIRONMENT_VARS}Environment=$2\n"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Validate required arguments
if [ -z "$SERVICE_NAME" ] || [ -z "$EXEC_START" ]; then
log_error "Service name and exec command are required"
usage
exit 1
fi
# Set default description if not provided
if [ -z "$SERVICE_DESCRIPTION" ]; then
SERVICE_DESCRIPTION="User service: $SERVICE_NAME"
fi
# Set default working directory if not provided
if [ -z "$WORKING_DIR" ]; then
WORKING_DIR="%h"
fi
}
# Check if running as root
check_root() {
if [ "$EUID" -eq 0 ]; then
log_error "This script should not be run as root since it configures user services"
exit 1
fi
}
# Detect distribution and set package manager
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="sudo apt install -y"
PKG_UPDATE="sudo apt update"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="sudo dnf install -y"
PKG_UPDATE="sudo dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="sudo yum install -y"
PKG_UPDATE="sudo yum update -y"
;;
*)
log_error "Unsupported distro: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
log_info "Detected distribution: $PRETTY_NAME"
}
# Check prerequisites
check_prerequisites() {
echo "[1/7] Checking prerequisites..."
# Check if systemctl is available
if ! command -v systemctl >/dev/null 2>&1; then
log_error "systemctl not found. This system doesn't appear to use systemd."
exit 1
fi
# Check if user systemd is running
if ! systemctl --user status >/dev/null 2>&1; then
log_warning "User systemd instance may not be running. Attempting to start..."
sudo loginctl enable-linger "$USER"
fi
log_success "Prerequisites check completed"
}
# Create user service directory
create_service_directory() {
echo "[2/7] Creating user service directory..."
local service_dir="$HOME/.config/systemd/user"
if [ ! -d "$service_dir" ]; then
mkdir -p "$service_dir"
chmod 755 "$service_dir"
log_success "Created directory: $service_dir"
else
log_info "Directory already exists: $service_dir"
fi
}
# Create working directory if specified
create_working_directory() {
echo "[3/7] Setting up working directory..."
if [ "$WORKING_DIR" != "%h" ]; then
# Expand environment variables in working directory
local expanded_dir=$(eval echo "$WORKING_DIR")
if [ ! -d "$expanded_dir" ]; then
mkdir -p "$expanded_dir"
chmod 755 "$expanded_dir"
log_success "Created working directory: $expanded_dir"
else
log_info "Working directory already exists: $expanded_dir"
fi
else
log_info "Using home directory as working directory"
fi
}
# Generate service file content
generate_service_file() {
echo "[4/7] Generating service file..."
local service_file="$HOME/.config/systemd/user/${SERVICE_NAME}.service"
# Create service file content
cat > "$service_file" << EOF
[Unit]
Description=$SERVICE_DESCRIPTION
After=network.target
[Service]
Type=$SERVICE_TYPE
ExecStart=$EXEC_START
EOF
# Add working directory if specified
if [ "$WORKING_DIR" != "%h" ]; then
echo "WorkingDirectory=$WORKING_DIR" >> "$service_file"
fi
# Add environment variables if specified
if [ -n "$ENVIRONMENT_VARS" ]; then
echo -e "$ENVIRONMENT_VARS" >> "$service_file"
fi
# Add restart configuration
cat >> "$service_file" << EOF
Restart=$RESTART_POLICY
RestartSec=$RESTART_SEC
StandardOutput=journal
StandardError=journal
SyslogIdentifier=$SERVICE_NAME
[Install]
WantedBy=default.target
EOF
chmod 644 "$service_file"
log_success "Service file created: $service_file"
}
# Enable and start the service
enable_service() {
echo "[5/7] Enabling and starting service..."
# Reload systemd user daemon
systemctl --user daemon-reload
log_success "Systemd user daemon reloaded"
# Enable the service
systemctl --user enable "${SERVICE_NAME}.service"
log_success "Service enabled: ${SERVICE_NAME}.service"
# Start the service
systemctl --user start "${SERVICE_NAME}.service"
log_success "Service started: ${SERVICE_NAME}.service"
}
# Verify service is running
verify_service() {
echo "[6/7] Verifying service status..."
# Check if service is active
if systemctl --user is-active --quiet "${SERVICE_NAME}.service"; then
log_success "Service is running: ${SERVICE_NAME}.service"
else
log_error "Service failed to start: ${SERVICE_NAME}.service"
log_info "Service status:"
systemctl --user status "${SERVICE_NAME}.service" --no-pager
return 1
fi
# Check if service is enabled
if systemctl --user is-enabled --quiet "${SERVICE_NAME}.service"; then
log_success "Service is enabled for auto-start: ${SERVICE_NAME}.service"
else
log_warning "Service is not enabled for auto-start"
fi
}
# Show service management commands
show_management_info() {
echo "[7/7] Service configuration completed!"
echo ""
echo -e "${GREEN}Service Management Commands:${NC}"
echo " Status: systemctl --user status ${SERVICE_NAME}.service"
echo " Stop: systemctl --user stop ${SERVICE_NAME}.service"
echo " Start: systemctl --user start ${SERVICE_NAME}.service"
echo " Restart: systemctl --user restart ${SERVICE_NAME}.service"
echo " Logs: journalctl --user -u ${SERVICE_NAME}.service -f"
echo " Disable: systemctl --user disable ${SERVICE_NAME}.service"
echo ""
echo -e "${YELLOW}Note:${NC} User services start automatically when you log in."
echo -e "${YELLOW}Note:${NC} To enable services to start without login, run: sudo loginctl enable-linger $USER"
}
# Main function
main() {
parse_args "$@"
check_root
detect_distro
check_prerequisites
create_service_directory
create_working_directory
generate_service_file
enable_service
verify_service
show_management_info
}
# Run main function with all arguments
main "$@"
Review the script before running. Execute with: bash install.sh