funwithlinux guide

Step-by-Step Tutorial: Creating Systemd Services

In the world of Linux, service management is a critical task for ensuring applications run reliably, start automatically on boot, and recover from failures. **Systemd** has emerged as the de facto init system for most modern Linux distributions (e.g., Ubuntu 16.04+, Fedora, Debian 9+, CentOS 7+), replacing older systems like SysVinit and Upstart. It provides a robust framework for managing services, sockets, timers, and more through "unit files"—plaintext configuration files that define how a service should behave. Whether you’re a developer deploying a custom application, a system administrator automating tasks, or a hobbyist running a home server, understanding how to create and manage systemd services is an essential skill. In this tutorial, we’ll walk through the process of creating a systemd service from scratch, covering everything from writing the service file to debugging common issues.

Table of Contents

  1. Prerequisites
  2. Understanding Systemd Service Files
  3. Step 1: Create a Sample Application
  4. Step 2: Write the Systemd Service File
  5. Step 3: Configure Dependencies and Execution Context
  6. Step 4: Install and Enable the Service
  7. Step 5: Manage and Monitor the Service
  8. Step 6: Debugging Common Issues
  9. Advanced Tips
  10. Conclusion
  11. References

Prerequisites

Before diving in, ensure you have the following:

  • A Linux system running systemd (check with systemctl --version).
  • Basic familiarity with the Linux command line (e.g., nano, sudo, chmod).
  • sudo privileges (to install and manage systemd services).
  • A text editor (we’ll use nano for simplicity, but vim or gedit works too).

Understanding Systemd Service Files

Systemd services are defined by unit files with a .service extension. These files dictate how the service starts, stops, restarts, and interacts with the system. They are stored in standard directories:

  • /etc/systemd/system/: For user-defined or custom services (preferred for modifications).
  • /lib/systemd/system/ or /usr/lib/systemd/system/: For distribution-provided services (avoid editing these directly).

A systemd service file has three core sections:

1. [Unit]

Defines metadata and dependencies (e.g., when the service should start relative to other services).

2. [Service]

Specifies how the service runs: executable path, user/group, restart policy, and logging.

3. [Install]

Configures how the service is enabled (i.e., which target it belongs to for boot-time activation).

Step 1: Create a Sample Application

To demonstrate systemd service creation, we’ll use a simple Python script that logs timestamps to a file. This helps verify the service is running and producing output.

Create the Script

Save the following as /opt/sample-app/sample-app.py (create the directory first with sudo mkdir -p /opt/sample-app):

#!/usr/bin/env python3
import time
import logging

# Configure logging to write to a file
logging.basicConfig(
    filename='/var/log/sample-app/sample-app.log',
    level=logging.INFO,
    format='%(asctime)s - %(message)s'
)

# Create log directory if it doesn't exist
import os
log_dir = '/var/log/sample-app'
if not os.path.exists(log_dir):
    os.makedirs(log_dir, exist_ok=True)

# Simulate a long-running process
try:
    while True:
        logging.info("Sample application is running...")
        time.sleep(5)  # Log every 5 seconds
except KeyboardInterrupt:
    logging.info("Application stopped manually.")

Make the Script Executable

Set executable permissions and ensure the log directory is writable:

sudo chmod +x /opt/sample-app/sample-app.py
sudo mkdir -p /var/log/sample-app
sudo chown -R $USER:$USER /var/log/sample-app  # Temporarily set user ownership (we’ll adjust later)

Test the Script

Run it manually to ensure it works:

/opt/sample-app/sample-app.py

Check /var/log/sample-app/sample-app.log after a few seconds—you should see timestamps. Press Ctrl+C to stop it.

Step 2: Write the Systemd Service File

Now, we’ll create a systemd service file to manage this script.

Create the Service File

Create /etc/systemd/system/sample-app.service with sudo nano /etc/systemd/system/sample-app.service and add the following:

[Unit]
Description=Sample Application - A simple logging service
Documentation=https://example.com/sample-app
After=multi-user.target  # Start after the system reaches multi-user mode

[Service]
Type=simple  # Service runs in foreground (no forking)
ExecStart=/usr/bin/python3 /opt/sample-app/sample-app.py  # Path to executable
User=www-data  # Run as non-root user (least privilege)
Group=www-data  # Group for the user
Restart=always  # Restart on crash, exit, or reboot
WorkingDirectory=/tmp  # Working directory for the service
StandardOutput=append:/var/log/sample-app/stdout.log  # Redirect stdout
StandardError=append:/var/log/sample-app/stderr.log  # Redirect stderr
Environment="PYTHONUNBUFFERED=1"  # Disable Python output buffering
UMask=0002  # File permissions: rw-rw-r-- for new files

[Install]
WantedBy=multi-user.target  # Start on boot (multi-user runlevel)

Key Directives Explained

DirectivePurpose
DescriptionHuman-readable name for the service.
AfterEnsures the service starts after multi-user.target (avoids race conditions).
Type=simpleThe service runs in the foreground (systemd waits for it to start).
ExecStartFull path to the executable (use absolute paths to avoid errors).
User/GroupRuns the service as www-data (non-root) for security.
Restart=alwaysRestarts the service if it exits (e.g., crashes, manual stop with systemctl stop除外).
StandardOutput/ErrorRedirects stdout/stderr to log files (optional but useful for debugging).
EnvironmentSets environment variables (e.g., PYTHONUNBUFFERED=1 ensures logs appear immediately).
WantedBy=multi-user.targetEnsures the service is enabled for the multi-user runlevel (starts on boot).

Step 3: Configure Dependencies and Execution Context

Systemd services often depend on other services (e.g., a web app needing network.target for internet access). Let’s refine our service with best practices for security and reliability.

Dependencies: After and Requires

  • After=X.service: Starts the service after X.service (but doesn’t require X to run).
  • Requires=X.service: Starts X.service if it’s not running, and stops the current service if X fails.

For example, if our app needed a database, we’d add:

After=mysql.service
Requires=mysql.service

Security: Run as Non-Root

Always run services as a non-root user to limit damage from vulnerabilities. We used User=www-data, but you can create a dedicated user:

sudo useradd -r -s /sbin/nologin sample-app  # Create a system user with no login shell

Update the service file:

User=sample-app
Group=sample-app

Log File Permissions

Ensure the sample-app user can write to the log directory:

sudo chown -R sample-app:sample-app /var/log/sample-app

Step 4: Install and Enable the Service

With the service file written, we need to tell systemd about it and configure it to start on boot.

Reload Systemd

Systemd caches unit files, so reload the daemon to detect the new service:

sudo systemctl daemon-reload

Start the Service

Test the service manually before enabling it on boot:

sudo systemctl start sample-app.service

Verify the Service Status

Check if the service is running:

sudo systemctl status sample-app.service

You should see output like this:

● sample-app.service - Sample Application - A simple logging service
     Loaded: loaded (/etc/systemd/system/sample-app.service; disabled; vendor preset: enabled)
     Active: active (running) since Wed 2024-05-20 12:34:56 UTC; 10s ago
       Docs: https://example.com/sample-app
   Main PID: 12345 (python3)
      Tasks: 1 (limit: 4915)
     Memory: 10.0M
     CGroup: /system.slice/sample-app.service
             └─12345 /usr/bin/python3 /opt/sample-app/sample-app.py

Enable on Boot

To start the service automatically after reboots:

sudo systemctl enable sample-app.service

Output:

Created symlink /etc/systemd/system/multi-user.target.wants/sample-app.service → /etc/systemd/system/sample-app.service.

Step 5: Manage and Monitor the Service

Systemd provides powerful commands to control and monitor services.

Common Service Commands

CommandPurpose
sudo systemctl start sample-appStart the service.
sudo systemctl stop sample-appStop the service.
sudo systemctl restart sample-appRestart the service.
sudo systemctl reload sample-appReload configuration (if supported).
sudo systemctl enable sample-appEnable on boot.
sudo systemctl disable sample-appDisable on boot.
sudo systemctl is-active sample-appCheck if the service is running.
sudo systemctl is-enabled sample-appCheck if the service is enabled on boot.

View Logs

Systemd logs to the journal, a centralized logging system. Use journalctl to view service logs:

# View all logs for the service
sudo journalctl -u sample-app.service

# Follow live logs (like tail -f)
sudo journalctl -u sample-app.service -f

# View logs from the last hour
sudo journalctl -u sample-app.service --since "1 hour ago"

Step 6: Debugging Common Issues

Services can fail for many reasons—here’s how to diagnose and fix them.

1. Service Fails to Start

Check the status for errors:

sudo systemctl status sample-app.service

Common causes:

  • Incorrect ExecStart path: Verify the path to your script/executable with which python3 or ls /opt/sample-app/sample-app.py.
  • Permission denied: Ensure the service user has execute permissions on the script and write access to logs.
  • Missing dependencies: Use journalctl -u sample-app.service to check for missing libraries (e.g., Python modules).

2. Logs Not Appearing

  • Buffering: Python buffers output by default. Use Environment="PYTHONUNBUFFERED=1" in the service file.
  • Incorrect log path: Verify StandardOutput/StandardError paths and permissions.

3. Service Won’t Restart

Check the Restart directive—Restart=always restarts on most exits, but Restart=on-failure only restarts on non-zero exit codes. Use journalctl to see why the service exited.

4. Syntax Errors in Service File

Validate the service file with:

sudo systemd-analyze verify sample-app.service

Advanced Tips

1. Template Services

For managing multiple instances of a service (e.g., app-1, app-2), use template services. Name the file [email protected] and use %i in the service file to reference the instance name:

ExecStart=/usr/bin/python3 /opt/sample-app/sample-app-%i.py

Start with:

sudo systemctl start [email protected]

2. Timers (Cron Alternative)

Use systemd.timer units to run services on a schedule (e.g., daily backups). Create a .timer file alongside your .service file.

3. User-Specific Services

To run a service for a single user (not system-wide), place the .service file in ~/.config/systemd/user/ and use systemctl --user to manage it:

systemctl --user start sample-app.service
systemctl --user enable sample-app.service

Conclusion

Creating systemd services is a foundational skill for Linux administration. By following this tutorial, you’ve learned to:

  • Write a systemd service file with [Unit], [Service], and [Install] sections.
  • Configure security (non-root users, permissions) and reliability (restart policies, logging).
  • Manage and monitor services with systemctl and journalctl.
  • Debug common issues like failed starts and missing logs.

Systemd’s flexibility makes it suitable for everything from small scripts to complex applications. Always test services thoroughly and follow security best practices (e.g., least privilege, logging) to ensure stability.

References