Skip to main content

Chapter 8.2 - Custom Services

Learning Objectives​

By the end of this chapter, you will be able to:

  • Create a systemd unit file
  • Configure a custom service
  • Understand the sections of a unit file
  • Manage dependencies
  • Create services for scripts

Structure of a Unit File​

Location​

# System
/etc/systemd/system/

# User
~/.config/systemd/user/

Priority:

  • /etc/systemd/system/ (system) > /usr/lib/systemd/system/ (packages)

Basic Format​

[Unit]
Description=Service description
After=network.target

[Service]
Type=simple
ExecStart=/path/to/command
Restart=always

[Install]
WantedBy=multi-user.target

Main Sections​

[Unit]​

[Unit]
Description=My Custom Service
Documentation=man:my-service(1)
After=network.target sshd.service
Before=some-service.service
Requires=network.target
Wants=sshd.service
Conflicts=old-service.service

Directives:

  • Description: Service description
  • After: Starts after these units
  • Before: Starts before these units
  • Requires: Strong dependency (fails if dependency fails)
  • Wants: Weak dependency (continues if dependency fails)
  • Conflicts: Cannot run simultaneously

[Service]​

[Service]
Type=simple
ExecStart=/usr/local/bin/my-script.sh
ExecStartPre=/bin/echo "Starting"
ExecStartPost=/bin/echo "Started"
ExecStop=/bin/echo "Stopping"
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
User=myuser
Group=mygroup
WorkingDirectory=/var/lib/myservice
Environment="VAR1=value1"
EnvironmentFile=/etc/myservice/env
StandardOutput=journal
StandardError=journal

Types:

  • simple: Main command (default)
  • forking: Forks into background
  • oneshot: Single execution
  • notify: Notifies systemd when ready

Restart:

  • no: Does not restart
  • always: Always restart
  • on-failure: Restart on failure
  • on-success: Restart on success

[Install]​

[Install]
WantedBy=multi-user.target
Alias=myservice.service

Common targets:

  • multi-user.target: Multi-user mode
  • graphical.target: Graphical mode
  • default.target: Default target

Practical Examples​

Simple Service​

# /etc/systemd/system/my-service.service
[Unit]
Description=My Simple Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/my-script.sh
Restart=always
RestartSec=10
User=www-data
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable my-service
sudo systemctl start my-service

Service with Script​

#!/bin/bash
# /usr/local/bin/my-script.sh
# Service script

# Cleanup function
cleanup() {
echo "Stopping service"
# Cleanup...
exit 0
}

# Trap signals
trap cleanup SIGTERM SIGINT

# Main loop
while true; do
# Work...
echo "Service active: $(date)"
sleep 60
done

Unit file:

[Unit]
Description=Service with Script
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/my-script.sh
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Forking Service​

[Unit]
Description=Forking Service
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/daemon --daemonize
PIDFile=/var/run/daemon.pid
Restart=always

[Install]
WantedBy=multi-user.target

Oneshot Service​

[Unit]
Description=Oneshot Script
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/init-script.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Environment Variables​

In Unit File​

[Service]
Environment="VAR1=value1"
Environment="VAR2=value2"
EnvironmentFile=/etc/myservice/env

Environment File​

# /etc/myservice/env
VAR1=value1
VAR2=value2
VAR3="value with spaces"

Log Management​

systemd Journal​

# View service logs
sudo journalctl -u my-service

# Follow logs
sudo journalctl -u my-service -f

# Recent logs
sudo journalctl -u my-service -n 50

# Since date
sudo journalctl -u my-service --since "2024-01-01"

Redirection​

[Service]
# To journal (default)
StandardOutput=journal
StandardError=journal

# To file
StandardOutput=file:/var/log/myservice.log
StandardError=file:/var/log/myservice-error.log

# Ignore
StandardOutput=null
StandardError=null

Best Practices​

Structure​

[Unit]
# Clear description
# Appropriate dependencies

[Service]
# Correct type
# Non-root User/Group if possible
# Appropriate restart
# Logging configured

[Install]
# Appropriate target

Security​

[Service]
# Avoid using root if possible
User=serviceuser
Group=servicegroup

# Limit capabilities
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

# Chroot if possible
RootDirectory=/var/lib/myservice

Summary​

In this chapter, you learned:

Unit files: Structure [Unit], [Service], [Install]
Types: simple, forking, oneshot, notify
Dependencies: After, Before, Requires, Wants
Restart: always, on-failure, never
Environment: Environment, EnvironmentFile
Logs: journalctl, file redirection
Practice: Create services for scripts and applications


Next Steps​

Chapter 8.3: Advanced Cron and systemd Timers
Module 9: System Security


Chapter created on: December 2024