The Minecraft server on my home server, a 2009 laptop with 4GB of RAM. Two players, me and a friend, reachable over the tailnet only.
This repo is the server folder itself (~/mc). Only the service file,
the backup script and the setup live in git. The world, the jars and
anything with secrets in it stay out (see .gitignore).
The rest of the box (hardware, firewall, SSH, Pi-hole, the backup runner) is in homeserver-barebasics.
- Paper 26.2 on Java 25 (OpenJDK)
- Runs as a systemd service, so it starts on boot and restarts if it crashes
- Java runs with a fixed heap,
-Xms1500M -Xmx1500M, and the serial garbage collector (-XX:+UseSerialGC), so it can't eat all the RAM - Stopping the service sends SIGINT, so the world saves before it shuts down. Java then exits with code 130, so the service counts 0, 1 and 130 as a clean stop
- One plugin: Chunky (
Chunky-Bukkit-1.5.3.jar), for pre-generating terrain online-mode=false, because we play on cracked launchers. That means the server can't check who anyone is. Tailnet only is what keeps everyone else out- World moved over from Aternos. Self-hosted since I didn't want to keep muting their ads, no wait for the server to start, full control of the data, and it's a good use for my old laptop
| Port | Who can reach it | RAM used |
|---|---|---|
| 25565 | Tailnet only | 2.2 GB |
RAM measured with systemd-cgtop -m.
Needs the base setup from homeserver-barebasics done first, including the backup runner (step 8).
sudo apt install -y openjdk-25-jre-headless unzip
git clone git@github.com:<you>/mc-server.git ~/mc
cd ~/mc
Download Paper from fill.papermc.io (v3 API) and save it as
paper.jar. Check the sha256 against the one on the site:
sha256sum paper.jar
echo "eula=true" > eula.txt
If you're bringing a world over, unzip it into ~/mc and delete
world/session.lock.
Run it once by hand to make sure it starts, then stop it and install:
java -Xms1500M -Xmx1500M -jar paper.jar nogui
./install.sh
sudo systemctl start minecraft
systemctl status minecraft
install.sh does three things:
- Installs
minecraft.servicewith your username filled in - Opens port 25565 on the tailnet only
- Links
mc-backup.shinto~/.config/backup.d/, so the nightly backup picks it up
- Every night at 04:00,
backup-runfrom the base repo runsmc-backup.sh, which zips the world - Local copies are kept for 14 days
- rclone copies them to Google Drive, where they're kept for 30 days
- I tested a restore on day one: unpacked a backup and checked the files were all there
One weakness: the backup runs while the server is live, so it could catch a file halfway through being saved. Fixing that properly needs RCON so the script can pause saving first.
Test a restore before you trust it:
mkdir -p ~/restore-test && cd ~/restore-test
tar -xzf ~/backups/world_<date>.tar.gz
ls world/ && ls -lh world/level.dat
cd ~ && rm -rf ~/restore-test
Minecraft crashed three times. I guessed it ran out of memory and made the heap smaller. It crashed again anyway.
The crash reports showed 0.8 to 1.7GB of RAM free each time, and the system log had no out-of-memory kills. So it wasn't memory. The real cause is still unknown. On old hardware, bad RAM or heat are the usual suspects. One crash also broke a chunk of the world, which Minecraft then dropped.
Later I switched to the fixed -Xms1500M -Xmx1500M heap and the serial
garbage collector.
- Backups aren't crash-safe (see Backups)
- The crashes are still unexplained
- Anyone who can reach the port can join under any name, including
mine (
online-mode=false)
systemctl status minecraft # is it running?
journalctl -u minecraft -f # live server log
sudo systemctl stop minecraft # saves the world, then stops
ls -lh ~/backups # latest backups
git status # should be empty, if not something changedminecraft.service systemd unit (REPLACE_USER gets filled in)
mc-backup.sh zips the world, keeps 14 days, copies to Drive
install.sh installs the unit, firewall rule and backup
server.properties.example my settings, secrets left blank