Host a Minecraft Java server on a VPS
From cold VPS to a Minecraft server your friends can join, with proper systemd, automated backups, and tuned JVM flags.

Minecraft is the most-searched self-hosting topic year after year, and most guides leave you running java -jar in a screen session like it's 2009. Here's the modern way: systemd, sensible JVM flags, and a backup loop you'll actually trust.
Plan size
Vanilla Java edition is comfortable on:
- 2-4 players: 2 vCPU, 4 GB RAM
- 5-15 players: 4 vCPU, 8 GB RAM
- 15-30 players: 6+ vCPU, 16 GB RAM, faster disk
Mods (Forge, Fabric, NeoForge) roughly double the RAM requirement. World size and chunk-loading habits matter more than player count past about 15.
Install Java
Recent Minecraft versions need Java 21:
sudo apt update
sudo apt install -y openjdk-21-jre-headless
java -version
Create a service user and directory
sudo useradd -r -m -d /srv/minecraft -s /usr/sbin/nologin minecraft
sudo -u minecraft mkdir -p /srv/minecraft/server
Drop the server JAR
For vanilla, remember there's no browser on a headless VPS, so grab the link on your local machine: open the official server download page (minecraft.net/download/server), copy the server.jar link, then pull it straight into place:
sudo apt install -y curl
sudo -u minecraft curl -o /srv/minecraft/server/server.jar '<paste the URL you copied>'
For PaperMC (faster, recommended for >5 players), there is no static "latest" URL: the download API serves numbered builds, so you ask it for the newest build of your version and it hands back the exact URL to fetch:
sudo apt install -y curl jq
PAPER_URL=$(curl -s https://fill.papermc.io/v3/projects/paper/versions/1.21.4/builds/latest \
| jq -r '.downloads["server:default"].url')
sudo -u minecraft curl -o /srv/minecraft/server/server.jar "$PAPER_URL"
Swap 1.21.4 for whichever version you want; the same commands work for any release. If the API shape ever changes again (it has before), the PaperMC downloads page always shows the current URL to copy by hand.
Accept the EULA:
echo "eula=true" | sudo -u minecraft tee /srv/minecraft/server/eula.txt
systemd unit
Minecraft is administered by typing commands into its console, and a plain systemd unit gives you no way to reach that console. A small socket unit fixes it: systemd creates a named pipe, connects it to the server's stdin, and anything you write to the pipe arrives as a console command. The backup script later in this guide depends on it, so set it up now. Create /etc/systemd/system/minecraft.socket:
[Unit]
PartOf=minecraft.service
[Socket]
ListenFIFO=/run/minecraft.stdin
SocketMode=0600
RemoveOnStop=true
SocketMode=0600 matters: anything written to that pipe runs as a server command, op and stop included, so only root should be able to write to it.
Size the heap before you copy the unit below. The rule: -Xmx is total RAM minus 1-2 GB for the OS, so the 4 GB entry plan gets -Xms2G -Xmx2G and an 8 GB plan gets -Xms6G -Xmx6G. Do not set it at or near the full RAM of the VPS: the flags include -XX:+AlwaysPreTouch, which commits the entire heap the moment the service starts, so an oversized heap gets the Java process OOM-killed or leaves the whole VPS unresponsive the first time you start it. The unit below uses 2 GB, sized for the 4 GB entry plan from the table up top.
Now create /etc/systemd/system/minecraft.service:
[Unit]
Description=Minecraft Server
Requires=minecraft.socket
After=network-online.target minecraft.socket
Wants=network-online.target
[Service]
User=minecraft
WorkingDirectory=/srv/minecraft/server
Sockets=minecraft.socket
StandardInput=socket
StandardOutput=journal
StandardError=journal
ExecStart=/usr/bin/java -Xms2G -Xmx2G \
-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC \
-XX:+AlwaysPreTouch -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 server.jar nogui
Restart=on-failure
RestartSec=10
SuccessExitStatus=0 1
[Install]
WantedBy=multi-user.target
The G1GC flags are Aikar's well-known set, which most server admins consider the baseline.
sudo systemctl daemon-reload
sudo systemctl enable --now minecraft
sudo journalctl -u minecraft -f
Once the log shows the server is done starting, prove the console pipe works:
echo "say Console pipe is wired up" | sudo tee /run/minecraft.stdin > /dev/null
sudo journalctl -u minecraft -n 3
You should see the say message echoed in the log. That pipe is how the backup script talks to the server.
Open the firewall
A fresh Ubuntu VPS usually ships with UFW installed but inactive, and some images skip it entirely, so install it and check where you stand:
sudo apt install -y ufw
sudo ufw status
If the status says inactive, the allow rule below won't do anything until you enable UFW, and enabling it without an SSH rule in place cuts off your own connection. Locking yourself out is a real risk; the GetVPS console is the back door, but it's awkward. So allow SSH first (swap OpenSSH for your custom port if you've moved it), then Minecraft, then enable:
sudo ufw allow OpenSSH
sudo ufw allow 25565/tcp
sudo ufw enable
Keep your current session open and test a fresh SSH connection from a second terminal before logging out. If UFW was already active, the two allow rules are all you need.
If you have a domain, set up an _minecraft._tcp SRV record so players can connect with just mc.example.com.
Backups that actually run
The obvious script, a bare tar of the world directories, is a trap. Minecraft writes region files continuously, so an archive taken while the server is running can capture files mid-write, and you discover it's corrupt on the day you need to restore it. The fix is to pause autosaving and flush everything to disk first, take the tar, then switch saving back on, all through the console pipe you set up earlier.
Be careful with one thing here: save-off stays in effect until something turns it back on, and a server left in that state loses progress on its next crash. The trap line in the script re-enables saving even if the tar fails partway through, so don't remove it, and if you ever run these console commands by hand, don't forget the matching save-on.
Two more things before you copy the script. The last line deletes every archive older than 14 days, permanently: that's the retention policy, so adjust -mtime +14 if two weeks of history isn't enough, because once the nightly run prunes a backup there is no undo. And these archives land in /srv/minecraft/backups, on the same disk as the world they protect, so a dead disk takes the server and every restore point with it. Sync $DEST somewhere off the VPS (rsync to another machine, rclone to object storage) if the world matters to you.
Create /usr/local/bin/mc-backup:
#!/bin/bash
set -euo pipefail
DEST=/srv/minecraft/backups
CONSOLE=/run/minecraft.stdin
mkdir -p "$DEST"
DATE=$(date +%Y%m%d-%H%M)
if systemctl is-active --quiet minecraft; then
echo "save-off" > "$CONSOLE"
trap 'echo "save-on" > "$CONSOLE"' EXIT
echo "save-all flush" > "$CONSOLE"
sleep 15
fi
tar czf "$DEST/world-$DATE.tar.gz" -C /srv/minecraft/server world world_nether world_the_end
find "$DEST" -name 'world-*.tar.gz' -mtime +14 -delete
The sleep gives the flush time to finish writing; raise it if your world is big. Paper splits dimensions into world, world_nether, and world_the_end; vanilla keeps everything inside world, so drop the other two names from the tar line if you're not on Paper. And if you'd rather not touch the console at all, stopping the service before the tar and starting it again after works too, at the cost of kicking everyone off every night.
sudo chmod +x /usr/local/bin/mc-backup
sudo crontab -e
# add: 0 4 * * * /usr/local/bin/mc-backup
Performance tips
- Use PaperMC over vanilla. Faster TPS, more config knobs.
- Set
view-distance=8inserver.properties. Default 10 is wasteful for most plans. - Run mcMMO or other CPU-heavy plugins on a separate VPS via BungeeCord if you outgrow one box.
That's a Minecraft server you can leave running. Backups every night, restarts on crash, no screen sessions to forget.
Continue reading