If you are running a modern Linux server, you are running systemd. It is the init system, the service manager, the logger, the timer scheduler, and much more. Yet many administrators only know the basics: systemctl start and systemctl stop. In this guide, we will go deep into systemd — from writing custom service unit files to replacing cron with timers, mastering journalctl for log analysis, and understanding boot targets. By the end, you will have the knowledge to manage any service on your server with confidence.
Understanding Unit Files
Everything in systemd is a unit. Services, timers, mount points, sockets — they are all defined by unit files. These are plain text INI-style configuration files that tell systemd what to run and how to run it.
Where Unit Files Live
| Path | Purpose | Priority |
|---|---|---|
/etc/systemd/system/ | Admin-created and override units | Highest |
/run/systemd/system/ | Runtime-generated units | Medium |
/lib/systemd/system/ | Package-installed units | Lowest |
When you create or modify a service, always work in /etc/systemd/system/. This ensures your changes survive package updates, which overwrite files in /lib/systemd/system/.
Anatomy of a Service Unit File
A service unit file has three main sections: [Unit], [Service], and [Install]. Let us walk through a complete, real-world example.
Let us break down every important directive.
The [Unit] Section
This section describes the unit and defines its relationships with other units.
| Directive | Purpose | Example |
|---|---|---|
Description | Human-readable name shown in status output | My Go Application Server |
After | Start this unit AFTER the listed units | network-online.target postgresql.service |
Before | Start this unit BEFORE the listed units | nginx.service |
Requires | Hard dependency — if the listed unit fails, this one stops too | postgresql.service |
Wants | Soft dependency — try to start, but do not fail if it cannot | network-online.target |
After=postgres controls ordering (start after postgres). Requires=postgres controls dependency (fail if postgres fails). You usually want both: After=postgresql.service AND Requires=postgresql.service.
The [Service] Section
This is where the real configuration happens. The Type directive is especially important.
Type=simple Most Common
The default. Systemd considers the service started as soon as the ExecStart process launches. Use this for applications that stay in the foreground.
Examples: Go binaries, Node.js apps, Python web servers
Type=forking
For daemons that fork a child process and then the parent exits. Systemd tracks the child via a PID file.
Examples: Apache httpd, traditional nginx, PHP-FPM
Type=oneshot
For commands that do one thing and exit. Systemd waits for the process to finish before considering the unit started.
Examples: Initialization scripts, migration runners, cleanup tasks
Type=notify
The service sends a notification to systemd when it is fully ready using sd_notify(). More precise startup tracking.
Examples: PostgreSQL, systemd-aware daemons
Other critical directives in the [Service] section include:
The [Install] Section
This section controls what happens when you run systemctl enable. The most common directive is WantedBy=multi-user.target, which makes the service start at boot in normal multi-user mode.
Essential systemctl Commands
Here is every systemctl command you will use regularly, grouped by purpose.
Service Lifecycle
Status and Diagnostics
Advanced Operations
/etc/systemd/system/, you MUST run systemctl daemon-reload before starting or restarting the service. Systemd caches unit files in memory, so changes are invisible until you reload.
Timer Units: The Modern cron
Systemd timers are a powerful alternative to cron. They offer better logging, dependency management, and can trigger on events (not just time).
Creating a Timer
A timer requires two files: a .timer unit and a matching .service unit.
systemctl enable --now backup.timerTimer vs. Cron Comparison
| Feature | cron | systemd Timer |
|---|---|---|
| Logging | Must redirect to file manually | Automatic via journalctl |
| Dependencies | None | After=, Requires= supported |
| Missed runs | Lost forever | Persistent=true catches up |
| Random delay | Must script manually | RandomizedDelaySec= built-in |
| Resource limits | None | CPUQuota=, MemoryMax= supported |
| Setup complexity | One line in crontab | Two unit files required |
| Listing jobs | crontab -l | systemctl list-timers |
OnCalendar Syntax
The systemd-analyze calendar command is incredibly useful for testing your schedule expressions before deploying them.
Listing and Managing Timers
Mastering journalctl
Systemd captures all service output (stdout and stderr) into the journal. journalctl is the tool to query it, and it is far more powerful than manually grepping through log files.
Basic Queries
Filtering by Priority
| Priority Level | Keyword | Description |
|---|---|---|
| 0 | emerg | System is unusable |
| 1 | alert | Immediate action required |
| 2 | crit | Critical conditions |
| 3 | err | Error conditions |
| 4 | warning | Warning conditions |
| 5 | notice | Normal but significant |
| 6 | info | Informational messages |
| 7 | debug | Debug-level messages |
Journal Maintenance
Boot Targets (Runlevels)
Systemd uses targets instead of the old SysVinit runlevels. A target is a grouping of units that defines a system state.
| Target | Old Runlevel | Description |
|---|---|---|
rescue.target | 1 | Single-user mode, minimal services |
multi-user.target | 3 | Full multi-user, no GUI — Server default |
graphical.target | 5 | Multi-user with GUI |
reboot.target | 6 | System reboot |
poweroff.target | 0 | System shutdown |
Overriding Unit Files Without Editing Them
Never edit unit files in /lib/systemd/system/ directly — package updates will overwrite your changes. Instead, use drop-in overrides.
The override file only needs the directives you want to change. Everything else is inherited from the base unit file.
Practical Example: Creating a Complete Service
Let us put it all together by creating a service for a Node.js application that requires a database, restarts on failure, and has resource limits.
/etc/systemd/system/nodeapp.servicejournalctl -u nodeapp -fDebugging Failed Services
When a service refuses to start, follow this systematic debugging workflow:
Security Hardening with Systemd
Systemd provides built-in security features that isolate services without Docker or containers.
You can audit a service's security posture with:
Systemd and Panelica
pn-service wrapper provides a simplified interface (pn-service restart nginx) while leveraging the full power of systemd under the hood. Each service has proper dependencies, resource limits, and restart policies configured, so you get enterprise-grade service management with single-command simplicity.
Summary
- Unit files define services in three sections: [Unit], [Service], and [Install]
- Always use
/etc/systemd/system/for custom units and overrides - Run
systemctl daemon-reloadafter every unit file change - Use
Type=simplefor most modern applications - Replace cron with systemd timers for better logging and reliability
- Master
journalctlfilters:-u,-p,--since,-f - Use security directives (ProtectSystem, NoNewPrivileges) to harden services
- Follow the debug flow: status → journal → cat → manual test → fix
Systemd is a deep topic, but the knowledge in this guide covers what you will use 95% of the time. As you manage more complex server setups, you will appreciate the consistency and power that systemd brings to service management, logging, and scheduling.