Vibe monitoring with Last9 MCP: Ask your agent to fix production issues! Setup →
Last9 Last9

Mar 17th, ‘25 / 20 min read

systemctl: The Complete Guide to Managing Linux Services

Learn how to use systemctl to start, stop, and manage services on Linux. From basics to advanced tips, this guide covers it all.

systemctl: The Complete Guide to Managing Linux Services

Ever found yourself staring at your terminal, wondering why a service won’t start? systemctl is the backbone of modern Linux service management, but if you’re new to it, it can feel overwhelming.

This guide breaks it down—covering essential commands and advanced techniques in a clear, practical way. No unnecessary jargon, just the know-how you need to manage services with confidence.

systemctl: The Linux Service Manager Explained

Systemctl is the control center for systemd, the system and service manager that's become standard in most Linux distributions. Think of it as the conductor orchestrating all the services and processes running on your machine.

Unlike older init systems like SysVinit, systemctl gives you granular control over services, making it easier to manage dependencies and parallelize operations. This means faster boot times and more reliable service management.

For example, on a traditional SysVinit system, services start sequentially, causing slow boot times. With systemctl, services can start in parallel when possible:

# Check how long your system took to boot
systemd-analyze

# You might see output like:
# Startup finished in 4.231s (kernel) + 15.141s (userspace) = 19.373s

This parallel processing is one of the many reasons most major distributions have switched to systemd.

💡
If you're working with systemctl, you'll likely need other essential Linux commands too. Check out our Linux commands cheat sheet for a quick reference.

Essential Systemctl Commands: Day-to-Day Service Management Tools

Let's kick things off with the commands you'll use daily:

Comprehensive Service Status Checking and Analysis

systemctl status service-name

This command shows you whether a service is running, stopped, or failed, along with recent log entries and process details. It's your first stop when troubleshooting.

Example output for the SSH service:

● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2025-03-15 09:42:17 UTC; 2 days ago
   Main PID: 1234 (sshd)
      Tasks: 1 (limit: 4915)
     Memory: 6.1M
        CPU: 237ms
     CGroup: /system.slice/ssh.service
             └─1234 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

You can see:

  • Current state (active/running)
  • When it started
  • Process ID
  • Resource usage
  • The control group hierarchy

For a more concise status, use:

systemctl is-active ssh
# Returns simply: active

Or check if it's enabled to start at boot:

systemctl is-enabled ssh
# Returns: enabled
💡
If a service keeps getting killed unexpectedly, the Linux OOM Killer might be the reason. Learn how it works here.

How to Start, Stop, and Restart Services the Right Way

systemctl start service-name    # Start a service
systemctl stop service-name     # Stop a service
systemctl restart service-name  # Stop and then start a service

These commands do exactly what you'd expect. Need to apply new config changes without stopping the service? That's where reload comes in:

systemctl reload service-name

Not all services support reload, though. If you're unsure, use reload-or-restart:

systemctl reload-or-restart nginx

This attempts a reload first, and if that's not supported, it performs a full restart.

Example:

Let's say you've modified your Nginx configuration and want to apply the changes:

# Edit the Nginx config
sudo nano /etc/nginx/nginx.conf

# Check if the syntax is valid
sudo nginx -t

# If valid, reload the service
sudo systemctl reload nginx

# If reload fails, you'll see an error and can try restart instead
sudo systemctl restart nginx

How to Enable or Disable Services at Boot

Want a service to start automatically at boot?

systemctl enable service-name

Changed your mind?

systemctl disable service-name
💡
You can combine commands to save time:
systemctl enable --now service-name  # Enable and start immediately
systemctl disable --now service-name # Disable and stop immediately

Example scenario:

You've just installed MariaDB but don't want it running all the time:

# Check its current status
systemctl status mariadb

# If it's running but you don't want it starting at boot
sudo systemctl disable mariadb

# If you also want to stop it right now
sudo systemctl disable --now mariadb

# Later, when you need to use it
sudo systemctl start mariadb
💡
Need to debug a failing service? Journalctl can help you check logs efficiently. Learn how to use it here.

How Systemd Defines and Manages Services

Services don't magically appear in systemd. They're defined in unit files that tell systemctl how to manage them.

Service File Locations and Search Order Explained

System service files live in these directories (in order of precedence):

  • /etc/systemd/system/ – Custom or modified service files
  • /run/systemd/system/ – Runtime service files
  • /usr/lib/systemd/system/ – Package-provided service files

When you run a systemctl command, it looks for the service file in this order. This means you can override package-provided services by placing a modified version in /etc/systemd/system/.

For example:

# Find where the SSH service file is located
systemctl show -p FragmentPath ssh.service
# FragmentPath=/lib/systemd/system/ssh.service

# Create an override
sudo mkdir -p /etc/systemd/system/ssh.service.d/
sudo nano /etc/systemd/system/ssh.service.d/override.conf

# Add your customizations
# [Service]
# ExecStartPre=/bin/sleep 5

# Apply the changes
sudo systemctl daemon-reload
sudo systemctl restart ssh

In-Depth Service File Anatomy: Sections and Configuration Options

Here's what a basic service file looks like:

[Unit]
Description=My Awesome Service
Documentation=https://example.com/docs
After=network.target
Requires=postgresql.service

[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/myservice --config /etc/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
TimeoutStartSec=30s
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Let's break this down section by section:

The [Unit] Section: Metadata and Dependencies

  • Description: Human-readable service description
  • Documentation: URLs or man pages with service documentation
  • After: Defines start order (but doesn't create a dependency)
  • Requires: Hard dependency - if this fails, the service won't start
  • Wants: Soft dependency - service will start even if this fails

Example: A web app that needs a database but can function (with limited features) without a cache:

[Unit]
Description=My Web Application
After=network.target
Requires=postgresql.service
Wants=redis.service

The [Service] Section: Runtime Behavior Configuration

  • Type: How systemd determines if service started successfully
    • simple: Default - main process is the service
    • forking: Service forks, parent exits
    • oneshot: Service exits after completing task
    • notify: Service signals when ready
    • dbus: Service registers on D-Bus
  • User/Group: Run service as this user/group instead of root
  • WorkingDirectory: Working directory for the service
  • ExecStart: Command to start the service
  • ExecReload: Command to reload configuration
  • Restart: When to restart the service automatically
    • Options: no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always
  • RestartSec: How long to wait before restarting
  • Environment: Environment variables for the service

The [Install] Section: Boot-Time Integration

  • WantedBy: Which target wants this service
    • Common targets:
      • multi-user.target: Normal multi-user system
      • graphical.target: Graphical interface
      • network-online.target: When network is fully up

Example: A service that should only run on systems with a GUI:

[Install]
WantedBy=graphical.target
💡
If you're managing services on Ubuntu, you might find ZFS useful for storage and performance. Learn more about it here.

How Can You Run Your Scripts as System Services

Now for the fun part – creating your own service! Let's work through an example of turning a Python web application into a systemd service.

Step-by-Step Service Creation: A Practical Python Web App Example

Let's say you've built a Flask web application and want it to run as a service.

Step 1: Prepare your application

First, make sure your application is properly set up:

# Create a dedicated user for the service
sudo useradd -r -s /bin/false webappuser

# Ensure proper permissions
sudo chown -R webappuser:webappuser /opt/mywebapp

Step 2: Create the service file

sudo nano /etc/systemd/system/mywebapp.service

Step 3: Define the service with the appropriate settings

[Unit]
Description=My Flask Web Application
After=network.target
Requires=postgresql.service

[Service]
Type=simple
User=webappuser
Group=webappuser
WorkingDirectory=/opt/mywebapp
ExecStart=/opt/mywebapp/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 app:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mywebapp
Environment=FLASK_ENV=production
Environment=DATABASE_URL=postgresql://user:password@localhost/mydb

[Install]
WantedBy=multi-user.target

Step 4: Reload systemd to recognize the new service

sudo systemctl daemon-reload

Step 5: Enable and start your service

sudo systemctl enable --now mywebapp.service

Step 6: Verify it's running correctly

sudo systemctl status mywebapp.service
curl http://localhost:8000

Why Set Resource Limits for System Services

You can further customize your service with environment variables and resource limits.

Environment variables:

[Service]
# Single variable
Environment=NODE_ENV=production

# Multiple variables
Environment="NODE_ENV=production" "PORT=3000" "DEBUG=false"

# Or from a file
EnvironmentFile=/etc/myapp/env

Resource limits:

[Service]
# Limit CPU usage
CPUQuota=50%

# Limit memory usage
MemoryLimit=512M

# Limit number of processes/threads
LimitNPROC=100

# Set disk IO priority
IOSchedulingClass=best-effort
IOSchedulingPriority=5

Example for a resource-intensive data processing service:

[Unit]
Description=Data Processing Service

[Service]
ExecStart=/opt/dataprocessor/bin/processor
CPUQuota=80%
MemoryLimit=2G
LimitNOFILE=65535
IOSchedulingClass=best-effort
IOSchedulingPriority=0
Nice=10

[Install]
WantedBy=multi-user.target
💡
If your services rely on Redis, knowing how to retrieve all keys can be handy. Learn how to do it efficiently here.

Advanced systemctl Operations

Here are some power-user moves:

Comprehensive Service Listing and Filtering Techniques

# View all active services
systemctl list-units --type=service

# See all services (including inactive)
systemctl list-units --type=service --all

# Filter services by state
systemctl list-units --type=service --state=running
systemctl list-units --type=service --state=failed

# Search for specific services
systemctl list-units --type=service | grep nginx

Example output:

UNIT                  LOAD   ACTIVE SUB     DESCRIPTION
nginx.service         loaded active running A high performance web server and a reverse proxy server
postgresql.service    loaded active running PostgreSQL RDBMS
ssh.service           loaded active running OpenBSD Secure Shell server

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state.
SUB    = The low-level unit activation state.

You can list other unit types too:

# List all targets (systemd's replacement for runlevels)
systemctl list-units --type=target

# List all sockets
systemctl list-units --type=socket

Service Masking: Preventing Accidental Service Activation

Sometimes disabling isn't enough. Masking a service makes it impossible to start:

systemctl mask bluetooth.service

This creates a symlink to /dev/null, effectively blocking the service. To unmask:

systemctl unmask bluetooth.service

Example scenario: You're setting up a server and want to ensure Bluetooth never runs:

# Check if Bluetooth is installed
systemctl status bluetooth

# If it is, mask it
sudo systemctl mask bluetooth.service

# Now try to start it
sudo systemctl start bluetooth.service
# You'll get an error: Failed to start bluetooth.service: Unit bluetooth.service is masked.

Exploring Service Dependencies: Understanding the Service Relationship Tree

Need to see what a service depends on?

systemctl list-dependencies service-name

Or what depends on it?

systemctl list-dependencies --reverse service-name

For example, exploring dependencies for the network target:

$ systemctl list-dependencies network.target
network.target
 ├─NetworkManager.service
 ├─auditd.service
 ├─network-online.target
 ├─NetworkManager-wait-online.service
 └─systemd-networkd-wait-online.service
 └─nss-lookup.target
   └─systemd-resolved.service

This shows NetworkManager.service and auditd.service depend on network.target.

Unit File Manipulation and Management

View the content of a unit file:

systemctl cat nginx.service

Edit a unit file directly:

systemctl edit --full nginx.service

Create a drop-in configuration (override parts without modifying the original):

systemctl edit nginx.service
# This opens an editor for creating an override file in /etc/systemd/system/nginx.service.d/override.conf

Example of creating an override to add an extra argument to a service:

sudo systemctl edit ssh.service
# Add this to the editor:
# [Service]
# ExecStart=
# ExecStart=/usr/sbin/sshd -D -o "MaxAuthTries=10"

The empty ExecStart= line is necessary to clear the original value before setting a new one.

💡
If you're managing Node.js services, logging is key to debugging and performance. Learn how to use Pino for efficient logging here.

Most Common systemctl Problems and Fixes

When things go wrong, systemctl has your back:

Advanced Service Log Analysis and Filtering Techniques

# View basic logs for a service
journalctl -u service-name

# Follow logs in real-time (like tail -f)
journalctl -u service-name -f

# Show logs since the last boot
journalctl -u service-name -b

# Show logs from the last hour
journalctl -u service-name --since "1 hour ago"

# Show only error and critical messages
journalctl -u service-name -p err..crit

# Show logs with JSON output (for scripting)
journalctl -u service-name -o json

Example troubleshooting MySQL not starting:

# First check status
systemctl status mysql.service
# If status shows failed, check the logs
journalctl -u mysql.service -b

# You might see errors like:
# InnoDB: Cannot open datafile './ibdata1'
# This indicates a permission issue or corrupt data file

# Check permissions
ls -la /var/lib/mysql/

# Fix permissions if needed
sudo chown -R mysql:mysql /var/lib/mysql/

# Try starting again
sudo systemctl start mysql

Identifying and Resolving Failed Services

# List all failed services
systemctl --failed

# Attempt to restart all failed services
systemctl reset-failed

Example output:

UNIT           LOAD   ACTIVE SUB    DESCRIPTION
apache2.service loaded failed failed The Apache HTTP Server

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state.
SUB    = The low-level unit activation state.

1 loaded units listed.

Common reasons for failure:

  • Configuration errors
  • Missing dependencies
  • Permission issues
  • Port conflicts
  • Resource constraints

Example fixing a configuration error:

# Service fails to start
systemctl status apache2

# Check logs for the error
journalctl -u apache2 -b

# Fix the configuration file
sudo nano /etc/apache2/apache2.conf

# Verify the config is valid
sudo apache2ctl configtest

# Restart the service
sudo systemctl restart apache2

Boot Time Analysis and Service Optimization

# See which services took longest to start
systemd-analyze blame

# Show critical chain of services that delayed boot
systemd-analyze critical-chain

# Generate an SVG graph of boot sequence
systemd-analyze plot > boot.svg

Example output from systemd-analyze blame:

9.175s docker.service
7.263s postgresql@13-main.service
6.919s snapd.service
5.644s NetworkManager.service
3.499s dev-sda1.device
2.427s udisks2.service
2.222s accounts-daemon.service

Based on this, you could optimize by:

  1. Disabling unnecessary services
  2. Using socket activation where appropriate
  3. Fixing slow-starting services

Example optimization for Docker:

# Edit the Docker service
sudo systemctl edit docker.service

# Add timeout to prevent long delays
[Service]
TimeoutStartSec=1min
💡
Managing logs is just as important as collecting them. Learn how to set up log retention to keep what matters and avoid clutter here.

How Do You Control the Entire System with systemctl?

Systemctl isn't just for services – it manages the entire system:

System Power State Management: Shutdown, Reboot, and Power Options

# Shut down immediately
systemctl poweroff

# Reboot the system
systemctl reboot

# Put system in sleep/suspend mode
systemctl suspend

# Hibernate the system
systemctl hibernate

# Schedule a shutdown in 30 minutes
systemctl poweroff --scheduled=+30min

# Cancel a scheduled shutdown
systemctl cancel

You can also combine power states:

# Hybrid sleep (both suspend and hibernate)
systemctl hybrid-sleep

For servers, you can schedule maintenance reboots:

# Schedule reboot at 2AM
systemctl reboot --scheduled="02:00"

Comprehensive System Status Analysis

# Get a system overview
systemctl status

# Check if system is fully booted and operational
systemctl is-system-running

Example output:

State: running
Jobs: 0 queued
Failed: 0 units
Since: Thu 2025-03-12 08:15:42 UTC; 5 days ago
CGroup: /
        ├─user.slice
        │ ├─user-1000.slice
        │ │ ├─user@1000.service
        │ │ │ ├─init.scope
        │ │ │ │ ├─1539 /lib/systemd/systemd --user
        │ │ │ │ └─1540 (sd-pam)

This gives you a quick overview of your system's health, running services, and potential issues.

Managing System Targets: Changing System States

In systemd, targets replace the concept of run levels:

# View current target
systemctl get-default

# Change default target
systemctl set-default graphical.target

# Switch to a different target now
systemctl isolate multi-user.target

Common targets:

  • poweroff.target: Shut down system
  • rescue.target: Single-user mode for recovery
  • multi-user.target: Multi-user, non-graphical
  • graphical.target: Multi-user, graphical
  • reboot.target: Reboot the system

For example, to temporarily drop to a console-only mode:

sudo systemctl isolate multi-user.target
# This kills the graphical environment
# To go back to graphical:
sudo systemctl isolate graphical.target

Systemctl vs. Traditional Service Managers

Feature Systemctl SysVinit Upstart
Parallel startup Yes - Sophisticated dependency resolution No - Sequential Partial - Event-based
Dependency management Advanced - Includes optional dependencies Basic - Static ordering Improved - Event-based
Service types Multiple (simple, forking, oneshot, etc.) Limited (mostly daemon-based) Several (task, service, etc.)
Resource control Cgroup integration for memory, CPU limits No built-in resource tracking Limited
Socket activation Yes - Services start on first connection No No
Dynamic service creation Yes - Runtime units No - Static init scripts Limited
Service monitoring Built-in with automatic restart Requires external tools Basic monitoring
Consistency across distros High - Standardized unit files Varies - Distro-specific scripts Varies
Logging integration Journal integration Separate syslog Upstart-specific logging
On-demand services Yes - Socket and bus activation No Limited

Examples and Practical Differences

Starting a service that has dependencies:

sysVinit:

# Start database
/etc/init.d/mysql start
# Check if it started
ps aux | grep mysql
# If it didn't, check logs manually
cat /var/log/mysql/error.log
# Start web server that depends on database
/etc/init.d/apache2 start

systemctl:

# Start web server and all dependencies automatically
systemctl start apache2
# Everything gets started in the right order
# Check status with logs included
systemctl status apache2
💡
Understanding log levels helps in filtering important information and troubleshooting efficiently. Learn more about them here.

Handling service crashes:

SysVinit:

# Need a separate monitoring tool like monit
# Or write custom watchdog scripts

systemctl:

# Built-in restart capability
[Service]
Restart=on-failure
RestartSec=5s

Time-Saving systemctl Shortcuts and Productivity Hacks

Working with long service names can be tedious. Here are some shortcuts to save you time:

Tab Completion and Command Shortcuts

Use pattern matching for bulk operations:

# Restart all apache-related services
systemctl restart apache*

# Show status of all network-related services
systemctl status network*

Reference the last active service with .:

systemctl status nginx
systemctl restart .
# Restarts nginx without typing the name again

Use tab completion for service names:

systemctl status ng<tab>
# Autocompletes to: systemctl status nginx

Command Chaining for Efficient Management

Combine multiple operations:

# Normal approach:
systemctl stop apache2
systemctl disable apache2

# Combined approach:
systemctl disable --now apache2

Other useful combinations:

# Restart and then show status
systemctl restart nginx && systemctl status nginx

# Try to reload, fall back to restart if that fails
systemctl reload nginx || systemctl restart nginx

Output Formatting and Filtering

Control the output format:

# Get specific property values
systemctl show -p ActiveState nginx
# Returns: ActiveState=active

# Get multiple properties
systemctl show -p Type -p ExecStart nginx

# JSON output for scripting
systemctl show --output=json nginx

Limit status output with the -n flag:

# Show only last 5 log lines instead of default 10
systemctl status nginx -n5

Filter service lists:

# Show only enabled services
systemctl list-unit-files --state=enabled

# Show only socket-activated services
systemctl list-sockets --all
💡
Keeping an eye on error logs in real time can help you catch issues before they escalate. Learn how to do it effectively here.

Security Best Practices

With great power comes great responsibility. Here are essential systemctl security tips:

User Management and Permission Controls

Restrict service permissions:

[Service]
# Remove capability to bind to privileged ports
CapabilityBoundingSet=~CAP_NET_BIND_SERVICE

# No new privileges (prevent setuid programs)
NoNewPrivileges=yes

Set appropriate service users and groups:

[Service]
User=www-data
Group=www-data

Use the --user flag for user services:

systemctl --user status syncthing

Always run systemctl with sudo for system services:

sudo systemctl restart nginx

Service Isolation and Sandboxing

Protect your system with service sandboxing:

[Service]
# Protect system directories
ProtectSystem=strict

# Protect home directories
ProtectHome=true

# Read-only access to specific directories
ReadOnlyDirectories=/var/www

# Restrict file system access
PrivateTmp=true
PrivateDevices=true

# Network namespace isolation
PrivateNetwork=true

# Isolate from other processes
ProtectKernelTunables=true
ProtectControlGroups=true
ProtectKernelModules=true

Example of a secure web server service:

[Unit]
Description=Secure Web Server

[Service]
ExecStart=/usr/bin/secure-web-server
User=www-data
Group=www-data
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true

[Install]
WantedBy=multi-user.target

Audit and Monitoring Service Activities

Regular service auditing:

# List all failed services
systemctl --failed

# Check services with high resource usage
systemd-cgtop

# Review service journal logs for suspicious activity
journalctl -u service-name -p warning..err --since "24 hours ago"

# Monitor service restarts
journalctl -b | grep "Service restarts"

For critical services, set up automatic alerts:

# Create a monitoring script
cat > /usr/local/bin/service-monitor.sh << 'EOF'
#!/bin/bash
SERVICE=$1
if ! systemctl is-active $SERVICE >/dev/null; then
  echo "ALERT: $SERVICE is not running!"
  # Add notification command here (e.g., mail, Slack webhook)
fi
EOF

chmod +x /usr/local/bin/service-monitor.sh

# Add to crontab for regular checks
crontab -e
# Add: */5 * * * * /usr/local/bin/service-monitor.sh nginx
💡
Keeping an eye on error logs in real time can help you catch issues before they escalate. Learn how to do it effectively here.

Cross-Distribution systemctl Guide

While systemctl works similarly across Linux distributions, there are some differences to be aware of:

Distribution-Specific Service Naming Conventions

  • Ubuntu/Debian:
    • Often uses .service suffix in package names
    • Example: apache2.service
  • RHEL/CentOS/Fedora:
    • Often uses service names without the .service suffix
    • Example: httpd (not apache2)

Example of cross-distro command adjustments:

# On Ubuntu/Debian
systemctl restart apache2

# On RHEL/CentOS/Fedora
systemctl restart httpd

Package Manager Integration Differences

Each distribution integrates systemd services with its package manager differently:

  • Ubuntu/Debian (apt):
    • Services often start automatically after installation
  • RHEL/CentOS (dnf/yum):
    • Services typically need manual enabling
  • Arch Linux (pacman):
    • Services are installed but not enabled

Example:

sudo pacman -S nginxsudo systemctl enable --now nginx

Example:

sudo dnf install nginxsudo systemctl enable --now nginx

Example:

sudo apt install nginx# Service automatically starts and enables

Feature Support and Default Configuration Variations

Each distribution configures systemd slightly differently:

  • Ubuntu/Debian:
    • More conservative defaults
    • More likely to have apparmor integration
  • RHEL/CentOS/Fedora:
    • SELinux integration
    • More enterprise-focused security policies
  • Arch Linux:
    • Bleeding-edge systemd versions
    • Minimal default configurations

Example: Different firewall service names:

# Ubuntu/Debian
systemctl status ufw

# RHEL/CentOS/Fedora
systemctl status firewalld
💡
If you're troubleshooting SSH access issues, checking sshd logs is a great place to start. Learn how to read and use them here.

Advanced Systemctl Techniques

Once you've mastered the basics, explore these advanced topics:

Template Service Files for Multiple Service Instances

Create one template for multiple similar services:

# /etc/systemd/system/website@.service
[Unit]
Description=Website for %i
After=network.target

[Service]
User=www-data
WorkingDirectory=/var/www/%i
ExecStart=/usr/bin/python3 -m http.server 80
Restart=on-failure

[Install]
WantedBy=multi-user.target

Now you can use it for multiple websites:

# Enable for 'blog' and 'shop' sites
systemctl enable --now website@blog.service
systemctl enable --now website@shop.service

The %i gets replaced with whatever comes after the @ in the service name.

Socket Activation for On-Demand Service Loading

Socket activation starts services only when needed:

Create a socket file:

# /etc/systemd/system/echo.socket
[Unit]
Description=Echo Service Socket

[Socket]
ListenStream=2000
Accept=yes

[Install]
WantedBy=sockets.target

Create the corresponding service:

# /etc/systemd/system/echo@.service
[Unit]
Description=Echo Service on %i

[Service]
ExecStart=/usr/bin/cat
StandardInput=socket
StandardOutput=socket

Enable the socket:

systemctl enable --now echo.socket

Now the service only starts when someone connects to port 2000.

💡
Logs are essential for debugging and monitoring system health. Learn how log files work and how to manage them effectively here.

Custom Service Monitoring with Systemd Timers

Systemd timers can replace cron jobs and monitor services:

# /etc/systemd/system/service-check.timer
[Unit]
Description=Check Critical Services Every 5 Minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Unit=service-check.service

[Install]
WantedBy=timers.target
# /etc/systemd/system/service-check.service
[Unit]
Description=Check Critical Services

[Service]
Type=oneshot
ExecStart=/usr/local/bin/check-services.sh

Example check-services.sh script:

#!/bin/bash
SERVICES="nginx postgresql docker"
for SERVICE in $SERVICES; do
  if ! systemctl is-active --quiet $SERVICE; then
    systemctl restart $SERVICE
    echo "Restarted $SERVICE at $(date)" >> /var/log/service-restarts.log
  fi
done

Enable the timer:

chmod +x /usr/local/bin/check-services.sh
systemctl enable --now service-check.timer

Practical Systemctl Application

Let's examine how systemctl solves common challenges in real-world scenarios:

Web Server Management: Nginx Configuration with High Availability

Managing a high-traffic web server requires careful service configuration:

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
# Increase open file limit for high traffic
LimitNOFILE=65536

# Ensure service restarts if it crashes
Restart=always
RestartSec=5s

# Give nginx time to finish connections before shutdown
TimeoutStopSec=30s

# Allow binding to low ports without root privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE

# Apply security hardening
ProtectSystem=full
PrivateTmp=true

Implementation steps:

# Create the override
sudo systemctl edit nginx.service
# Add the configuration above

# Apply the changes
sudo systemctl daemon-reload
sudo systemctl restart nginx

# Verify the new limits
sudo systemctl show nginx -p LimitNOFILE

This configuration ensures that:

  • The service can handle many concurrent connections
  • It automatically recovers from crashes
  • It shuts down gracefully
  • It runs with minimal privileges for security

Database Service Optimization: Performance Tuning MySQL/MariaDB

Database services need specific optimizations:

# /etc/systemd/system/mariadb.service.d/limits.conf
[Service]
# Adjust OOM score to prevent the kernel from killing the DB
OOMScoreAdjust=-900

# Set IO scheduling class to real-time for better disk performance
IOSchedulingClass=realtime
IOSchedulingPriority=0

# Memory limits
MemoryLow=2G
MemoryHigh=6G

# Allow large memory locking for buffer pool
LimitMEMLOCK=infinity

Example implementation and testing:

# Create the configuration
sudo mkdir -p /etc/systemd/system/mariadb.service.d/
sudo nano /etc/systemd/system/mariadb.service.d/limits.conf
# Add the configuration above

# Apply and restart
sudo systemctl daemon-reload
sudo systemctl restart mariadb

# Verify settings
sudo systemctl show mariadb | grep OOMScoreAdjust
sudo systemctl show mariadb | grep IOScheduling

Containerization Integration: Managing Docker with Systemd

Docker itself is managed by systemd, and they can work together effectively:

# /etc/systemd/system/docker.service.d/override.conf
[Unit]
# Wait for additional storage
After=data-storage.mount

[Service]
# Use specific storage driver
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --storage-driver=overlay2 --data-root=/data/docker

# Don't restart too quickly if something's wrong
RestartSec=10s

# Handle more simultaneous connections
LimitNOFILE=1048576

Example integrating a Docker container as a systemd service:

# /etc/systemd/system/my-container.service
[Unit]
Description=My Docker Container
After=docker.service
Requires=docker.service

[Service]
Type=simple
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker stop my-container
ExecStartPre=-/usr/bin/docker rm my-container
ExecStart=/usr/bin/docker run --rm --name my-container -p 8080:80 my-image:latest
ExecStop=/usr/bin/docker stop my-container

[Install]
WantedBy=multi-user.target

This allows you to manage Docker containers with systemctl:

sudo systemctl enable --now my-container
sudo systemctl status my-container
💡
Understanding incident severity levels helps teams respond faster and prioritize issues better. Learn more about them here.

Application Server Deployments: Node.js App with Environment Management

Deploy Node.js applications professionally:

# /etc/systemd/system/nodejs-app.service
[Unit]
Description=Node.js Application
After=network.target mongodb.service
Wants=mongodb.service

[Service]
Type=simple
User=nodejs
WorkingDirectory=/opt/my-nodejs-app
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10

# Environment configuration
EnvironmentFile=/opt/my-nodejs-app/.env

# Resource limits
CPUQuota=70%
MemoryLimit=1G

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ReadOnlyDirectories=/opt/my-nodejs-app
ReadWriteDirectories=/opt/my-nodejs-app/logs /opt/my-nodejs-app/uploads

[Install]
WantedBy=multi-user.target

Application deployment workflow:

# Deploy new version
cd /opt/my-nodejs-app
git pull origin main
npm install --production

# Restart service to apply changes
sudo systemctl restart nodejs-app

# Monitor for errors after deployment
journalctl -u nodejs-app -f

Scheduled Jobs and Cron Replacement: Systemd Timers in Action

Replace traditional cron jobs with more powerful systemd timers:

# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Database Backup

[Timer]
# Run at 2:30 AM every day
OnCalendar=*-*-* 02:30:00
# Add randomized delay to prevent server load spikes
RandomizedDelaySec=30min
# Keep the timer persistent if the time was missed (e.g., server was off)
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Database Backup Service
After=postgresql.service

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-script.sh
# Email on failure
OnFailure=status-email@%n.service

Create the status email service:

# /etc/systemd/system/status-email@.service
[Unit]
Description=Send status email about %i

[Service]
Type=oneshot
ExecStart=/usr/local/bin/send-status-email.sh %i

Example backup script:

#!/bin/bash
DATE=$(date +%Y-%m-%d)
BACKUP_DIR="/var/backups/postgresql"
mkdir -p $BACKUP_DIR

# Perform the backup
pg_dump -U postgres mydb > $BACKUP_DIR/mydb-$DATE.sql

# Clean up old backups (keep last 14 days)
find $BACKUP_DIR -name "mydb-*.sql" -mtime +14 -delete

Enable and monitor:

sudo systemctl enable backup.timer
sudo systemctl start backup.timer
systemctl list-timers --all

This approach offers several advantages over traditional cron:

  • Built-in logging through the journal
  • Email notifications on failure
  • Ability to set dependencies on other services
  • Random delays to prevent resource contention
  • Persistent timers that won't miss executions if the system was off

You can check the status of all your timer-based jobs with:

systemctl list-timers

Example output:

NEXT                        LEFT          LAST                        PASSED       UNIT                         ACTIVATES
Mon 2025-03-17 21:15:00 UTC 13min left    Mon 2025-03-17 20:15:00 UTC 46min ago    certbot-renewal.timer        certbot-renewal.service
Tue 2025-03-18 00:00:00 UTC 2h 58min left Mon 2025-03-17 00:00:09 UTC 20h ago      logrotate.timer              logrotate.service
Tue 2025-03-18 02:30:00 UTC 5h 28min left Mon 2025-03-17 02:30:01 UTC 17h ago      backup.timer                 backup.service

Distributed Systems Management: Coordinating Services Across Multiple Servers

For larger deployments across multiple servers, systemctl can be used in conjunction with orchestration tools:

# Remote systemctl execution via SSH
ssh server1.example.com "sudo systemctl status nginx"

# Parallel service checks across multiple servers
for server in server1 server2 server3; do
  ssh $server "sudo systemctl is-active nginx" &
done
wait

For a more robust solution, create a service synchronization script:

#!/bin/bash
# sync-service.sh
SERVICE=$1
ACTION=$2
SERVERS="server1 server2 server3 server4"

for SERVER in $SERVERS; do
  echo "Performing $ACTION on $SERVICE at $SERVER..."
  ssh $SERVER "sudo systemctl $ACTION $SERVICE"
  if [ $? -ne 0 ]; then
    echo "Failed on $SERVER!"
    exit 1
  fi
done

echo "Successfully completed $ACTION on $SERVICE across all servers."

Use it to coordinate service restarts across your fleet:

./sync-service.sh nginx restart

This approach ensures services across your infrastructure are managed consistently.

Conclusion

Throughout this guide, we've explored systemctl from basic commands to advanced techniques that can transform how you manage services on Linux systems.

Let's recap the key takeaways:

  • systemctl provides a unified, powerful interface for managing services across modern Linux distributions
  • The systemd architecture offers significant advantages over traditional init systems, including parallel service startup, dependency management, and resource control
  • Creating custom services allows you to run your applications reliably with automatic recovery from failures
  • Security hardening through systemd's built-in isolation and sandboxing features helps protect your systems
  • Advanced troubleshooting techniques using journalctl and systemd's diagnostic tools make identifying and fixing issues straightforward

Think about your services in terms of dependencies, isolation, and lifecycle management, and you'll create more robust systems that are easier to maintain.

💡
What systemctl techniques have you found most valuable in your environment? Do you have custom service configurations that have saved the day? Join our Discord Community to share your experiences and chat with other developers!

Contents


Newsletter

Stay updated on the latest from Last9.

Authors
Prathamesh Sonpatki

Prathamesh Sonpatki

Prathamesh works as an evangelist at Last9, runs SRE stories - where SRE and DevOps folks share their stories, and maintains o11y.wiki - a glossary of all terms related to observability.

X