Create a Custom systemd Service
Detailed guide on creating robust custom systemd services, covering unit file structure, service types, dependencies, security, and common troubleshooting for Linux systems.
Managing background processes and applications effectively on Linux systems is critical for stability and reliability. systemd, the prevalent init system in modern Linux distributions (e.g., RHEL 7+, Ubuntu 16.04+, Debian 8+), provides a powerful and standardized framework for this. This guide delves into creating a custom systemd service from scratch, covering the essential components, configuration options, and best practices for robust application management.
A systemd service unit file defines how a daemon or application is started, stopped, and managed by the operating system. These files typically reside in /etc/systemd/system/ for custom services, or /usr/lib/systemd/system/ for packages installed by the system's package manager.
Understanding the Unit File Structure
A systemd unit file is an INI-style plain text file divided into sections. The most common sections for service units are [Unit], [Service], and [Install]. Let's break down a typical example, expanding on the provided stub.
# /etc/systemd/system/myapp.service
[Unit]
Description=My Custom Application Service
Documentation=https://docs.example.com/myapp-service
After=network.target syslog.target
Wants=postgresql.service # Example: myapp wants postgresql but doesn't strictly require it to start
Requires=another_critical_service.service # Example: myapp will fail if this critical service doesn't start
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.yml
ExecReload=/bin/kill -HUP $MAINPID # Send HUP signal for graceful reload
ExecStop=/bin/kill -TERM $MAINPID # Send TERM signal for graceful shutdown
Restart=on-failure
RestartSec=5s
User=myappuser
Group=myappgroup
WorkingDirectory=/var/lib/myapp
Environment="MYAPP_ENV=production" "PORT=8080"
StandardOutput=journal
StandardError=journal
LimitNOFILE=65536 # Increase file descriptor limit
PrivateTmp=true # Isolate /tmp and /var/tmp
ProtectSystem=full # Read-only /usr, /boot, /etc
ProtectHome=true # Isolate /home, /root, /run/user
[Install]
WantedBy=multi-user.target
The [Unit] Section
This section contains generic options about the unit itself, independent of its type.
Description: A human-readable description of the service. Appears insystemctl statusoutput.Documentation: Optional URLs for documentation related to the service.After,Before: Define ordering dependencies.After=network.targetmeans this service starts only after the network is up. It implies an ordering but not a strong dependency.Wants,Requires: Define dependency types.Wantsis a weaker dependency; if the wanted unit fails, this service can still start.Requiresis a stronger dependency; if the required unit fails or is not active, this service will not be started. These also imply ordering.PartOf: Makes this unit part of another unit. If the other unit is stopped, this one is too.BindsTo: A stronger version ofRequires; if the bound unit stops, this one also stops.
The [Service] Section
This section defines the behavior specific to services.
Type: Specifies the process startup type. Common types include:simple(default): TheExecStartcommand is the main process.systemdconsiders the service active as soon asExecStartis invoked. Use for processes that daemonize themselves or stay in the foreground.forking:ExecStartforks a child process and the parent exits.systemdconsiders the service started once the parent exits. RequiresPIDFileto specify the PID of the main process if systemd can't detect it automatically.oneshot: A command that runs once and then exits.systemdwaits for the command to complete. Useful for setup tasks.notify: Similar tosimple, but the daemon sends a signal (READY=1) viasd_notify()when it's ready.idle:ExecStartis executed only when all active jobs are dispatched. Useful for services that should not interfere with active requests.
ExecStart: The absolute path to the command or script to execute when the service starts.ExecReload: Command to execute when the service is reloaded (systemctl reload myapp.service). Often sends a SIGHUP to the main process.ExecStop: Command to execute when the service is stopped (systemctl stop myapp.service). Usually sends a SIGTERM.ExecStartPre,ExecStartPost,ExecStopPost: Commands run before/afterExecStartor afterExecStop.Restart: Defines when the service should be restarted. Options includeno,on-success,on-failure,on-abnormal,on-watchdog,on-abort,always.on-failureis a good default for most applications.RestartSec: How long to wait before restarting the service (e.g.,5s).User,Group: The user and group under which the service process will run. Crucial for security.WorkingDirectory: The working directory for the executed process.Environment,EnvironmentFile: Set environment variables for the service.EnvironmentFilecan load variables from an external file.StandardOutput,StandardError: Where to direct stdout/stderr. Options includeinherit,null,tty,journal(default),syslog,kmsg,file:/path/to/log. Directing tojournalis generally recommended for easy log access viajournalctl.LimitNOFILE,LimitNPROC: Set resource limits (e.g., open file descriptors, number of processes).PrivateTmp: Provides a private/tmpand/var/tmpdirectory for the service, enhancing isolation.ProtectSystem,ProtectHome: Security directives that make parts of the filesystem read-only or inaccessible to the service, significantly reducing attack surface.ProtectSystem=fullmakes/usr,/boot,/etcread-only.ProtectHome=truemakes/home,/root,/run/userinaccessible.
The [Install] Section
This section defines how the service is enabled and disabled.
WantedBy,RequiredBy: Specifies the target (runlevel) under which the service should be enabled.multi-user.targetis the standard for most server-side services, equivalent to traditional runlevel 3 or 5. When you runsystemctl enable myapp.service, a symlink is created from/etc/systemd/system/multi-user.target.wants/myapp.serviceto the actual unit file.
Service Creation Workflow
- Create the Unit File: Place your
.servicefile in/etc/systemd/system/. Ensure it has appropriate permissions (e.g.,644). - Create Application User/Group (if needed): For security, run services under a dedicated, unprivileged user.
sudo useradd --system --no-create-home --shell /sbin/nologin myappuser sudo groupadd --system myappgroup sudo usermod -a -G myappgroup myappuser - Reload
systemdDaemon: After creating or modifying any unit file, informsystemdabout the changes.sudo systemctl daemon-reload - Enable the Service: This creates the necessary symlinks so the service starts automatically on boot.
sudo systemctl enable myapp.service - Start the Service: Immediately start the service.
sudo systemctl start myapp.serviceYou can combine enable and start:
sudo systemctl enable --now myapp.service - Check Service Status: Verify the service is running correctly and inspect recent logs.
sudo systemctl status myapp.service - View Logs: Access detailed logs for your service using
journalctl.sudo journalctl -u myapp.service -fThe
-fflag tails the logs for real-time monitoring.
Common Pitfalls and Troubleshooting
- Absolute Paths: Always use absolute paths for
ExecStart,ExecReload,ExecStop, and any binaries or configuration files referenced. The service's environment might not have the samePATHas your interactive shell. - Permissions: Ensure the
UserandGroupspecified in the unit file have read/write access to necessary directories and files (e.g., logs, configuration, data). Also, ensure the service unit file itself has correct permissions (e.g.,644). - Service Type Mismatch: Using
Type=simplefor a daemon that forks can lead tosystemdthinking the service has exited prematurely. If your application daemonizes, useType=forkingand potentiallyPIDFile. - Dependency Issues: If your service fails to start, check
After=andRequires=directives. A common mistake is starting a service before its required database or network interface is fully up. Usesystemctl list-dependencies myapp.serviceto inspect its dependency tree. - Environment Variables: Ensure all necessary environment variables are set either directly in the unit file via
Environment=or throughEnvironmentFile=. - Resource Limits: Services might fail if they hit system resource limits (e.g., too many open files). Use
LimitNOFILE,LimitNPROCto adjust these. Checkulimit -ain your shell to see current limits, but note thatsystemdsets its own. - Logging: Always configure
StandardOutput=journalandStandardError=journal. This centralizes logs and makes debugging easier withjournalctl. Avoid writing directly to files unless absolutely necessary, as it complicates log rotation and centralization. - Error Messages:
journalctl -u myapp.service --since "1 hour ago" -eprovides the last few log entries, very useful for quickly identifying startup errors. Pay close attention to error codes fromsystemctl status. An exit code of 0 usually means success, anything else indicates a problem.