Managing services with systemd

Start, stop and enable services with systemctl, read logs with journalctl, and write a unit file so your server survives a logout.

Linux9 min read

On this page
  1. systemctl, the six commands you need
  2. Reading systemctl status
  3. journalctl
  4. Writing a unit file
  5. User= and Group=
  6. WorkingDirectory=
  7. ExecStart=
  8. Restart= and RestartSec=
  9. After= and Wants=
  10. WantedBy=multi-user.target
  11. Putting it in service
  12. Changing a service you did not write
  13. When a unit will not start
  14. Related

If you start your game server by running its script over SSH, it dies when you close the terminal. That is not a bug in the game server, it is how a login session works: the processes you start belong to that session, and when the session ends they are told to end with it. The fix is to hand the program to systemd instead, so it belongs to the system rather than to your terminal.

Doing that also gets you three things you would otherwise build by hand: it starts at boot, it restarts after a crash, and its output goes somewhere you can read later. This page covers running the services that came with your server, reading their logs, and writing a unit file for something that did not come with one.

systemctl, the six commands you need

Every command here runs as root or with sudo.

Shell
systemctl status nginx
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl enable nginx
systemctl disable nginx

The distinction that catches everyone: start and stop act on right now, enable and disable act on the next boot. A service you started but never enabled is gone after a reboot. A service you disabled but never stopped is still running until you stop it or reboot. When you mean both, say both:

Shell
systemctl enable --now fivem
systemctl disable --now postfix
enablestarts at the next bootdisablestays off at the next bootstartrunning nowRunning, and back after a rebootStarted and enabled.systemctl enable --now fivemRunning until a stop or rebootStarted but never enabled, or disabledbut never stopped. Gone after the nextreboot.stopoff nowOff now, starts at the next bootEnabled but not started.Off now and after a rebootStopped and disabled.systemctl disable --now postfixstarted and enabledRunning, and back after a rebootStarted and enabled.systemctl enable --now fivemstarted, not enabledRunning until a stop or rebootStarted but never enabled, or disabled butnever stopped. Gone after the next reboot.enabled, not startedOff now, starts at the next bootEnabled but not started.stopped and disabledOff now and after a rebootStopped and disabled.systemctl disable --now postfix
The two pairs answer different questions, so a service is always in one of four states. The trap is the one in red: running now, and gone after the next reboot, whether you started it without enabling it or disabled it without stopping it. Illustration.

Two more that answer questions directly, without you reading through status output:

Shell
systemctl is-active fivem
systemctl is-enabled fivem

And reload, which some services support, asks the service to re-read its configuration without dropping what it is doing. systemctl reload nginx keeps connections alive where restart would break them. Not every service implements it, and systemctl reload-or-restart falls back for you.

To see what is on the machine:

Shell
systemctl list-units --type=service --state=running
systemctl list-unit-files --state=enabled

The first is what is running now, the second is what will come back after a reboot. Comparing the two is a good way to find something you started by hand months ago and forgot to enable.

Reading systemctl status

systemctl status fivem is the first thing to run when something is wrong. Read it in this order:

  • The Loaded: line. It names the unit file on disk and says whether it is enabled or disabled. If it says not-found, the unit does not exist, usually a typo in the name.
  • The Active: line. active (running) is healthy. inactive (dead) means it is not running and nothing is trying. failed means it tried and gave up, and the same line carries the reason.
  • The exit code on a failure, shown as status= followed by a number. Common ones are covered under troubleshooting below.
  • The last log lines, printed underneath. They are usually the actual answer, and journalctl gives you the rest of them.
Example output for a unit that will not start, numbered in the order of the list above. The Loaded line says the unit was found and is enabled, the Active line says it failed, status= gives the exit code, and the log line under it names the missing file: the 203/EXEC case in the table further down. Example output, trimmed.

journalctl

systemd captures whatever a service writes to standard output and standard error, so a program that prints to the screen when you run it by hand has its output collected without you configuring any logging at all.

Narrow by unit first, because the whole journal is everything on the machine:

Shell
journalctl -u fivem

Then narrow further. These combine freely:

Shell
journalctl -u fivem -n 200
journalctl -u fivem -f
journalctl -u fivem --since "2026-08-18 20:00" --until "2026-08-18 21:00"
journalctl -u fivem --since "30 min ago"
journalctl -u fivem -p err
journalctl -u fivem -b
journalctl -u fivem -b -1

What each one does:

  • -n 200 shows the last 200 lines instead of the default 10.
  • -f follows the log live, like tail -f. This is the one to have open in a second window while you restart something.
  • --since and --until take an absolute timestamp or plain English such as yesterday, 1 hour ago, or today.
  • -p err shows only entries at error priority or worse, which cuts a noisy log down to the lines that matter. -p warning is a gentler filter.
  • -b limits to the current boot, and -b -1 to the previous one, which is how you find out what happened before an unplanned reboot.
  • -k shows kernel messages only, which is where an out-of-memory kill appears. See Disk, memory and swap.

By default the journal pages through less, so q quits, / searches, and -e jumps straight to the end. Add --no-pager when you want to pipe it:

Shell
journalctl -u fivem --since today --no-pager | grep -i error

Writing a unit file

A unit file is a text file that tells systemd how to run your program. Put your own in /etc/systemd/system/, which is the directory reserved for the administrator, and name it after the service with a .service extension.

Before you write it, create an account for the service to run as and give that account ownership of its files. See Users, groups and sudo for why this matters more than anything else on this page.

Here is a complete unit for a game server, /etc/systemd/system/fivem.service:

Config
[Unit]
Description=FiveM game server
After=network-online.target
Wants=network-online.target
 
[Service]
Type=simple
User=fivem
Group=fivem
WorkingDirectory=/opt/fivem/server-data
ExecStart=/opt/fivem/server/run.sh +exec server.cfg
Restart=on-failure
RestartSec=10s
 
[Install]
WantedBy=multi-user.target

Taking the settings that carry real weight in turn.

User= and Group=

The account the process runs as. systemd drops to it before your program starts, so the program never has root at all, and a flaw in it cannot reach beyond what that account owns. Leaving this out means running as root, which is the single worst default on this page.

If the unit fails immediately with status=217/USER, the account named here does not exist.

WorkingDirectory=

The directory the process starts in. Game servers care about this more than most software, because their configuration and resource paths are usually written relative to where the server was launched, so the same command that works when you cd there by hand fails from systemd without it.

If the unit fails with status=200/CHDIR, this directory is missing, or the account in User= cannot enter it.

ExecStart=

The command to run. Two rules that account for most first-attempt failures:

  • It needs an absolute path. There is no PATH lookup the way there is in your shell.
  • It is not a shell. Pipes, &&, output redirection with >, wildcards, and environment variable expansion do not work, because no shell is involved. If you need any of them, either put the command in a small script and point ExecStart at the script, or run it explicitly through one with /bin/sh -c followed by the quoted command line.

status=203/EXEC means the path is wrong, the file is not executable, or its interpreter line points at something that is not there.

Environment variables that your program needs go in the unit, either inline or from a file, which is the tidier place for secrets:

Config
Environment=FIVEM_PORT=30120
EnvironmentFile=/etc/fivem/env

Restart= and RestartSec=

Restart= decides what happens when the program exits:

  • no is the default. It stays down.
  • on-failure restarts when it exits with a non-zero code or is killed by a signal, and leaves it down after a clean exit. This is the right choice for most things, because a clean exit usually means it was told to stop.
  • always restarts whatever the exit code was. Use it for a program that exits cleanly when it should not, which some game servers do on a fatal configuration error.

systemctl stop never triggers a restart under any setting, so you can always stop a service by hand.

RestartSec= is how long to wait before trying again. The default is 100 milliseconds, which for anything that binds a port is too fast: the old socket may still be closing. A few seconds is kinder and makes the logs readable.

Crashes: a non-zero exitor a signalExits cleanlyYou run systemctl stopRestart=noThe defaultStays downStays downStays downRestart=on-failureRight for most thingsRestartsStays downStays downRestart=alwaysFor programs that exit cleanlywhen they should notRestartsRestartsStays downEvery restart waits RestartSec= first, 100 milliseconds by default. A unit that starts more than a handfulof times in a short window is left failed instead.Crash orsignalCleanexitsystemctlstopRestart=noThe defaultStays downStays downStays downRestart=on-failureRight for mostthingsRestartsStays downStays downRestart=alwaysFor programs thatexit cleanly whenthey should notRestartsRestartsStays downEvery restart waits RestartSec= first, 100milliseconds by default. A unit that startsmore than a handful of times in a short windowis left failed instead.
Each setting against the three ways a program can end. on-failure and always differ only on a clean exit, and nothing restarts after systemctl stop. Illustration.

After= and Wants=

After= controls ordering, not dependency: it says start this later than that. Wants= says pull that in as well, without failing if it does not come up. network-online.target is the pairing you want for anything that binds to your public address at startup, because plain network.target is reached before an address has been assigned.

If your server needs a database on the same machine, add its unit to both, for example After=mariadb.service and Wants=mariadb.service.

WantedBy=multi-user.target

This is what systemctl enable acts on. It is the reason enabling works at all, so a unit without an [Install] section can be started by hand but cannot be enabled.

Putting it in service

After creating or editing any unit file, systemd needs to re-read it. It does not notice changes on its own:

Shell
systemctl daemon-reload
systemctl enable --now fivem
systemctl status fivem

systemd-analyze verify catches typos and unknown settings before you find them the hard way:

Shell
systemd-analyze verify /etc/systemd/system/fivem.service

Then confirm the port is actually listening, and that the rules on the network side let players reach it:

Shell
ss -lntup | grep 30120

See Port rules for the network half.

Changing a service you did not write

Do not edit a unit file that came from a package. The next upgrade overwrites it. Use a drop-in instead, which systemd merges over the packaged unit:

Shell
systemctl edit nginx

That opens an empty override file, writes it to /etc/systemd/system/nginx.service.d/override.conf, and runs daemon-reload when you save. Put only the settings you are changing in it.

To see the result, including every drop-in in the order they apply:

Shell
systemctl cat nginx

One quirk worth knowing: settings that can appear more than once, ExecStart among them, add to the list rather than replacing it. To replace one, set it to nothing first, on its own line, and then set your value.

When a unit will not start

Work through these in order.

Read the status and the last logs together.

Shell
systemctl status fivem -l --no-pager
journalctl -u fivem -n 100 --no-pager

Match the exit code. systemctl status prints it as status= and a number:

CodeMeaning
203/EXECThe ExecStart binary was not found, is not executable, or its interpreter is missing
200/CHDIRWorkingDirectory does not exist or is not reachable by the service account
217/USERThe user or group named in the unit does not exist
226/NAMESPACEA sandboxing setting such as ProtectSystem or PrivateTmp could not be applied
1Your program started and exited on its own. Its own logs have the reason

Run the same command by hand as the service account. This separates "systemd is configured wrong" from "the program is broken", which is worth ten minutes of guessing:

Shell
sudo -u fivem /opt/fivem/server/run.sh +exec server.cfg

Check permissions if it exits immediately with nothing useful. A service account that cannot write to its own data directory fails in ways that look unrelated. See Users, groups and sudo.

Check the disk is not full. A service that cannot write refuses to start, and this is common enough to be worth ruling out early. See Disk, memory and swap.

Need a server, or a hand with one?

Pick a plan to deploy a server, or open a ticket in the panel. Support runs 24/7/365.