Creating a Custom Systemd Service
Your application needs to start on boot, restart on crash, and log output. Shell scripts in /etc/rc.local give you none of that. Systemd solves all three with a single declarative file.
Why Write a Custom Unit File
Supervisord and init scripts are overkill for most cases. Systemd provides a unified interface for service management: socket-based activation, dependency tracking, resource limits, and built-in logging via journald. You get all of it without additional tooling.
Unit File Structure
A unit file is an ini-style text file placed in /etc/systemd/system/ for persistent configuration or /run/systemd/system/ for runtime-only changes. Naming convention is name.service.
Required Sections and Directives
| Section | Key | Purpose |
|---|---|---|
[Unit] | Description | Human-readable name |
[Unit] | After | Startup ordering relative to other units |
[Service] | Type | How the service demonizes |
[Service] | ExecStart | Command to execute on start |
[Install] | WantedBy | Target that enables this unit |
Type
simple— process stays in foreground, systemd monitors it directlyforking— process forks and parent exits (classic daemon pattern)oneshot— runs once and exits, useful for one-off tasksexec— like simple, but waits for ExecStartPre to finish first
For modern applications written in Go, Node.js, or similar, simple is almost always correct.
Example: Application Service
The --user flag creates a user-level service that lives with the user’s session instead of the system. Useful for personal tooling without root access.
Python: venv and gunicorn
Point ExecStart at the venv interpreter, not system python. That keeps service dependencies off the host packages:
For a WSGI app:
Keep secrets in EnvironmentFile (KEY=VALUE), mode 600, owner root:appuser. If the unit fails to start, journalctl -u myapp usually shows a traceback, a missing WorkingDirectory, or a missing env file.
Production-Ready Directives
Restart Behavior
Dependencies
Resource Limits
Logging
Reading logs:
Activation and Management
Syntax check without applying changes:
Common Mistakes
Missing ExecStart. Service fails immediately with Unit entered failed state.
Type not specified. Defaults to simple, but if your process self-daemonizes, you need forking.
Wrong path to script. Verify the file exists and is executable. Systemd does not validate paths at parse time — it simply fails to start.
Runtime edit without reload. Changed the file, forgot daemon-reload. Systemd continues using the old version.
WorkingDirectory does not exist. If you specify it, the directory must be present.
Restart=always without ExecStop. If the process exits cleanly, always restarts it anyway. This means systemctl stop may not behave as expected for long-running services.
Never edit package-provided unit files directly in /usr/lib/systemd/system/. Updates overwrite them. Use drop-in files in /etc/systemd/system/<name>.service.d/ instead.
Drop-in example: