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.
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
2. Basic Service Examples
Simple Script Service
#!/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"
# /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
#!/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()
# /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
simpleforkingoneshotdbusnotifyidle4. 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
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
6. Security Best Practices
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
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.