How to Run a Minecraft Server with systemd

Run a self-hosted server as a service that restarts on crash, sends a clean shutdown, and starts on boot: a systemd unit with safe stop, watchdog and logging.

Written by
Mihail Griniuc
Last updated
Checked against
Vanilla 1.21.11 and Paper 1.21.11 on Ubuntu and Debian
Reading time
3 min

Short answer

Create a [email protected] unit that runs the JAR with the right heap, set Restart=on-failure and a stop timeout longer than the world save, enable it with systemctl enable --now, and use mc-server@world to manage instances. Stop it with the stop command, never with kill.

A server started from an SSH session dies when the session ends, restarts by hand after every crash, and is killed without warning when the machine reboots. systemd solves all three, and it adds the two things a Minecraft server specifically needs: a clean stop that gives the world time to save, and an exit code that distinguishes a crash from a deliberate shutdown.

This guide builds a template unit so one file manages any number of servers on one machine, which is how you run a survival world and an event arena on the same box without two sets of scripts.

Before you start

  • A Linux machine with systemd (Ubuntu, Debian, Fedora and Arch all ship it)
  • Root access, or an account with sudo
  • The server JAR already downloaded and tested by hand

1. Set up the user and the directories

Run the server as a dedicated unprivileged user rather than as root. It is one extra account, and it means a plugin with a bug or a dependency you do not control cannot own the machine.

Give that user ownership of the server directory and nothing else. Keep the JAR, the world and the configuration inside it, and nothing writable outside it. If you later add a second instance, it gets its own directory under the same parent with the same owner.

shell, as root
sudo adduser --system --group --home /opt/mc minecraft
sudo mkdir -p /opt/mc/world
sudo chown -R minecraft:minecraft /opt/mc
sudo -u minecraft cp server.jar /opt/mc/world/server.jar

2. Write the unit template

The @ in the filename is what makes this a template: [email protected] runs the instance named world, [email protected] runs another. One file, any number of servers.

Three parts matter. Restart=on-failure with a delay stops a crash loop from spinning. TimeoutStopSec has to outlast a world save, because systemd sends SIGTERM, waits, then sends SIGKILL; a server killed mid-save can lose chunks. And SuccessExitStatus tells systemd that the server's own exit code 1 on a deliberate stop is not a failure.

/etc/systemd/system/[email protected]
[Unit]
Description=Minecraft server %i
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/opt/mc/%i
ExecStart=/usr/bin/java -Xms4G -Xmx4G -XX:+UseG1GC -jar server.jar nogui
SuccessExitStatus=0 143
Restart=on-failure
RestartSec=30
TimeoutStopSec=120

# Slow the JVM down politely on stop, so it flushes and saves the world
KillSignal=SIGTERM

StandardInput=pipe
StandardOutput=journal+info

[Install]
WantedBy=multi-user.target

3. Teach systemd to send commands, not just signals

Console commands typed by an administrator need a way into the process. A file that systemd forwards into the process standard input is the usual answer: a process substitution on the command line, writing whatever appears in the file into the server.

With that in place, echo into the file becomes a console command, and stop becomes an orderly shutdown that saves the world before the process exits.

the service file, replacing StandardInput
StandardInput=pipe
ExecStart=/bin/bash -c 'cat /dev/null | /usr/bin/java -Xms4G -Xmx4G -jar server.jar nogui < <(tail -F /run/mc-%i.cmd)'
shell
sudo systemctl daemon-reload
sudo systemctl enable --now [email protected]

# send a console command
echo "say server restarting" | sudo tee -a /run/mc-world.cmd

# stop cleanly
echo "stop" | sudo tee -a /run/mc-world.cmd
sudo systemctl status [email protected]

4. Set the heap to what the machine has

-Xms and -Xmx in ExecStart decide how much memory the server may use, and they must be equal so the heap never resizes while players are on. Do not set -Xmx to the whole of the machine's RAM: the JVM and the operating system need the rest, and taking it all gets the process killed by the kernel instead.

To run more than one server on one machine, divide the memory between them and leave headroom for the system. The JVM flags guide covers the flags themselves and the sizing arithmetic.

memory arithmetic on a 16 GB machine
16 GB total
  - 2 GB for the OS and the JVM overhead
  - 6 GB world server  (-Xms6G -Xmx6G)
  - 6 GB event server  (-Xms6G -Xmx6G)
  ~ 2 GB unallocated

5. Operate it: status, logs, restart

Everything you used to do by hand is now a subcommand: status for the state, journalctl -u mc-server@world -f for the live log, restart for a deliberate restart. Logs go to the journal, so rotation, searching and disk limits are handled for you.

Accept the EULA inside the instance directory once, by hand, before the first start, because that first run has no console to type into.

shell, once per instance, before the first start
sudo -u minecraft bash -c 'cd /opt/mc/world && echo "eula=true" > eula.txt'
shell
sudo systemctl status mc-server@world
sudo journalctl -u mc-server@world -f
sudo systemctl restart mc-server@world
sudo systemctl disable --now mc-server@world

Good to know

  • Never stop the server with kill or kill -9. A signal systemd sends is fine because the service file gives the JVM time; an out-of-band kill is how worlds get corrupted.
  • The unit file runs the JAR named server.jar in the instance directory. Whatever you installed there, vanilla or Paper, runs the same way.
  • Put the world backup in the same schedule whether the server is a service or not. A machine that reboots unexpectedly is exactly when you discover your last backup was never tested.
  • On a hosting panel you do not need any of this: the platform runs the server as a service already, which is the reason PufferHost works without a terminal.

Common questions

Do I still need screen or tmux with systemd?

No. That was the workaround before service managers: run the server in a detached terminal so closing SSH did not kill it. A systemd service survives the session, restarts on crash and starts on boot on its own.

Why does my server take a long time to stop?

Because it is saving the world, which is what you want. systemd sends SIGTERM, waits TimeoutStopSec, then kills. If the wait ends in SIGKILL, the server did not get to save: raise TimeoutStopSec and check how long a save actually takes.

How do I run two servers on one machine?

Enable the same template twice with different instance names: mc-server@world and mc-server@event. Each instance is its own directory with its own JAR, world and configuration, and you split the machine's memory between them in ExecStart.

Where do the server logs go?

Into the systemd journal, because the unit sends standard output and error there. journalctl -u mc-server@world -f follows it live, and rotation and size limits are the journal's job rather than yours.

Sources

Official documentation this guide was checked against.

Written and maintained by Mihail Griniuc. Last reviewed against Vanilla 1.21.11 and Paper 1.21.11 on Ubuntu and Debian. Spotted a mistake or something out of date? Email [email protected].

Revision history
  • Rewritten as a template unit with a safe stop timeout, console input, and the journal replacing ad-hoc log files.

Rather not run this on your own machine? PufferHost gives every player a free Minecraft server with 3 GB of RAM and 10 slots. Create a free server.