Creating Custom Services: Complete Guide to systemd Service Development

Learn how to create, configure, and manage custom systemd services from scratch. This comprehensive guide covers everything from basic service creation to advanced patterns for production-ready services.

Custom Service Development Workflow 1. Design Define requirements Choose service type Plan dependencies 2. Create Write service script Create unit file Set permissions 3. Test Validate syntax Test start/stop Check logs 4. Deploy Enable service Set auto-start Monitor operation Testing & Validation Steps: Unit Test Integration System Test Production Design Phase Development Phase Testing Phase Deployment Phase
Complete workflow for developing and deploying custom systemd services

Why Create Custom Services?

Custom services allow you to automate tasks, manage applications, and extend system functionality. Understanding service creation is essential for system administrators and developers alike.

  • Automation: Run scripts and applications automatically
  • Dependency Management: Control startup/shutdown order
  • Resource Management: Set limits and control resource usage
  • Monitoring: Integrated logging and status monitoring
  • Security: Run services with appropriate permissions
  • Reliability: Automatic restarts and failure recovery

1. Step-by-Step Service Creation

1
Define Your Service
Determine what your service will do, what resources it needs, and when it should run.
2
Create Service Script
Write the actual executable script or program that your service will run.
3
Design Unit File
Create the systemd unit file that defines how your service should be managed.
4
Configure Dependencies
Define what other services or resources your service depends on.
5
Set Permissions
Configure user, group, and file permissions for security.
6
Test & Validate
Test your service thoroughly before deployment.

2. Basic Service Examples

Simple Script Service

Service Script
#!/bin/bash
# /usr/local/bin/my-script.sh
# A simple service script

LOGFILE="/var/log/my-service.log"
PIDFILE="/var/run/my-service.pid"

# Function to start the service
start_service() {
    echo "$(date): Starting my-service" >> "$LOGFILE"
    # Main service loop
    while true; do
        echo "$(date): Service is running" >> "$LOGFILE"
        sleep 60
    done
}

# Handle signals
trap 'echo "$(date): Service stopping" >> "$LOGFILE"; exit 0' TERM INT

# Create PID file
echo $$ > "$PIDFILE"

# Start the service
start_service

# Cleanup on exit
rm -f "$PIDFILE"
Unit File
# /etc/systemd/system/my-script.service
[Unit]
Description=My Custom Script Service
Documentation=https://example.com/docs
After=network.target
Wants=network.target

[Service]
Type=simple
User=nobody
Group=nogroup
ExecStart=/usr/local/bin/my-script.sh
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=my-script

# Security options
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

Python Application Service

Python Application
#!/usr/bin/env python3
# /opt/myapp/app.py
# Python service application

import time
import logging
import signal
import sys

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class MyService:
    def __init__(self):
        self.running = True
        signal.signal(signal.SIGTERM, self.handle_signal)
        signal.signal(signal.SIGINT, self.handle_signal)
    
    def handle_signal(self, signum, frame):
        logger.info(f"Received signal {signum}, shutting down")
        self.running = False
    
    def run(self):
        logger.info("Starting MyService")
        count = 0
        
        while self.running:
            count += 1
            logger.info(f"Service iteration {count}")
            
            # Main service logic here
            self.process_data()
            
            # Sleep for 30 seconds
            for _ in range(30):
                if not self.running:
                    break
                time.sleep(1)
        
        logger.info("MyService stopped")
    
    def process_data(self):
        """Example processing method"""
        # Add your business logic here
        pass

if __name__ == "__main__":
    service = MyService()
    service.run()
Python Service Unit
# /etc/systemd/system/myapp.service
[Unit]
Description=Python Application Service
Documentation=https://example.com/myapp/docs
After=network.target postgresql.service redis.service
Wants=postgresql.service redis.service
Requires=network.target

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment="PYTHONPATH=/opt/myapp"
Environment="APP_ENV=production"
EnvironmentFile=/etc/default/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
TimeoutStartSec=30

# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
LimitCORE=infinity

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/myapp
ReadOnlyPaths=/etc/myapp

[Install]
WantedBy=multi-user.target

3. Service Types Explained

Type Description When to Use Example simple Default type, service runs in foreground Most scripts, foreground applications Python/Node.js apps, monitoring scripts forking Service forks child process, parent exits Traditional daemons that fork Apache, Nginx, MySQL oneshot Runs once and exits Init scripts, setup tasks Database migrations, cleanup tasks dbus Service acquires D-Bus name D-Bus services Desktop services, IPC services notify Sends notification when ready Services that need to signal readiness Modern daemons with sd_notify() idle Delays execution until jobs finish Low priority services Background maintenance tasks

4. Advanced Service Patterns

Multi-Instance Services

# /etc/systemd/system/myapp@.service
[Unit]
Description=MyApp Instance %i
After=network.target
Wants=network.target

[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/myapp
Environment="INSTANCE=%i"
Environment="PORT=808%i"
ExecStart=/usr/bin/python3 /opt/myapp/main.py --instance %i --port 808%i
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

# Start multiple instances
systemctl start myapp@1.service
systemctl start myapp@2.service
systemctl start myapp@3.service

# Enable all instances
systemctl enable myapp@{1..3}.service

Socket-Activated Services

# /etc/systemd/system/myapp.socket
[Unit]
Description=MyApp Socket
PartOf=myapp.service

[Socket]
ListenStream=0.0.0.0:8080
Accept=true
SocketUser=myapp
SocketGroup=myapp
SocketMode=0660

[Install]
WantedBy=sockets.target

# /etc/systemd/system/myapp@.service
[Unit]
Description=MyApp Instance %i
Requires=myapp.socket
After=myapp.socket

[Service]
Type=simple
User=myapp
Group=myapp
StandardInput=socket
ExecStart=/usr/bin/myapp --instance %i
PrivateTmp=true

Timer-Based Services

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

[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal

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

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h
AccuracySec=1min
Unit=backup.service

[Install]
WantedBy=timers.target

5. Service Testing & Validation

# Test service without starting
systemd-analyze verify myapp.service # Validate syntax
systemd-analyze cat-config myapp.service # View complete config
# Dry-run service start
systemctl dry-run start myapp.service # Show what would happen
systemd-id128 new # Generate machine ID for testing
# Test service in isolated environment
systemd-run --unit=test-service --service-type=simple /path/to/script
systemd-nspawn -D /path/to/chroot systemctl start myapp.service
# Check service dependencies
systemctl list-dependencies myapp.service
systemctl show myapp.service -p Requires,Wants,After
# Test failure scenarios
systemctl kill myapp.service --signal=SIGTERM # Test graceful shutdown
systemctl kill myapp.service --signal=SIGKILL # Test force kill
systemctl reset-failed myapp.service # Reset failure count

Testing Checklist

  • Validate unit file syntax with systemd-analyze verify
  • Test service start: systemctl start --no-block service
  • Check service status: systemctl status -l service
  • Monitor logs: journalctl -u service -f
  • Test service stop: systemctl stop service
  • Verify restart behavior: systemctl restart service
  • Check dependencies: systemctl list-dependencies service
  • Test failure recovery: Kill process and verify restart
  • Verify resource limits: Check with systemctl show service
  • Test with different users: Run as different system users
Service Testing Pipeline Syntax Validation systemd-analyze verify Unit Testing systemctl start/stop Integration Test With dependencies Load Testing High traffic simulation Production Ready Monitoring enabled Dec 07 14:30:01 server myapp[1234]: Service started successfully Dec 07 14:30:05 server myapp[1234]: Connected to database Dec 07 14:30:10 server myapp[1234]: Listening on port 8080 Dec 07 14:31:00 server myapp[1234]: Processed 100 requests journalctl -u myapp.service -f
Service testing pipeline and log monitoring example

6. Security Best Practices

Security Considerations:
1. Never run as root: Always specify User and Group directives
2. Use PrivateTmp: Isolate service temporary files
3. Limit capabilities: Use CapabilityBoundingSet to restrict privileges
4. Protect system directories: Use ProtectSystem and ReadOnlyPaths
5. Restrict network access: Use PrivateNetwork for services that don't need network
6. Set resource limits: Prevent resource exhaustion attacks
7. Use NoNewPrivileges: Prevent privilege escalation
8. Sandbox when possible: Use DynamicUser for disposable users
9. Secure socket activation: Set appropriate SocketUser/SocketGroup
10. Regular audits: Review service permissions and configurations

Secure Service Example

# /etc/systemd/system/secure-app.service
[Unit]
Description=Secured Application Service
After=network.target

[Service]
Type=simple

# Run as non-root user
User=secureapp
Group=secureapp

# Dynamic user (optional, creates temporary user)
# DynamicUser=true
# SupplementaryGroups=systemd-journal

# Command execution
ExecStart=/usr/local/bin/secure-app
WorkingDirectory=/var/lib/secureapp

# Resource limits
LimitNOFILE=65536
LimitNPROC=4096
LimitCORE=0
LimitFSIZE=infinity
LimitDATA=infinity
LimitSTACK=8388608
LimitRSS=infinity
LimitAS=infinity
LimitMEMLOCK=65536

# Security features
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
PrivateUsers=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
LockPersonality=true
MemoryDenyWriteExecute=true

# Network security
PrivateNetwork=false  # Set to true if no network needed
IPAddressDeny=any
IPAddressAllow=localhost
IPAddressAllow=192.168.1.0/24

# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=secureapp
LogLevelMax=info

[Install]
WantedBy=multi-user.target

7. Deployment & Maintenance

# Deploy service
cp myapp.service /etc/systemd/system/
chmod 644 /etc/systemd/system/myapp.service
systemctl daemon-reload
systemctl enable myapp.service
systemctl start myapp.service
# Monitor service
systemctl status myapp.service # Current status
journalctl -u myapp.service -f # Follow logs
journalctl -u myapp.service --since "1 hour ago"
systemctl show myapp.service # Show all properties
# Maintenance commands
systemctl reload myapp.service # Reload configuration
systemctl restart myapp.service # Restart service
systemctl stop myapp.service # Stop service
systemctl disable myapp.service # Disable auto-start
systemctl mask myapp.service # Prevent starting
systemctl unmask myapp.service # Remove mask
# Troubleshooting
systemctl list-unit-files | grep myapp # Check if enabled
systemctl list-units --type=service --state=failed
systemd-analyze blame # Find slow services
systemd-analyze critical-chain myapp.service

Maintenance Checklist

  • Regular log review: journalctl -u service --since "yesterday"
  • Monitor resource usage: systemctl show service -p MemoryCurrent,CPUUsageNSec
  • Check service health: systemctl is-active service
  • Update service configuration when application updates
  • Test restart after system updates
  • Review security settings periodically
  • Backup unit files: cp -r /etc/systemd/system /backup/
  • Document service dependencies and requirements
  • Set up monitoring alerts for service failures
  • Plan for graceful shutdown during maintenance

Master Custom Service Development

Creating custom systemd services is a fundamental skill for Linux system administration and development. By following best practices for design, implementation, testing, and security, you can build robust, maintainable services that integrate seamlessly with the system.

Remember: Start simple and add complexity gradually. Test thoroughly at each stage. Use the security features systemd provides. Document your services well. And always validate your unit files before deployment.

Next Steps: Begin by creating a simple service for a script you use regularly. Experiment with different service types. Implement socket activation for a network service. Create a timer-based service for periodic tasks. As you gain experience, you'll be able to design sophisticated service architectures that are reliable, secure, and maintainable.