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.