Introduction

Running a Minecraft server is relatively easy - just run the server.jar file using something like:

$ java -Xmx1G -Xms1G -jar server.jar --nogui

And it works.

But what about running it as a background service/daemon, automatically on boot, with automatic backups?

We will use systemd for this - a modern service manager that many Linux distributions have adopted as the default. It provides many useful features that we will take advantage of to simplify things and secure our server, including PID guessing and many isolation/namespacing security features.

This was tested to work on Arch Linux (current) and Fedora Server 40.

Setup

For this guide, lets assume we want to run a Minecraft server named “survival”. We will use the /srv/minecraft directory, with /srv/minecraft/survival storing the data for the survival Minecraft server. Backups will be stored in /srv/minecraft/backups.

Install the required software - tmux and Java 21+. The commands will differ depending on distribution, here are some common examples:

Debian (13+) / Ubuntu

# apt install tmux openjdk-21-jre-headless

Note: Debian 12 Bookworm doesn’t have Java 21 available in the official repositories. You can use the Adoptium repository for Temurin Java 21+ builds.

Fedora / Enterprise Linux

# dnf install tmux java-21-openjdk-headless

Arch Linux

# pacman -S tmux jre21-openjdk-headless

Note that Java 23+ currently has issues with Fabric, as of writing this post. It is thus recommended to stick with Java 21 (the current LTS) for now.

Create a new unprivileged user for each Minecraft server to run under.

# useradd --comment "Minecraft Survival" --home-dir /srv/minecraft/survival --create-home minecraft-survival

Create a backups directory:

# mkdir /srv/minecraft/backups

Now we will set up each Minecraft server user. The steps below will use the “survival” server as an example. Repeat the steps again for any other Minecraft server users you need to set up. Switch to the minecraft-survival user:

# su - minecraft-survival

Download the server.jar file:

$ wget <server.jar download link copied from minecraft.net/download/server goes here>

Run the server to generate eula.txt and server.properties:

$ java -Xmx1G -Xms1G -jar server.jar --nogui

The server will terminate after setting up.

Edit eula.txt to accept the EULA (if you actually do accept it, if you don’t, you shouldn’t proceed):

eula=true

Edit server.properties to choose a unique port (to not conflict with other Minecraft servers for example). Use a different port for each Minecraft server that is listening on the same IP address:

server-port=25566

You might want to set sync-chunk-writes=false to greatly speed up saving and reduce stutter on slower disks. This does come with a risk of losing a little bit of progress if the server crashes or power goes out.

Set any other settings you want, start the server once more, and then stop it (by typing stop and pressing Enter/Return).

systemd

Time for the systemd unit files!

First up, the main Minecraft server service file.

/etc/systemd/system/minecraft@.service

[Unit]
Description=Minecraft Server for world %i
After=network.target
RequiresMountsFor=/srv/minecraft/%i

[Service]
Type=forking
WorkingDirectory=/srv/minecraft/%i
User=minecraft-%i
Group=minecraft-%i

ProtectProc=invisible
NoNewPrivileges=yes
UMask=0027
TimerSlackNSec=5ms
ProtectSystem=strict
ProtectHome=yes
NoExecPaths=/
InaccessiblePaths=-/boot -/efi -/home -/mnt -/media -/opt -/root -/var
ExecPaths=/usr/bin -/usr/lib -/usr/lib64 -/usr/libexec
ReadWritePaths=/srv/minecraft/%i
PrivateTmp=yes
PrivateDevices=yes
PrivateIPC=yes
PrivateUsers=yes
ProtectHostname=yes
ProtectClock=yes
ProtectKernelTunables=yes
MountAPIVFS=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
#RemoveIPC=yes
PrivateMounts=yes
SystemCallFilter=@system-service
#SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
EnvironmentFile=-/etc/minecraft/%i.conf

ExecStart=/usr/bin/tmux new-session -d -s minecraft-%i '/usr/bin/java -Xmx${JVM_MEMORY:-2G} -Xms${JVM_MEMORY:-2G} ${JVM_FLAGS:--XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1} -jar ${JAR_FILE:-server.jar} --nogui ${MC_FLAGS:-}'
ExecStop=/usr/bin/tmux send-keys -t minecraft-%i stop ENTER
ExecStop=/usr/bin/tmux wait-for minecraft-%i

TimeoutStopSec=30

[Install]
WantedBy=multi-user.target

Next, the backup oneshot service.

/etc/systemd/system/minecraft-backup@.service

Description=Backup Minecraft server for world %i
After=network.target
RequiresMountsFor=/srv/minecraft/%i /srv/minecraft/backups

[Service]
Type=oneshot
WorkingDirectory=/srv/minecraft/backups

ProtectProc=invisible
NoNewPrivileges=yes
UMask=0027
ProtectSystem=strict
ProtectHome=yes
NoExecPaths=/
InaccessiblePaths=-/boot -/efi -/home -/mnt -/media -/opt -/root -/var
ExecPaths=/usr/bin -/usr/lib -/usr/lib64 -/usr/libexec
ReadWritePaths=/srv/minecraft/backups
PrivateTmp=yes
PrivateDevices=yes
PrivateIPC=yes
ProtectHostname=yes
ProtectClock=yes
ProtectKernelTunables=yes
MountAPIVFS=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
PrivateNetwork=yes
RestrictAddressFamilies=AF_UNIX
RestrictNamespaces=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
#RemoveIPC=yes
PrivateMounts=yes
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
#SystemCallErrorNumber=EPERM
SystemCallArchitectures=native

ExecStartPre=-/usr/bin/mv /srv/minecraft/backups/minecraft-%i-auto-backup-latest.tar.gz /srv/minecraft/backups/minecraft-%i-auto-backup-old.tar.gz
ExecStart=/usr/bin/tar -czf /srv/minecraft/backups/minecraft-%i-auto-backup-latest.tar.gz /srv/minecraft/%i

[Install]
WantedBy=multi-user.target

Then, the backup timer for automatic backups.

/etc/systemd/system/minecraft-backup@.timer

[Unit]
Description=Weekly backup of Minecraft server for world %i

[Timer]
OnCalendar=weekly
RandomizedDelaySec=5m

[Install]
WantedBy=timers.target

A set of optimal JVM arguments (Aikar’s flags) are used by default.

2 GB of RAM is used by default.

All reasonable systemd security features are enabled to harden the service as much as reasonably possible. Adjustments to ExecPaths= and InaccessiblePaths= might be necessary on some systems (tested to work on Arch Linux and Fedora Linux 40).

TimerSlackNSec=5ms is a reasonable amount of timer slack to reduce the unnecessarily high amount of processor wakeups caused by the Minecraft server, reducing system power consumption and increasing performance, especially in virtualized environments.

The backup timer is set to run weekly by default. Adjust to your preference. The RandomizedDelaySec=5m avoids a case where all the backups for multiple servers run at the same time, thereby reducing contention.

The JVM memory, JVM flags, jar file, and Minecraft arguments can be adjusted through a configuration file:

/etc/minecraft/survival.conf

JVM_MEMORY=2G
JVM_FLAGS=--XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1
JAR_FILE=server.jar
MC_FLAGS=

To enable and start the service, run:

# systemctl enable --now minecraft@survival

Check the status with:

# systemctl status minecraft@survival

To run a backup immediately:

# systemctl start minecraft-backup@survival

To enable and start the timer to run backups automatically:

# systemctl enable --now minecraft-backup@survival.timer

If you need to attach to the console, you need to attach to the tmux session. Due to PrivateTmp=yes, access to the tmux session is restricted. Adjust the paths below for what is actually present on your system.

# cd /tmp/systemd-private-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-minecraft@survival.service-XXXXXX/tmp/tmux-XXXXX
# tmux -S default attach

cd is required to avoid tmux complaining about the path being too long.

Use Ctrl+B, then press D to detach from the session. Make sure not to leave any half-typed commands in the console before detaching so that the stop function can run correctly.

Conclusion

You should now have reasonably secure Minecraft server services running using systemd with automatic backups!

Enjoy.