> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Minecraft Java Server on Linux — Paper, systemd, and Memory Tuning
- URL: https://blog.sudowl.com/minecraft-java-server-linux/
- Published: 2026-04-19T10:00:00.000Z
- Updated: 2026-10-11T10:16:27.000Z
- Description: Run a Paper Minecraft server on Linux the clean way: own user, Java, systemd unit, Aikar's flags, sane RAM sizing, RCON console, firewall and safe backups.
- Author: Lukas
- Tags: Linux, Minecraft, Gameserver

No panel, no `screen -S mc` in root's home directory, no 16 GB heap "because more is better". This is how I set up a plain Paper server on a Linux box: own user, systemd, flags that make sense, and a console you can actually reach.

**TL;DR:**

```bash
sudo apt install temurin-21-jre      # Adoptium repo, see below
sudo adduser --system --group --home /opt/minecraft minecraft
cd /opt/minecraft
sudo -u minecraft curl -o paper.jar "<paper download url>"
sudo -u minecraft java -Xms4G -Xmx4G -jar paper.jar --nogui   # generates files, stops at EULA
echo "eula=true" | sudo -u minecraft tee eula.txt
# drop in the systemd unit below, then:
sudo systemctl enable --now minecraft
sudo ufw allow 25565/tcp

```

Why Paper and not vanilla or Spigot is its own post: [Vanilla vs Spigot vs Paper](/minecraft-server-software-comparison/). Short version: Paper runs Bukkit/Spigot plugins, is faster, and is what every proxy setup on this blog assumes as the backend.

## Java

Minecraft 1.20.5 and newer need **Java 21**. Older Java fails with an `UnsupportedClassVersionError` the moment you start the jar. I use Eclipse Temurin from the Adoptium repo because the distro's default JDK is either older or moves on its own schedule:

```bash
sudo apt install -y wget apt-transport-https gpg
wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public \
  | sudo gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg
echo "deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb $(. /etc/os-release; echo $VERSION_CODENAME) main" \
  | sudo tee /etc/apt/sources.list.d/adoptium.list
sudo apt update && sudo apt install -y temurin-21-jre
java -version

```

Ubuntu 24.04 and Debian 13 also have `openjdk-21-jre-headless` in the main repos, which works just as well. Whatever you pick, check what *your* Paper version wants on its download page — Mojang raises the Java floor every few major versions, and the next bump will come.

## Gotcha: several Javas installed

If `java -version` shows 17 even though you installed 21, the alternatives system still points at the old one:

```bash
sudo update-alternatives --config java

```

Or skip the question entirely and put the full path (`/usr/lib/jvm/temurin-21-jre-amd64/bin/java`) into the systemd unit. That's what I do — the server keeps working when someone installs a different JDK for something else.

## User and directory

```bash
sudo adduser --system --group --home /opt/minecraft minecraft

```

System user, no login shell, no password. The server can only write in its own directory. If a plugin ever does something stupid, it does it as `minecraft`, not root. If the box itself isn't set up yet, run through my [Ubuntu initial setup checklist](/ubuntu-server-initial-setup-checklist/) first.

## Get Paper

PaperMC moved their download API (now called Fill, v3) and the old v2 endpoints are being phased out. The safe way: open papermc.io/downloads/paper, copy the link for the version you want, and `curl` it. Scripted:

```bash
VER=1.21.8
URL=$(curl -s -A "my-server-setup (admin@example.com)" \
  "https://fill.papermc.io/v3/projects/paper/versions/$VER/builds/latest" \
  | jq -r '.downloads."server:default".url')
sudo -u minecraft curl -A "my-server-setup (admin@example.com)" -o /opt/minecraft/paper.jar "$URL"

```

The API wants a User-Agent that identifies you — the default curl one can get rejected. Pin the version. "Latest" sounds convenient until a major release lands and half your plugins don't load.

First start generates the files, then stops at the EULA:

```bash
cd /opt/minecraft
sudo -u minecraft java -Xms2G -Xmx2G -jar paper.jar --nogui
echo "eula=true" | sudo -u minecraft tee eula.txt

```

## server.properties — what I actually change

```properties
server-port=25565
motd=My Server
max-players=20
view-distance=8
simulation-distance=6
online-mode=true
white-list=true
enforce-whitelist=true
enable-rcon=true
rcon.port=25575
rcon.password=CHANGE-ME-LONG-RANDOM

```

- **view-distance / simulation-distance** — the biggest performance knobs you have. View distance is what players *see*, simulation distance is what actually *ticks* (mobs, crops, redstone). Lowering simulation distance does more for TPS than any JVM flag.
- **white-list** — on for anything not meant to be public. Bots scan port 25565 constantly. If this server will sit behind a proxy, a few of these change (`online-mode=false`, bind to `127.0.0.1`) — that's covered in the [Velocity setup](/velocity-minecraft-proxy-setup-from-scratch/).

## Memory: how much, and why less is often more

Rules I stick to:

- **Xms = Xmx.** Same value. The JVM doesn't have to grow the heap under load, and with `AlwaysPreTouch` the memory is committed at startup so you know immediately whether it fits.
- **Leave RAM for the OS.** The JVM uses more than the heap — metaspace, thread stacks, direct buffers, plus the page cache your world files live in. On an 8 GB box I'd give the server 5–6 GB, not 8.
- **Don't oversize.** A bigger heap doesn't make an unoptimized server faster. It makes GC pauses rarer but longer. If a small server "needs" 16 GB, there's a chunk loader, an entity farm or a leaking plugin somewhere — fix that.
- **Pre-generate the world.** Chunk generation is the most expensive thing a server does. Chunky (a plugin) pre-generates a radius before players arrive, and it makes a bigger difference than any heap tuning.

## Aikar's flags

These are the G1GC flags the Paper docs still recommend. For heaps up to 12 GB:

```text
-Xms6G -Xmx6G -XX:+AlwaysPreTouch -XX:+DisableExplicitGC -XX:+ParallelRefProcEnabled
-XX:+PerfDisableSharedMem -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC
-XX:G1HeapRegionSize=8M -XX:G1HeapWastePercent=5 -XX:G1MaxNewSizePercent=40
-XX:G1MixedGCCountTarget=4 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1NewSizePercent=30
-XX:G1RSetUpdatingPauseTimePercent=5 -XX:G1ReservePercent=20 -XX:InitiatingHeapOccupancyPercent=15
-XX:MaxGCPauseMillis=200 -XX:MaxTenuringThreshold=1 -XX:SurvivorRatio=32
-Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true

```

Above 12 GB, change five values:

```text
-XX:G1NewSizePercent=40 -XX:G1MaxNewSizePercent=50 -XX:G1HeapRegionSize=16M
-XX:G1ReservePercent=15 -XX:InitiatingHeapOccupancyPercent=20

```

Don't mix and match with flags from random forum posts. These are tuned as a set — the high young-gen percentage only makes sense together with the low tenuring threshold. The same set works for modded servers too.

## systemd unit

`/etc/systemd/system/minecraft.service`:

```ini
[Unit]
Description=Minecraft Paper Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/opt/minecraft
ExecStart=/usr/lib/jvm/temurin-21-jre-amd64/bin/java -Xms6G -Xmx6G \
  -XX:+AlwaysPreTouch -XX:+DisableExplicitGC -XX:+ParallelRefProcEnabled \
  -XX:+PerfDisableSharedMem -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC \
  -XX:G1HeapRegionSize=8M -XX:G1HeapWastePercent=5 -XX:G1MaxNewSizePercent=40 \
  -XX:G1MixedGCCountTarget=4 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1NewSizePercent=30 \
  -XX:G1RSetUpdatingPauseTimePercent=5 -XX:G1ReservePercent=20 \
  -XX:InitiatingHeapOccupancyPercent=15 -XX:MaxGCPauseMillis=200 \
  -XX:MaxTenuringThreshold=1 -XX:SurvivorRatio=32 \
  -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true \
  -jar paper.jar --nogui
Restart=on-failure
RestartSec=15
TimeoutStopSec=120
SuccessExitStatus=0 143
MemoryMax=7G
NoNewPrivileges=true
ProtectSystem=full
PrivateTmp=true

[Install]
WantedBy=multi-user.target

```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now minecraft
sudo journalctl -u minecraft -f

```

Wait for `Done (x.xxxs)! For help, type "help"`.

The lines that matter:

- **`TimeoutStopSec=120`** — on `systemctl stop`, systemd sends SIGTERM and the server saves all worlds before exiting. A big world needs more than the default 90 seconds; if systemd SIGKILLs mid-save, you get corrupted chunks.
- **`SuccessExitStatus=0 143`** — Java exits with 143 on SIGTERM. Without this, every clean stop shows up as "failed".
- **`MemoryMax=7G`** — heap plus overhead. A hard ceiling so a leak kills the server, not the whole box.

More on the options: [systemd service unit files](/systemd-service-unit-file/).

## A console without screen

systemd gives the server no stdin, so you can't type commands into it. RCON fixes that. It's already enabled in `server.properties` above. Use `mcrcon` (grab a release from its GitHub) or any RCON client:

```bash
mcrcon -H 127.0.0.1 -P 25575 -p 'CHANGE-ME-LONG-RANDOM' -t

```

`-t` gives you an interactive terminal. Single commands work too: `mcrcon ... "say restart in 5 minutes"`.

## Gotcha: RCON is plaintext

The RCON protocol is unencrypted and the password goes over the wire as-is. Never open 25575 to the internet. Only talk to it from localhost or over a VPN:

```bash
sudo ufw allow 25565/tcp
sudo ufw deny 25575/tcp

```

Minecraft Java is TCP-only — no UDP rule needed unless you run a Bedrock bridge or voice-chat plugin. More rules: [UFW firewall rules](/ufw-firewall-rules-ubuntu/).

## Backups that don't corrupt

Copying the world folder while the server writes to it gives you a backup that might not load. Pause saving first:

```bash
#!/bin/bash
RCON="mcrcon -H 127.0.0.1 -P 25575 -p CHANGE-ME-LONG-RANDOM"
$RCON "save-off" "save-all flush"
sleep 5
rsync -a --delete /opt/minecraft/ /backup/minecraft/$(date +%F)/
$RCON "save-on"

```

`save-all flush` blocks until everything is on disk. Put it on a timer, and make sure `save-on` runs even if rsync fails — otherwise the server silently stops saving. Full pattern: [rsync backups on Linux](/rsync-backup-linux/).

## Gotcha: TPS is bad and RAM isn't the problem

Before touching flags or memory, run the profiler that's already there:

```text
/spark profiler start

```

Spark ships with Paper these days. Let it run a few minutes during lag, `/spark profiler stop`, open the link. It shows you exactly which entity, plugin or chunk is eating the tick. Nine times out of ten it's an entity pile-up or a plugin, not the JVM.

## Next steps

One server is fine for friends. Once you want a lobby plus survival plus creative, put a proxy in front: [Velocity from scratch](/velocity-minecraft-proxy-setup-from-scratch/). If it should stay friends-only, the whitelist plus a firewall does most of the work.

---

### Related posts

- [PaperMC Download API v3: Why Your "latest" One-Liner Returns 404](https://blog.diengdoh.com/papermc-download-api-v3/?ref=blog.sudowl.com)
- [Migrating from BungeeCord/Waterfall to Velocity — A Practical Walkthrough](https://blog.diengdoh.com/migrate-bungeecord-waterfall-to-velocity/?ref=blog.sudowl.com)
- [Hosting a Forge / NeoForge Modpack Server on Linux — The Pragmatic Guide](https://blog.diengdoh.com/forge-neoforge-modpack-server-linux/?ref=blog.sudowl.com)