The most reliable way to keep a game server alive is to manage its process with a systemd service file. Processes started in the background with screen or nohup vanish when the server reboots, and nobody brings them back when they crash. systemd, on the other hand, starts your game process automatically at boot, restarts it after a crash, and makes all of its output readable from a single place. In this article we turn a game process into a systemd service from scratch.
Why use a systemd service?
Whether it's Metin2, Minecraft, a C++ game core, or your own server binary, they're all long-running background processes. Starting them straight from a terminal leads to these problems:
- The process dies when the SSH session closes.
- The game doesn't come back automatically after a reboot.
- A crash requires manual intervention.
- Logs are scattered, making it hard to track what happened and where.
systemd solves all of this. It's the default init system on modern Linux distributions (Debian, Ubuntu, AlmaLinux, Rocky), so you don't need to install anything extra.
Create a dedicated user for the service
For security reasons you don't want to run the game process as root. Create a dedicated, shell-less system user:
sudo useradd --system --create-home --shell /usr/sbin/nologin gameserver
sudo mkdir -p /opt/game
sudo chown -R gameserver:gameserver /opt/game
Place your game files under /opt/game and check the permissions of the executable binary: chmod +x /opt/game/start. This user only runs the game and cannot log into the system, which limits the impact of any potential security hole.
Writing the systemd service file
Service definitions live in files with a .service extension under /etc/systemd/system/. Create /etc/systemd/system/gameserver.service:
[Unit]
Description=Game Server
After=network.target
[Service]
Type=simple
User=gameserver
Group=gameserver
WorkingDirectory=/opt/game
ExecStart=/opt/game/start
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
What the sections mean:
- [Unit] —
After=network.targetmakes sure the service doesn't start before networking is up. - Type=simple — The right choice when the process runs in the foreground (most game servers do). If the process daemonizes itself, use
Type=forking. - WorkingDirectory — Some game cores look for config files relative to the working directory, so always set this.
- ExecStart — The full command to run. Always use an absolute path; systemd doesn't assume a
PATH. - Restart=on-failure — Restarts the process if it exits with a non-zero code.
RestartSec=5waits 5 seconds before restarting. - LimitNOFILE — Raises the open file/socket limit for handling many player connections.
Enabling and starting the service
After saving the file, tell systemd to read the new definition, then enable the service to start at boot and run it:
sudo systemctl daemon-reload
sudo systemctl enable gameserver
sudo systemctl start gameserver
Check the status:
sudo systemctl status gameserver
If you see active (running) in the output, the service is up. The enable command hooks the service into multi-user.target so the game comes back every time the server boots. To do both in one command, use systemctl enable --now gameserver.
Watching logs with journalctl
systemd collects the process's standard output and error in journald, so you don't have to manage a separate log file:
# Show recent logs
sudo journalctl -u gameserver
# Follow live (like tail)
sudo journalctl -u gameserver -f
# Only today's logs
sudo journalctl -u gameserver --since today
If your game core writes its own log files, those stay where they are; journald only captures what's printed to the console. To keep the disk from filling up, consider setting a limit like SystemMaxUse=500M in /etc/systemd/journald.conf.
Graceful shutdown and common mistakes
Many game servers need a soft shutdown to save player data when stopping. By default systemd sends SIGTERM; ideally your process catches it and exits cleanly. Adjust the wait window if needed:
[Service]
TimeoutStopSec=30
KillSignal=SIGINT
The most common issues: a wrong path (ExecStart must be absolute), a permission error (the binary must belong to the gameserver user and be executable), and a wrong Type (using Type=simple for a self-forking process makes the service look like it's constantly "restarting"). Don't forget to run sudo systemctl daemon-reload after every change.
Frequently Asked Questions
What should I do after editing the service?
Every time you change the .service file, run sudo systemctl daemon-reload first, then sudo systemctl restart gameserver. Otherwise systemd keeps using the old definition.
Can I run multiple game instances?
Yes. Write the same template as a template unit named gameserver@.service and start instances like systemctl start gameserver@1, gameserver@2. Each instance gets its own port or config via the %i variable.
Why does the process keep restarting?
Usually it means the binary exits immediately. Read the real error message with journalctl -u gameserver -f; it's often caused by a missing config file, an unavailable port, or a wrong working directory.
Want to harden your server infrastructure? If you need help with game server setup, systemd services, or Linux administration, get in touch with me.