Introduction
A long, long time ago, I started administering a Solaris server for a few months. From there I moved to Slackware, because that was what was available. And then to Red Hat. In those days, the service lifecycle was handled without complications using SysVinit. However, at some point along the way, someone had the idea of complicating things.
Enter systemd.
And, well, since Ubuntu, Debian and most derived distributions use this system, there is no choice but to learn a little about it.
Table of contents
Table of contents
- 1. Introduction: what is systemd and what does it manage
- 2. Core concepts: units, services and targets
- 3. Basic system and service inspection
- 4. Service installation
- 5. Manual creation of custom services
- 6. Starting, stopping, restarting and reloading services
- 7. Enabling, disabling, masking and unmasking services
- 8. Service configuration with unit files and drop-ins
- 9. Environment variables, users, permissions and working directories
- 10. Logs, diagnostics and troubleshooting
- 11. Uninstalling, removing and cleaning up services
- 11.1 Stop before uninstalling
- 11.2 Disable before uninstalling
- 11.3 Uninstall a package on Debian/Ubuntu
- 11.4 Uninstall a package on Fedora/RHEL
- 11.5 Uninstall a package on Arch Linux
- 11.6 Remove a manual service created in /etc/systemd/system
- 11.7 Remove data, logs and configuration from a custom application
- 11.8 Remove a service user and group
- 11.9 Clean up orphan or not-found units
- 11.10 Remove a manually masked service
- 12. User services: systemd —user
- 13. Timers: practical replacement for cron for periodic services
- 14. Security and service hardening
- 15. Operational best practices
- 15.1 Use clear names
- 15.2 Use dedicated users
- 15.3 Separate configuration, data and binaries
- 15.4 Validate before restarting
- 15.5 Use drop-ins for local changes
- 15.6 Document overrides
- 15.7 Review logs after any change
- 15.8 Avoid
rm -rfwithout verifying paths - 15.9 Use
daemon-reloadwhen appropriate - 15.10 Differentiate
reloadfromdaemon-reload
- 16. Important directories and files
- Reference sources
- Quick reference: cheat sheet of frequent commands
1. Introduction: what is systemd and what does it manage
systemd is the initialization system and service manager predominant in many modern Linux distributions. Its main process typically runs as PID 1 and is responsible for booting the system, managing services, mounting filesystems, starting sockets, scheduling tasks, handling sessions, collecting logs through journald and coordinating dependencies between system components.
In daily administration, the central command is systemctl. With it, systemd units are queried, started, stopped, enabled, disabled, restarted, reloaded, masked and managed.
A “service” in systemd typically corresponds to a unit with the .service extension, for example:
ssh.servicenginx.servicepostgresql.servicedocker.servicemyapp.service
However, systemd does not only manage services. It manages different types of units: services, sockets, timers, targets, mounts, automounts, paths, slices, scopes, devices and swaps.
Basic example to check the status of a service:
systemctl status ssh.serviceCommand explanation:
systemctl: main tool for communicating with the systemd manager.status: subcommand that shows the status of a unit.ssh.service: name of the unit to query. In many distributionssshcan also be used, because systemd infers the.serviceextension if none other is specified.
Alternative example:
systemctl status sshThis command usually resolves ssh to ssh.service, as long as there is no ambiguity with another unit of the same name and different extension.
2. Core concepts: units, services and targets
2.1 Unit
A unit is an object managed by systemd. Each unit is defined by a configuration file called a unit file. The unit name indicates its type through its extension.
Examples:
nginx.servicessh.socketapt-daily.timermulti-user.targethome.mount2.2 Service
A .service unit describes how to start, stop, reload and supervise a process or set of processes.
Example of a minimal service file:
[Unit]Description=Example serviceAfter=network.target
[Service]ExecStart=/usr/local/bin/my-serviceRestart=on-failure
[Install]WantedBy=multi-user.targetSection explanation:
[Unit]: general unit metadata and dependencies.[Service]: specific instructions for running the service.[Install]: rules used when the service is enabled withsystemctl enable.
2.3 Target
A target is a unit that groups other units. It is roughly comparable to the old SysVinit runlevels.
Common targets:
multi-user.target: multi-user mode without a full graphical interface. Very common on servers.graphical.target: multi-user mode with graphical environment.rescue.target: rescue mode.emergency.target: minimum emergency mode.default.target: alias to the system’s default target.
View the default target:
systemctl get-defaultExplanation:
get-default: shows which target will be used by default during boot.
Set the default target:
sudo systemctl set-default multi-user.targetExplanation:
sudo: runs the command with administrative privileges.systemctl: systemd client.set-default: changes the default target.multi-user.target: target to be set as the main boot destination.
Example for a server without a graphical desktop:
sudo systemctl set-default multi-user.targetExample for a workstation with graphical environment:
sudo systemctl set-default graphical.target3. Basic system and service inspection
Before installing, modifying or removing services, it is useful to know how to inspect the system state.
3.1 List active services
systemctl list-units --type=service --state=runningExplanation:
list-units: lists units loaded by systemd.--type=service: filters only service-type units.--state=running: shows only services whose active state isrunning.
Example:
systemctl list-units --type=service --state=runningUseful for checking which processes are currently being managed by systemd.
3.2 List all loaded services
systemctl list-units --type=service --allExplanation:
--all: includes inactive, failed or loaded but not necessarily active units.
Example:
systemctl list-units --type=service --all3.3 List installed unit files
systemctl list-unit-files --type=serviceExplanation:
list-unit-files: lists unit files available in systemd’s known paths.--type=service: limits output to services.
The output typically includes states such as:
enabled: the service is enabled to start automatically.disabled: the service exists, but does not start automatically.static: cannot be enabled directly; it is normally activated as a dependency of another unit.masked: is blocked via a symlink to/dev/null.alias: is an alias for another unit.generated: was automatically generated.
Example:
systemctl list-unit-files --type=service | grep nginx3.4 View the detailed status of a service
systemctl status nginx.serviceExplanation:
status: shows whether the service is active, whether it failed, its main PID, its latest logs and part of its load configuration.nginx.service: queried service.
Example:
systemctl status nginx.serviceTypical abbreviated output:
● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: active (running) since ... Main PID: 1234 (nginx) Tasks: 5 Memory: 12.3M3.5 View internal properties of a unit
systemctl show nginx.serviceExplanation:
show: prints internal systemd properties for the specified unit.- Useful for automation, scripts or advanced diagnostics.
Example filtering a property:
systemctl show nginx.service --property=MainPIDExplanation:
--property=MainPID: shows only theMainPIDproperty, which corresponds to the main process recognized by systemd.
Another example:
systemctl show nginx.service --property=FragmentPath --property=DropInPathsExplanation:
FragmentPath: path to the main loaded unit file.DropInPaths: paths of drop-in files applied to the unit.
3.6 View the effective unit file
systemctl cat nginx.serviceExplanation:
cat: shows the main unit file and applied drop-ins.- It is one of the safest ways to review the effective configuration that systemd is using.
Example:
systemctl cat nginx.service4. Service installation
Installing a service can mean two different things:
- Installing a system package that includes its own systemd service.
- Manually creating and installing a
.servicefile for a custom application.
This section covers installation via package managers. Manual creation is covered in the next section.
4.1 Install a service on Debian, Ubuntu and derivatives
Example with nginx:
sudo apt updatesudo apt install nginxFirst command explanation:
sudo: runs with administrator privileges.apt: high-level package manager in Debian, Ubuntu and derivatives.update: downloads updated package indexes from configured repositories.
Second command explanation:
install: installs one or more packages.nginx: name of the package to install. The package normally includes binaries, configuration files and systemd units.
Full example:
sudo apt updatesudo apt install nginxsystemctl status nginx.serviceAfter installing a package, it is useful to verify whether the service is active or enabled:
systemctl is-active nginx.servicesystemctl is-enabled nginx.serviceExplanation:
is-active: indicates whether the service is currently active.is-enabled: indicates whether the service is enabled to start automatically.
4.2 Install a service on Fedora, RHEL, CentOS Stream, Rocky Linux or AlmaLinux
Example with nginx:
sudo dnf install nginxExplanation:
sudo: administrative privileges.dnf: package manager used by Fedora and modern distributions of the Red Hat family.install: installs the specified package.nginx: web server package.
Full example:
sudo dnf install nginxsystemctl status nginx.serviceIn many Red Hat family distributions, installing a package does not necessarily mean starting or enabling it. To enable and start it:
sudo systemctl enable --now nginx.serviceExplanation:
enable: enables the service for future boots.--now: in addition to enabling it, starts it immediately.nginx.service: affected service.
4.3 Install a service on Arch Linux and derivatives
Example with nginx:
sudo pacman -Syu nginxExplanation:
pacman: Arch Linux package manager.-S: synchronizes and installs packages.-y: updates the package database.-u: updates installed packages if newer versions are available.nginx: package to install.
Then it can be enabled and started:
sudo systemctl enable --now nginx.service4.4 View which files a package installed
On Debian/Ubuntu:
dpkg -L nginx | grep systemdExplanation:
dpkg -L nginx: lists the files installed by thenginxpackage.grep systemd: filters lines containing the wordsystemd.
Example:
dpkg -L nginx | grep '\.service$'Explanation:
grep '\.service$': shows paths ending in.service.
On Fedora/RHEL:
rpm -ql nginx | grep '\.service$'Explanation:
rpm -ql nginx: lists files installed by thenginxpackage.grep '\.service$': filters service units.
5. Manual creation of custom services
When an application does not come packaged as a systemd service, a unit can be created manually.
5.1 Example custom application
Suppose there is a binary at:
/usr/local/bin/miappAnd we want to run it as a service.
First, it is recommended to create a dedicated system user:
sudo useradd --system --home /var/lib/miapp --create-home --shell /usr/sbin/nologin miappExplanation:
useradd: creates a local user.--system: creates a system account, usually with a UID in the range reserved for services.--home /var/lib/miapp: defines the user’s home directory.--create-home: creates the home directory if it does not exist.--shell /usr/sbin/nologin: prevents normal interactive login.miapp: name of the user created.
In some distributions the nologin path may be different. It can be verified with:
command -v nologin5.2 Create directories for the application
sudo mkdir -p /etc/miapp /var/lib/miapp /var/log/miappsudo chown -R miapp:miapp /var/lib/miapp /var/log/miappsudo chmod 0750 /var/lib/miapp /var/log/miappExplanation:
mkdir -p: creates directories and does not fail if they already exist./etc/miapp: recommended location for persistent configuration./var/lib/miapp: persistent application data./var/log/miapp: own logs if the application writes log files directly.chown -R miapp:miapp: changes owner and group recursively.chmod 0750: allows full access to the owner, read/execute to the group and no access to other users.
5.3 Create the unit file
sudo editor /etc/systemd/system/miapp.serviceSuggested content:
[Unit]Description=My example applicationDocumentation=https://example.com/docs/miappAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=miappGroup=miappWorkingDirectory=/var/lib/miappEnvironmentFile=-/etc/miapp/miapp.envExecStart=/usr/local/bin/miapp --config /etc/miapp/config.yamlRestart=on-failureRestartSec=5sTimeoutStartSec=30sTimeoutStopSec=30sKillSignal=SIGTERM
[Install]WantedBy=multi-user.target[Unit] explanation:
Description: human-readable description of the service.Documentation: URL or documentation reference.After=network-online.target: orders the service to start after systemd considers the online network available. It does not by itself create a strong dependency.Wants=network-online.target: requests thatnetwork-online.targetalso be activated, but without necessarily causing the boot to fail if that target fails.
[Service] explanation:
Type=simple: systemd considers the service started immediately after launching the process specified byExecStart. It is the most common type for foreground applications.User=miapp: runs the process as themiappuser.Group=miapp: runs the process withmiappas the primary group.WorkingDirectory=/var/lib/miapp: working directory of the process.EnvironmentFile=-/etc/miapp/miapp.env: loads environment variables from that file. The-prefix indicates it is not an error if the file does not exist.ExecStart=/usr/local/bin/miapp --config /etc/miapp/config.yaml: main service command.Restart=on-failure: restarts the service if it exits with an error, unclean signal or timeout.RestartSec=5s: waits 5 seconds before restarting.TimeoutStartSec=30s: maximum time allowed to start.TimeoutStopSec=30s: maximum time allowed to stop cleanly.KillSignal=SIGTERM: initial signal used to stop the process.
[Install] explanation:
WantedBy=multi-user.target: when the service is enabled, systemd creates a symlink so the service is started as part ofmulti-user.target.
5.4 Reload systemd after creating or modifying a unit
sudo systemctl daemon-reloadExplanation:
daemon-reload: reloads unit configuration from disk. It is necessary after creating, deleting or modifying.service,.socket,.timer, drop-in and other unit files.
Full example:
sudo editor /etc/systemd/system/miapp.servicesudo systemctl daemon-reloadsudo systemctl status miapp.service5.5 Verify syntax of a unit
systemd-analyze verify /etc/systemd/system/miapp.serviceExplanation:
systemd-analyze: systemd analysis and diagnostic tool.verify: validates unit files and reports syntax problems, dependency issues or unknown directives./etc/systemd/system/miapp.service: file to verify.
Example:
systemd-analyze verify /etc/systemd/system/miapp.serviceIf the command prints no errors, the unit is probably syntactically valid.
5.6 Enable and start the custom service
sudo systemctl enable --now miapp.serviceExplanation:
enable: configures automatic start.--now: starts the service immediately.miapp.service: affected unit.
Verify:
systemctl status miapp.servicejournalctl -u miapp.service -n 50 --no-pagerExplanation:
journalctl: queries journal logs.-u miapp.service: filters by unit.-n 50: shows the last 50 lines.--no-pager: prints directly without opening the interactive pager.
6. Starting, stopping, restarting and reloading services
6.1 Start a service
sudo systemctl start nginx.serviceExplanation:
start: requests immediate start of the unit.- Does not enable the service for future boots.
- If the system is rebooted, the service will only start automatically if it is also enabled.
Example:
sudo systemctl start nginx.servicesystemctl status nginx.service6.2 Stop a service
sudo systemctl stop nginx.serviceExplanation:
stop: stops the unit now.- Does not disable the service for future boots.
- If the service is enabled, it may start again on the next boot.
Example:
sudo systemctl stop nginx.servicesystemctl is-active nginx.service6.3 Restart a service
sudo systemctl restart nginx.serviceExplanation:
restart: stops and restarts the service.- Useful after configuration changes when the service does not support hot reload.
- Causes a service interruption, even if brief.
Example:
sudo nginx -tsudo systemctl restart nginx.serviceExplanation:
nginx -t: validates Nginx configuration before restarting.systemctl restart nginx.service: restarts Nginx only if you decide to continue.
6.4 Reload a service without restarting it
sudo systemctl reload nginx.serviceExplanation:
reload: requests the service to reload its configuration without stopping the main process.- Only works if the unit defines
ExecReload=or if the service has native reload support. - Usually preferable to
restartwhen trying to avoid interruptions.
Example:
sudo nginx -tsudo systemctl reload nginx.service6.5 Reload if possible, restart if not
sudo systemctl reload-or-restart nginx.serviceExplanation:
reload-or-restart: attempts to reload the configuration. If the service does not support reload, restarts it.- Useful in generic scripts.
Example:
sudo systemctl reload-or-restart nginx.service6.6 Restart only if the service is already active
sudo systemctl try-restart nginx.serviceExplanation:
try-restart: restarts the service only if it is already active.- If stopped, does not start it.
Example:
sudo systemctl try-restart nginx.service6.7 Reload or restart only if active
sudo systemctl reload-or-try-restart nginx.serviceExplanation:
reload-or-try-restart: if the service is active, attempts to reload it; if it cannot, restarts it.- If inactive, does not start it.
Example:
sudo systemctl reload-or-try-restart nginx.service6.8 Kill service processes
sudo systemctl kill nginx.serviceExplanation:
kill: sends a signal to the unit’s processes.- By default usually sends
SIGTERM. - Should be used with care, as it can abruptly interrupt production processes.
Example sending SIGKILL:
sudo systemctl kill --signal=SIGKILL nginx.serviceExplanation:
--signal=SIGKILL: sends an uncatchable signal that immediately terminates the process.- Should be used as a last resort.
Example sending SIGHUP:
sudo systemctl kill --signal=SIGHUP nginx.serviceExplanation:
SIGHUP: many daemons interpret this as a configuration reload request, though it depends on the program.
7. Enabling, disabling, masking and unmasking services
In systemd, it is important to distinguish between starting and enabling:
start: starts now.enable: configures automatic start.stop: stops now.disable: prevents automatic start.
7.1 Enable a service to start automatically
sudo systemctl enable nginx.serviceExplanation:
enable: creates symlinks according to the[Install]section of the unit file.- Does not necessarily start the service at that moment.
nginx.service: service being enabled.
Example:
sudo systemctl enable nginx.servicesystemctl is-enabled nginx.service7.2 Enable and start in one step
sudo systemctl enable --now nginx.serviceExplanation:
enable: enables automatic start.--now: starts the service immediately after enabling it.
Example:
sudo systemctl enable --now nginx.servicesystemctl status nginx.service7.3 Disable a service
sudo systemctl disable nginx.serviceExplanation:
disable: removes the symlinks created byenable.- Does not stop the service if it is currently running.
- The service may remain active until manually stopped or the system is rebooted.
Example:
sudo systemctl disable nginx.servicesystemctl is-enabled nginx.servicesystemctl is-active nginx.service7.4 Disable and stop in one flow
There is no single traditional subcommand equivalent to “disable —now” in all historical versions, although many modern versions accept disable --now. For maximum compatibility, it can be done explicitly:
sudo systemctl disable nginx.servicesudo systemctl stop nginx.serviceExplanation:
disable: prevents future automatic start.stop: stops the service in the current session.
Example using --now when available:
sudo systemctl disable --now nginx.serviceExplanation:
--now: withdisable, requests stopping the unit in addition to disabling it.
7.5 Re-enable a service
sudo systemctl reenable nginx.serviceExplanation:
reenable: disables and re-enables the unit.- Useful if the
[Install]section changed or if you want to rebuild the enable symlinks.
Example:
sudo systemctl reenable nginx.service7.6 Mask a service
sudo systemctl mask nginx.serviceExplanation:
mask: blocks the unit by creating a symlink to/dev/null.- A masked service cannot be started manually or as a dependency of another service.
- Stronger than
disable.
Example:
sudo systemctl mask nginx.servicesystemctl status nginx.serviceConceptual output:
Loaded: masked (Reason: Unit nginx.service is masked.)7.7 Mask and stop immediately
sudo systemctl mask --now nginx.serviceExplanation:
mask: blocks the unit.--now: also attempts to stop it immediately.
Example:
sudo systemctl mask --now nginx.service7.8 Unmask a service
sudo systemctl unmask nginx.serviceExplanation:
unmask: removes the symlink to/dev/null.- Does not start or enable the service on its own.
Example:
sudo systemctl unmask nginx.servicesudo systemctl enable --now nginx.service7.9 Check if a service is active or enabled
systemctl is-active nginx.servicesystemctl is-enabled nginx.serviceExplanation:
is-active: returnsactive,inactive,failed, etc.is-enabled: returnsenabled,disabled,masked,static, etc.- These commands are useful in scripts because they also return appropriate exit codes.
Example in a script:
if systemctl is-active --quiet nginx.service; then echo "Nginx is active"else echo "Nginx is not active"fiExplanation:
--quiet: avoids printing text and allows using the command’s exit code.
8. Service configuration with unit files and drop-ins
8.1 Do not directly edit package files
Services installed by packages usually have their units at paths such as:
/usr/lib/systemd/system//lib/systemd/system/Depending on the distribution, one or the other may be the main path. It is not advisable to edit these files directly, because a package update may overwrite changes.
The recommended location for local changes is:
/etc/systemd/system/8.2 Edit a service with a drop-in
sudo systemctl edit nginx.serviceExplanation:
edit: opens an editor to create or modify a local drop-in.- By default creates a file similar to
/etc/systemd/system/nginx.service.d/override.conf. - The drop-in is applied on top of the original unit file.
Example to add automatic restart:
sudo systemctl edit nginx.serviceContents:
[Service]Restart=on-failureRestartSec=3sThen:
sudo systemctl daemon-reloadsudo systemctl restart nginx.serviceExplanation:
daemon-reload: reloads the unit definition.restart: restarts so that options affecting the process start are applied.
8.3 Edit the complete unit file
sudo systemctl edit --full nginx.serviceExplanation:
edit --full: copies the full unit file to/etc/systemd/system/and opens it for editing.- From that point, the local copy may take precedence over the package unit.
- Should be used more carefully than a drop-in, because it may become outdated relative to package changes.
Example:
sudo systemctl edit --full nginx.servicesudo systemctl daemon-reloadsudo systemctl restart nginx.service8.4 View differences between original files and overrides
systemd-deltaExplanation:
systemd-delta: shows differences, overrides, extensions and masked files relative to package-provided units.
Example:
systemd-delta --type=extendedExplanation:
--type=extended: shows units extended via drop-ins.
Another example:
systemd-delta nginx.service8.5 Reset overrides of a unit
To remove a drop-in created with systemctl edit:
sudo rm -rf /etc/systemd/system/nginx.service.dsudo systemctl daemon-reloadsudo systemctl restart nginx.serviceExplanation:
rm -rf /etc/systemd/system/nginx.service.d: removes the local drop-ins directory for that service.daemon-reload: reloads units.restart: restarts the service to apply configuration without overrides.
If systemctl edit --full was used, the full copy must be deleted:
sudo rm -f /etc/systemd/system/nginx.servicesudo systemctl daemon-reloadsudo systemctl restart nginx.serviceExplanation:
rm -f /etc/systemd/system/nginx.service: removes the full local unit.- Upon reload, systemd will use the package unit again, if it exists.
8.6 Override lists in drop-ins
Some systemd directives accept multiple values. To completely replace them in a drop-in, they must first be cleared.
Example with ExecStart:
[Service]ExecStart=ExecStart=/usr/local/bin/miapp --modo produccionExplanation:
- Empty
ExecStart=clears inherited values. - The second line defines the new command.
- This is necessary because
ExecStartmay have special rules and is not always replaced as a simple variable.
Full example:
sudo systemctl edit miapp.serviceContents:
[Service]ExecStart=ExecStart=/usr/local/bin/miapp --config /etc/miapp/prod.yamlApply:
sudo systemctl daemon-reloadsudo systemctl restart miapp.service8.7 Configure automatic restarts
Drop-in:
[Service]Restart=on-failureRestartSec=10sStartLimitIntervalSec=300StartLimitBurst=5Explanation:
Restart=on-failure: restarts on failures.RestartSec=10s: waits 10 seconds before restarting.StartLimitIntervalSec=300: 300-second time window to limit attempts.StartLimitBurst=5: allows up to 5 attempts within that window before considering the service as repeatedly failing.
Commands:
sudo systemctl edit miapp.servicesudo systemctl daemon-reloadsudo systemctl restart miapp.service8.8 Configure dependencies and boot order
Example:
[Unit]After=network-online.target postgresql.serviceWants=network-online.targetRequires=postgresql.serviceExplanation:
After=: defines order. The unit starts after the listed ones.Wants=: weak dependency. Attempts to activate the other unit, but does not necessarily fail if it fails.Requires=: strong dependency. If the required unit fails to start, the dependent unit is also affected.
Drop-in example:
sudo systemctl edit miapp.serviceContents:
[Unit]After=network-online.target postgresql.serviceWants=network-online.targetRequires=postgresql.serviceApply:
sudo systemctl daemon-reloadsudo systemctl restart miapp.service9. Environment variables, users, permissions and working directories
9.1 Inline environment variables
[Service]Environment="APP_ENV=production" "APP_PORT=8080"Explanation:
Environment=defines variables directly inside the unit file.- Each assignment can be quoted.
- Useful for non-secret and relatively stable values.
Example:
sudo systemctl edit miapp.serviceContents:
[Service]Environment="APP_ENV=production" "LOG_LEVEL=info"Apply:
sudo systemctl daemon-reloadsudo systemctl restart miapp.service9.2 Variables from EnvironmentFile
File:
sudo install -d -m 0750 -o root -g miapp /etc/miappsudo editor /etc/miapp/miapp.envFile contents:
APP_ENV=productionAPP_PORT=8080LOG_LEVEL=infoUnit file:
[Service]EnvironmentFile=/etc/miapp/miapp.envExplanation:
EnvironmentFile=reads variables from a file.- The file should not have complex shell syntax; it should normally contain
KEY=valuelines. - If prefixed with
-, the absence of the file is not considered an error:
[Service]EnvironmentFile=-/etc/miapp/miapp.envFull example:
sudo editor /etc/miapp/miapp.envsudo systemctl restart miapp.serviceNote: if only the content of EnvironmentFile changes, restarting the service is usually sufficient. If the unit file is changed to add or remove the EnvironmentFile= directive, then daemon-reload is required.
9.3 Run as a dedicated user
[Service]User=miappGroup=miappExplanation:
User=reduces privileges by running the process as an unprivileged user.Group=defines the main effective group.- It is a basic security measure to avoid running applications as
rootunnecessarily.
User creation example:
sudo useradd --system --home /var/lib/miapp --create-home --shell /usr/sbin/nologin miapp9.4 Configure working directory
[Service]WorkingDirectory=/var/lib/miappExplanation:
WorkingDirectory=sets the current directory from which the process will run.- Useful if the application expects relative paths.
Example:
[Service]WorkingDirectory=/opt/miappExecStart=/opt/miapp/bin/miapp9.5 Automatically create directories with systemd
systemd can create runtime, state, cache and log directories for a service.
Example:
[Service]User=miappGroup=miappRuntimeDirectory=miappStateDirectory=miappCacheDirectory=miappLogsDirectory=miappExplanation:
RuntimeDirectory=miapp: creates/run/miappwhile the service is active.StateDirectory=miapp: creates/var/lib/miappfor persistent data.CacheDirectory=miapp: creates/var/cache/miapp.LogsDirectory=miapp: creates/var/log/miapp.- systemd assigns permissions and ownership corresponding to the service user when appropriate.
Usage example:
[Service]User=miappGroup=miappStateDirectory=miappWorkingDirectory=/var/lib/miappExecStart=/usr/local/bin/miapp9.6 Use DynamicUser
[Service]DynamicUser=yesStateDirectory=miappExecStart=/usr/local/bin/miappExplanation:
DynamicUser=yes: systemd assigns a dynamic user for the service.- Reduces the need for manual user creation.
- For persistent data, directives like
StateDirectory=should be used.
Example:
[Unit]Description=Service with dynamic user
[Service]DynamicUser=yesStateDirectory=miappExecStart=/usr/local/bin/miapp --data-dir /var/lib/miapp
[Install]WantedBy=multi-user.target10. Logs, diagnostics and troubleshooting
10.1 View logs for a service
journalctl -u nginx.serviceExplanation:
journalctl: queries the systemd journal.-u nginx.service: filters logs associated with that unit.
Example:
journalctl -u nginx.service10.2 View last log lines
journalctl -u nginx.service -n 100 --no-pagerExplanation:
-n 100: shows the last 100 lines.--no-pager: does not openlessor another pager.
Example:
journalctl -u nginx.service -n 100 --no-pager10.3 Follow logs in real time
journalctl -u nginx.service -fExplanation:
-f: follows the log in real time, similar totail -f.
Example:
journalctl -u miapp.service -f10.4 View logs from the last boot
journalctl -u nginx.service -bExplanation:
-b: limits the query to the current boot.
Example:
journalctl -u nginx.service -b --no-pager10.5 View logs from a previous boot
journalctl --list-bootsExplanation:
--list-boots: lists boots recorded in the journal.
Then:
journalctl -b -1 -u nginx.serviceExplanation:
-b -1: selects the previous boot.-u nginx.service: filters by service.
10.6 View system errors
journalctl -p err -bExplanation:
-p err: filters priorityerror more severe.-b: limits to the current boot.
Example:
journalctl -p warning..alert -b --no-pagerExplanation:
warning..alert: shows messages from warning to alert.
10.7 Diagnose failed services
systemctl --failedExplanation:
--failed: shows units in failed state.
Example:
systemctl --failedView details of a failed unit:
systemctl status miapp.servicejournalctl -u miapp.service -b -n 200 --no-pager10.8 Reset failed status
sudo systemctl reset-failed miapp.serviceExplanation:
reset-failed: clears thefailedstate recorded by systemd.- Does not fix the cause of the failure; only clears the failure state after fixing it or if it is no longer relevant.
Example:
sudo systemctl reset-failed miapp.servicesystemctl status miapp.serviceReset all recorded failures:
sudo systemctl reset-failed10.9 Check boot times
systemd-analyzeExplanation:
- Shows the general boot time of the firmware, bootloader, kernel, initrd and user space, depending on availability.
Example:
systemd-analyze blameExplanation:
blame: lists units ordered by initialization time.
Example with critical path:
systemd-analyze critical-chainExplanation:
critical-chain: shows the critical chain of units that influenced the boot time.
10.10 View dependency tree
systemctl list-dependencies nginx.serviceExplanation:
list-dependencies: shows dependencies related to the unit.
Reverse example:
systemctl list-dependencies --reverse nginx.serviceExplanation:
--reverse: shows units that depend on the indicated one.
11. Uninstalling, removing and cleaning up services
This section distinguishes several operations that are often confused.
- Stop: shut down the service now.
- Disable: prevent automatic starting.
- Mask: prevent it from starting at all.
- Uninstall: remove the package or application files.
- Remove: delete unit files, drop-ins, data, logs or local configuration.
- Clear failed state: remove the
failedstate from systemd.
11.1 Stop before uninstalling
sudo systemctl stop nginx.serviceExplanation:
stop: stops the service immediately.- It is good practice to stop services before removing packages or deleting manual units.
11.2 Disable before uninstalling
sudo systemctl disable nginx.serviceExplanation:
disable: removes automatic start symlinks.- Prevents leaving broken symlinks to units that will be removed.
Combined example:
sudo systemctl disable --now nginx.serviceExplanation:
disable: disables.--now: also attempts to stop it.
11.3 Uninstall a package on Debian/Ubuntu
Remove the package keeping configuration:
sudo apt remove nginxExplanation:
apt remove: uninstalls the package, but may keep configuration files in/etc.nginx: package to remove.
Remove the package and purge configuration managed by the package:
sudo apt purge nginxExplanation:
apt purge: removes the package and its configuration files managed by the package system.- Does not necessarily remove data generated by the application in
/var/lib, logs in/var/logor manually created files.
Remove unused dependencies:
sudo apt autoremoveExplanation:
autoremove: removes packages installed as dependencies that are no longer required.
Full example:
sudo systemctl disable --now nginx.servicesudo apt purge nginxsudo apt autoremove11.4 Uninstall a package on Fedora/RHEL
sudo dnf remove nginxExplanation:
dnf remove: removes the specified package and, depending on dependencies, may remove related packages.nginx: package to uninstall.
Example:
sudo systemctl disable --now nginx.servicesudo dnf remove nginx11.5 Uninstall a package on Arch Linux
sudo pacman -R nginxExplanation:
pacman -R: removes the specified package.
Remove package and unused dependencies installed as dependencies:
sudo pacman -Rs nginxExplanation:
-R: remove.-s: removes dependencies not required by other packages.
Example:
sudo systemctl disable --now nginx.servicesudo pacman -Rs nginx11.6 Remove a manual service created in /etc/systemd/system
Suppose it was created manually:
/etc/systemd/system/miapp.serviceRecommended process:
sudo systemctl disable --now miapp.servicesudo rm -f /etc/systemd/system/miapp.servicesudo systemctl daemon-reloadsudo systemctl reset-failed miapp.serviceExplanation:
disable --now: disables and stops the service.rm -f: removes the local unit file.daemon-reload: forces systemd to reload the list of available units.reset-failed: clears a possible failed state associated with the unit name.
If the service had drop-ins:
sudo rm -rf /etc/systemd/system/miapp.service.dsudo systemctl daemon-reloadExplanation:
.service.d: drop-in fragments directory.- Removing it eliminates local overrides.
11.7 Remove data, logs and configuration from a custom application
After removing the unit file, if you want to completely remove the application:
sudo rm -rf /etc/miapp /var/lib/miapp /var/log/miapp /var/cache/miappExplanation:
/etc/miapp: configuration./var/lib/miapp: persistent data./var/log/miapp: own logs./var/cache/miapp: caches.rm -rf: removes recursively without prompting; must be used with extreme caution.
Safer example, listing first:
sudo find /etc/miapp /var/lib/miapp /var/log/miapp /var/cache/miapp -maxdepth 2 -printThen, if the paths are confirmed correct:
sudo rm -rf /etc/miapp /var/lib/miapp /var/log/miapp /var/cache/miapp11.8 Remove a service user and group
sudo userdel miappExplanation:
userdel: removes the local account.miapp: user to delete.
If you also want to delete the home directory, depending on how it was created:
sudo userdel --remove miappExplanation:
--remove: attempts to delete the user’s home and spool directory.
Note: if the home directory was shared, contains necessary data or is in a sensitive path, it is advisable to review manually first.
11.9 Clean up orphan or not-found units
After manually removing services:
sudo systemctl daemon-reloadsystemctl list-units --type=service --all | grep miappIf it appears as failed:
sudo systemctl reset-failed miapp.serviceIf it appears as masked:
sudo systemctl unmask miapp.servicesudo systemctl daemon-reload11.10 Remove a manually masked service
If a symlink like this exists:
/etc/systemd/system/miapp.service -> /dev/nullIt can be unmasked:
sudo systemctl unmask miapp.serviceOr review manually:
ls -l /etc/systemd/system/miapp.serviceIf it is confirmed to be a symlink to /dev/null:
sudo rm -f /etc/systemd/system/miapp.servicesudo systemctl daemon-reload12. User services: systemd —user
systemd can also manage per-user services, without root privileges. These services belong to the user session and are normally configured under:
~/.config/systemd/user/12.1 Create a user service
mkdir -p ~/.config/systemd/usereditor ~/.config/systemd/user/mi-tarea.serviceContents:
[Unit]Description=Example user service
[Service]ExecStart=/home/usuario/bin/mi-tareaRestart=on-failure
[Install]WantedBy=default.targetExplanation:
- The unit does not use
sudo. WantedBy=default.targetrefers to the default target of the user manager, not the system’smulti-user.target.
12.2 Reload user units
systemctl --user daemon-reloadExplanation:
--user: talks to the current user’s systemd manager, not the system’s systemd.daemon-reload: reloads user units.
12.3 Enable and start a user service
systemctl --user enable --now mi-tarea.serviceExplanation:
--user: user mode.enable: enables for future user sessions.--now: starts immediately.
12.4 View user logs
journalctl --user -u mi-tarea.serviceExplanation:
--user: queries user unit logs.-u mi-tarea.service: filters by unit.
12.5 Allow user services to persist after logout
loginctl enable-linger usuarioExplanation:
loginctl: manages sessions and users from systemd-logind.enable-linger usuario: allows the user’s systemd manager to keep running after logging out.
Example:
sudo loginctl enable-linger deployThis is useful for user services run by a deployment account.
Disable linger:
sudo loginctl disable-linger deploy13. Timers: practical replacement for cron for periodic services
systemd timers allow services to be run on a schedule.
13.1 Create a service executable by a timer
File:
sudo editor /etc/systemd/system/backup-miapp.serviceContents:
[Unit]Description=MiApp Backup
[Service]Type=oneshotUser=miappGroup=miappExecStart=/usr/local/bin/backup-miappExplanation:
Type=oneshot: one-shot execution service; systemd waits for the process to finish.ExecStart: command executed by the timer.
13.2 Create the timer
File:
sudo editor /etc/systemd/system/backup-miapp.timerContents:
[Unit]Description=Runs MiApp backup daily
[Timer]OnCalendar=*-*-* 03:30:00Persistent=trueUnit=backup-miapp.service
[Install]WantedBy=timers.targetExplanation:
OnCalendar=*-*-* 03:30:00: runs daily at 03:30.Persistent=true: if the system was off at the scheduled time, runs the task on boot.Unit=backup-miapp.service: service that will be triggered.WantedBy=timers.target: allows enabling the timer.
13.3 Enable and start the timer
sudo systemctl daemon-reloadsudo systemctl enable --now backup-miapp.timerExplanation:
daemon-reload: loads the new.serviceand.timerfiles.enable --now: enables and starts the timer.
13.4 List timers
systemctl list-timers --allExplanation:
list-timers: lists active timers.--all: includes inactive timers.
Example:
systemctl list-timers --all | grep backup-miapp13.5 Manually run the timer service
sudo systemctl start backup-miapp.serviceExplanation:
- Runs the service immediately, without waiting for the next timer trigger.
14. Security and service hardening
systemd allows applying isolation measures without modifying the application.
14.1 Protect the filesystem
Drop-in example:
[Service]ProtectSystem=strictProtectHome=trueReadWritePaths=/var/lib/miapp /var/log/miappExplanation:
ProtectSystem=strict: mounts parts of the filesystem as read-only for the service.ProtectHome=true: blocks access to/home,/rootand/run/user.ReadWritePaths=: allows explicit write access to necessary paths.
Apply:
sudo systemctl edit miapp.servicesudo systemctl daemon-reloadsudo systemctl restart miapp.service14.2 Restrict privileges
[Service]NoNewPrivileges=truePrivateTmp=truePrivateDevices=trueExplanation:
NoNewPrivileges=true: prevents the process from gaining additional privileges through mechanisms such as setuid binaries.PrivateTmp=true: provides a private/tmpfor the service.PrivateDevices=true: restricts access to devices.
14.3 Restrict Linux capabilities
[Service]CapabilityBoundingSet=CAP_NET_BIND_SERVICEAmbientCapabilities=CAP_NET_BIND_SERVICEExplanation:
CapabilityBoundingSet=limits the capabilities available to the process.CAP_NET_BIND_SERVICEallows binding ports below 1024 without running as root.AmbientCapabilities=allows passing capabilities to the executed process.
Example for an application listening on port 80:
[Service]User=miappGroup=miappAmbientCapabilities=CAP_NET_BIND_SERVICECapabilityBoundingSet=CAP_NET_BIND_SERVICEExecStart=/usr/local/bin/miapp --listen :8014.4 Analyze security of a unit
systemd-analyze security miapp.serviceExplanation:
systemd-analyze security: evaluates the security exposure of a unit based on available isolation directives.- Does not replace a security audit, but provides a useful guide.
Example:
systemd-analyze security nginx.service14.5 Reasonable hardening of a custom service
Example:
[Service]User=miappGroup=miappNoNewPrivileges=truePrivateTmp=trueProtectSystem=fullProtectHome=trueReadWritePaths=/var/lib/miapp /var/log/miappRestart=on-failureExplanation:
- Do not copy a hardening template without testing it.
- Some applications need access to specific paths, devices, network, user homes or system calls.
- It is recommended to apply one restriction at a time and verify logs.
15. Operational best practices
15.1 Use clear names
For custom services, use descriptive names:
miapp-api.servicemiapp-worker.servicemiapp-scheduler.serviceThis facilitates logging, monitoring and administration.
15.2 Use dedicated users
Avoid running services as root unless strictly necessary. Use:
[Service]User=miappGroup=miapp15.3 Separate configuration, data and binaries
Common convention:
/usr/local/bin/miapp # manually installed binary/etc/miapp/ # configuration/var/lib/miapp/ # persistent data/var/log/miapp/ # own logs, if applicable/run/miapp/ # temporary runtime files15.4 Validate before restarting
Example with Nginx:
sudo nginx -t && sudo systemctl reload nginx.serviceExplanation:
nginx -t: validates configuration.&&: runs the second command only if the first was successful.systemctl reload: reloads without fully restarting.
Example with a custom application that supports validation:
/usr/local/bin/miapp --check-config /etc/miapp/config.yaml && sudo systemctl restart miapp.service15.5 Use drop-ins for local changes
Preferred:
sudo systemctl edit miapp.serviceAvoid, unless justified:
sudo editor /usr/lib/systemd/system/miapp.serviceReason: files under /usr/lib/systemd/system or /lib/systemd/system usually belong to packages and may be overwritten by updates.
15.6 Document overrides
A drop-in can include comments:
[Service]# Automatic restart to tolerate transient network failures.Restart=on-failureRestartSec=5s15.7 Review logs after any change
sudo systemctl restart miapp.servicesystemctl status miapp.servicejournalctl -u miapp.service -b -n 100 --no-pager15.8 Avoid rm -rf without verifying paths
Before deleting data:
sudo find /var/lib/miapp -maxdepth 2 -printThen, if confirmed:
sudo rm -rf /var/lib/miapp15.9 Use daemon-reload when appropriate
Should be used after:
- Creating a unit file.
- Deleting a unit file.
- Modifying a unit file.
- Creating, deleting or modifying drop-ins.
- Changing
.timer,.socket,.mount, etc. units.
Command:
sudo systemctl daemon-reloadNot usually necessary after modifying only a file loaded with EnvironmentFile=, unless the unit file itself was changed.
15.10 Differentiate reload from daemon-reload
sudo systemctl reload nginx.serviceReloads the Nginx configuration.
sudo systemctl daemon-reloadReloads the systemd configuration, that is, the unit files.
These are different operations.
16. Important directories and files
16.1 System unit files
/etc/systemd/system/Use:
- Units created by the administrator.
- Local overrides.
- Drop-ins.
- Symlinks created by
systemctl enable. - Higher precedence than package-installed units.
Examples:
/etc/systemd/system/miapp.service/etc/systemd/system/nginx.service.d/override.conf/etc/systemd/system/multi-user.target.wants/nginx.service/usr/lib/systemd/system/Use:
- Common location for package-installed units in many distributions.
- In Debian and derivatives it may coexist with
/lib/systemd/system. - Direct editing is not recommended.
Examples:
/usr/lib/systemd/system/sshd.service/usr/lib/systemd/system/docker.service/lib/systemd/system/Use:
- Traditional location in Debian/Ubuntu for package-installed units.
- May be a symlink or functional equivalent to paths under
/usrdepending on the distribution. - Direct editing is not recommended.
Examples:
/lib/systemd/system/ssh.service/lib/systemd/system/nginx.service/run/systemd/system/Use:
- Generated or temporary runtime units.
- Lost on reboot.
16.2 User unit files
~/.config/systemd/user/Use:
- Current user’s services.
- Does not require root privileges.
Example:
/home/usuario/.config/systemd/user/mi-tarea.service/etc/systemd/user/Use:
- User units globally available to all users.
/usr/lib/systemd/user/Use:
- User units installed by packages.
16.3 General systemd configuration
/etc/systemd/system.confUse:
- Global configuration for the system’s systemd manager.
- Should be modified with care.
/etc/systemd/user.confUse:
- Global configuration for user systemd managers.
/etc/systemd/journald.confUse:
- Configuration for
systemd-journald. - Controls persistence, size and journal log policies.
/etc/systemd/logind.confUse:
- Configuration for
systemd-logind. - Manages sessions, seats, power buttons, suspension and linger.
16.4 Journal logs
/run/log/journal/Use:
- Volatile journal logs.
- Lost on reboot if persistence is not configured.
/var/log/journal/Use:
- Persistent journal logs.
- If it exists and journald is configured to use persistent storage, logs survive reboots.
Create persistent journal storage:
sudo mkdir -p /var/log/journalsudo systemctl restart systemd-journald.serviceExplanation:
mkdir -p /var/log/journal: creates the persistent path.restart systemd-journald.service: restarts journald to take the change into account.
16.5 Application configuration
/etc/<service>/Use:
- Service-specific persistent configuration.
Examples:
/etc/nginx//etc/ssh//etc/postgresql//etc/miapp/16.6 Persistent data
/var/lib/<service>/Use:
- Persistent data for applications and services.
Examples:
/var/lib/postgresql//var/lib/mysql//var/lib/docker//var/lib/miapp/16.7 Classic file-based logs
/var/log/<service>/Use:
- Logs written directly by the application.
- Not all services use their own files; many write to stdout/stderr and are queried with
journalctl.
Examples:
/var/log/nginx//var/log/apache2//var/log/miapp/16.8 Runtime files
/run/<service>/Use:
- PID files, sockets, locks and temporary runtime files.
- Cleared on each reboot.
Examples:
/run/nginx.pid/run/sshd.pid/run/miapp/16.9 tmpfiles.d
/etc/tmpfiles.d//usr/lib/tmpfiles.d//run/tmpfiles.d/Use:
- Rules for creating, cleaning or removing temporary or persistent files and directories.
- Useful for preparing directories under
/run,/var/cache,/var/logor other paths.
Example file:
/etc/tmpfiles.d/miapp.confContents:
d /run/miapp 0750 miapp miapp -d /var/log/miapp 0750 miapp miapp -Apply manually:
sudo systemd-tmpfiles --create /etc/tmpfiles.d/miapp.confExplanation:
systemd-tmpfiles: creates, cleans or removes files according to tmpfiles.d rules.--create: creates files/directories defined in the configuration./etc/tmpfiles.d/miapp.conf: rules file to apply.
16.10 sysusers.d
/etc/sysusers.d//usr/lib/sysusers.d//run/sysusers.d/Use:
- Declarative rules for creating system users and groups.
- Widely used by packages.
Example:
/etc/sysusers.d/miapp.confContents:
u miapp - "System user for MiApp" /var/lib/miapp /usr/sbin/nologinApply manually:
sudo systemd-sysusers /etc/sysusers.d/miapp.confExplanation:
systemd-sysusers: creates system users and groups according tosysusers.dfiles.- The file defines the
miappuser, description, home and shell.
16.11 Paths related to enabling
When running:
sudo systemctl enable miapp.servicesystemd may create symlinks such as:
/etc/systemd/system/multi-user.target.wants/miapp.service -> /etc/systemd/system/miapp.serviceExplanation:
- The
.wantsdirectory represents a weak dependency of the target. - These symlinks are normally not created manually; they are managed with
systemctl enableandsystemctl disable.
16.12 Common environment files
There is no single universal location, but these are common:
/etc/default/<service> # Debian/Ubuntu in some packages/etc/sysconfig/<service> # RHEL/Fedora in some packages/etc/<service>/<service>.envExamples:
/etc/default/nginx/etc/sysconfig/sshd/etc/miapp/miapp.envThese files only affect the service if the unit file has a directive such as:
EnvironmentFile=/etc/miapp/miapp.envReference sources
- systemd.unit — official freedesktop.org documentation: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
- systemd.service — official freedesktop.org documentation: https://www.freedesktop.org/software/systemd/man/systemd.service.html
- systemd-tmpfiles — man-pages documentation: https://man7.org/linux/man-pages/man8/systemd-tmpfiles.8.html
- tmpfiles.d — official freedesktop.org documentation: https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html
- sysusers.d — man-pages documentation: https://man7.org/linux/man-pages/man5/sysusers.d.5.html
Quick reference: cheat sheet of frequent commands
# Check statussystemctl status servicio.service
# Start nowsudo systemctl start servicio.service
# Stop nowsudo systemctl stop servicio.service
# Restartsudo systemctl restart servicio.service
# Reload service configurationsudo systemctl reload servicio.service
# Enable on bootsudo systemctl enable servicio.service
# Enable and start nowsudo systemctl enable --now servicio.service
# Disable on bootsudo systemctl disable servicio.service
# Disable and stop nowsudo systemctl disable --now servicio.service
# Mask to prevent startingsudo systemctl mask servicio.service
# Unmasksudo systemctl unmask servicio.service
# Reload unit definitionssudo systemctl daemon-reload
# View logsjournalctl -u servicio.service
# View recent logsjournalctl -u servicio.service -n 100 --no-pager
# Follow logs in real timejournalctl -u servicio.service -f
# View failed unitssystemctl --failed
# Clear failed statesudo systemctl reset-failed servicio.service
# View effective unit filesystemctl cat servicio.service
# Create local overridesudo systemctl edit servicio.service
# Validate unitsystemd-analyze verify /etc/systemd/system/servicio.service