How it runs

Hardware, runtime, and ops

Where the server lives, what runs it, and how it stays alive.

AoW SMP runs on a single Hostinger KVM 4 VPS in a clean, opinionated stack: Ubuntu at the base, Temurin Java 21 as the runtime, PaperMC 1.21.8 as the server, and systemd babysitting the process. No Docker, no orchestrator, no panel — just the JVM, a unit file, and a console FIFO. The goal is “boring infrastructure” so the fun parts (gameplay, plugins) get all the attention.

4vCPU
16 GBRAM
200 GBNVMe
8 GBJVM heap
20max players
1VPS, 1 process

1Hardware

The box is a Hostinger KVM 4 instance — full hardware-virtualized, not a shared-kernel container. That matters for Minecraft: the JVM benefits from real CPU pinning, real RAM, and predictable NVMe latency. The server was originally provisioned at 2 vCPU / 8 GB, but we hit translation lag from Geyser + ViaVersion under modest load, so it was upgraded mid-session to the current 4 vCPU / 16 GB tier. Headroom solved it.

SpecValueNotes
ProviderHostingerKVM 4 plan
vCPU4Used for: Paper main thread, Geyser, ViaVersion, async chunk gen / I/O
RAM16 GB8 GB JVM heap, ~2 GB OS + Geyser/Floodgate overhead, 2 GB swap cushion
Disk200 GB NVMeWorld, backups, plugin source all fit comfortably
NetworkPublic IPv4play.aowmc.com is the public-facing address for players
OSUbuntuLong-lived LTS, kept patched with unattended-upgrades
Why a single VPS? 20 max players, one survival world, no minigame network. A Velocity proxy fronting multiple Paper instances would just burn RAM and add a hop. One Paper process is the right shape for this server.

2OS & runtime

Operating system

Ubuntu on the host. Standard package layout, systemd as PID 1, ufw for firewall, journalctl for logs. Everything Minecraft-related lives under /opt/ so the rest of the system stays vanilla.

Java runtime — why 21?

The JVM is Temurin / OpenJDK 21 — the current LTS line. Paper 1.21.8 targets Java 21, and all six in-house packs are built with Java 21 release bytecode.

Distribution
Eclipse Temurin
The free, TCK-certified OpenJDK build. No Oracle license footgun.
Version
21 (LTS)
Current Java LTS. Supported and patched for years.
Why a fixed heap
-Xms = -Xmx
Avoids heap-resize pauses; G1 sizes regions once at startup.

3Server software

The server jar is PaperMC 1.21.8. Paper is a high-performance fork of Spigot with better chunk handling, async chunk I/O, configurable patches for vanilla edge cases, and the most active plugin ecosystem in the 1.21.x line. Crucially for us, Paper is what Geyser, Floodgate, and ViaVersion all target as their first-class platform.

SoftwarePaperMC Game version1.21.8 ModeSurvival DifficultyEasy Max players20 online-modetrue view-distance8 sim-distance6 spawn-protection16

Two Paper config flags need to be off so Floodgate-authenticated Bedrock players don’t get rejected by Mojang’s profile checks:

# paper-global.yml
proxies:
  velocity:
    enabled: false
unsupported-settings:
  allow-headless-pistons: false

# relevant flags
enforce-secure-profile: false
perform-username-validation: false

online-mode stays true — Java players still authenticate against Mojang normally. Floodgate handles Bedrock auth out-of-band via Xbox Live.

4JVM tuning

The JVM is launched with a fixed 8 GB heap and the well-known Aikar GC flags for G1 — these are the de-facto standard for Paper servers and tune G1 for many short-lived allocations (which is exactly what Minecraft does each tick).

java \
  -Xms8G -Xmx8G \
  -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 \
  -Dusing.aikars.flags=https://mcflags.emc.gs \
  -Daikars.new.flags=true \
  -jar paper-1.21.8.jar nogui
-Xms = -Xmx = 8G
Fixed heap
Prevents in-flight resize pauses. Leaves headroom for OS, Geyser threads, and disk cache.
G1 + Aikar flags
Pause-tuned GC
Bounded pauses (~200 ms target) and aggressive young-gen sizing for tick-rate workloads.
2 GB swap
OOM cushion
Not for real paging — just stops a transient spike from invoking the OOM killer.
Why no ZGC? ZGC is great for huge heaps with low pause requirements, but it eats more CPU and our heap is only 8 GB. G1 + Aikar is the sweet spot for Paper at this size.

5Process management

The server runs as a dedicated unprivileged user named minecraft, managed by a single systemd unit. systemd handles boot-on-startup, auto-restart on crash, and log capture into the journal.

/etc/systemd/system/minecraft.service
[Unit]
Description=AoW SMP - PaperMC 1.21.8
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/opt/mcserver
ExecStart=/opt/mcserver/start.sh
Restart=on-failure
RestartSec=10
StandardInput=file:/opt/mcserver/console.in
StandardOutput=journal
StandardError=journal
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
Console FIFO

The server’s STDIN is wired to a named pipe (FIFO) at /opt/mcserver/console.in. To send a command into the running server, you just echo into the pipe. There’s a one-line wrapper:

# Send a command to the live server
/opt/mcserver/cmd.sh "say Server reboot in 5 minutes"
/opt/mcserver/cmd.sh "lp user .Steve parent add member"
/opt/mcserver/cmd.sh "save-all"
Day-to-day ops
# Status
systemctl status minecraft

# Tail live console (Paper stdout)
journalctl -u minecraft -f

# Restart cleanly (Paper handles save-all on SIGTERM)
sudo systemctl restart minecraft

# Stop / start
sudo systemctl stop minecraft
sudo systemctl start minecraft
Never kill -9 Paper. A clean SIGTERM via systemctl stop lets Paper flush the world to disk. Hard-kills risk chunk corruption.

6Network

DNS

play.aowmc.com is a Cloudflare DNS-only A record pointing at the VPS public IP. DNS-only (the grey cloud, not orange) is mandatory — Cloudflare’s proxy only handles HTTP/S, and Minecraft is a raw TCP/UDP protocol. Proxying would break the connection entirely.

HostnameTypeTargetProxyPurpose
play.aowmc.comAVPS public IPDNS-onlyJava + Bedrock connect address
aowmc.comAVPS1ProxiedApex; public website
map.aowmc.comAVPS public IPDNS-onlyPublic squaremap endpoint
Ports
25565/TCPJava 19132/UDPBedrock (Geyser) 22/TCPSSH (UFW: trusted IPs only) Mapsquaremap internal service

UFW is enabled with a deny-by-default policy. Only the Minecraft ports above and SSH are open. map.aowmc.com points to the squaremap public endpoint. Internal map service traffic is not exposed publicly.

Admin access

The VPS is also reachable on a private admin network for SSH and tooling. Players never see this address; all public traffic uses play.aowmc.com and map.aowmc.com.

7Storage layout

Everything Minecraft-related lives under /opt. The world data, pack source code, and backups are deliberately separated so a wipe of one doesn’t touch the others.

/opt
├── mcserver/                   # Runtime: the actual server
│   ├── paper-1.21.8.jar
│   ├── start.sh                # JVM flags + jar launch
│   ├── cmd.sh                  # Wrapper to write into console.in
│   ├── console.in              # FIFO -> server STDIN
│   ├── eula.txt
│   ├── server.properties       # port 25565, max-players 20, MOTD, etc.
│   ├── paper-global.yml        # enforce-secure-profile=false, etc.
│   ├── bukkit.yml  spigot.yml  # Vanilla bukkit/spigot config
│   ├── world/  world_nether/  world_the_end/
│   ├── plugins/
│   │   ├── Geyser-Spigot.jar
│   │   ├── floodgate-spigot.jar
│   │   ├── ViaVersion.jar  ViaBackwards.jar
│   │   ├── EssentialsX.jar  Vault.jar
│   │   ├── GriefPrevention.jar  WorldGuard.jar  WorldEdit.jar
│   │   ├── LuckPerms.jar  CoreProtect.jar
│   │   ├── AuraSkills.jar  AxGraves.jar
│   │   ├── QuickShop-Hikari.jar  GlobalMarketplace.jar
│   │   ├── DecentHolograms.jar  TAB.jar  PlaceholderAPI.jar  Plan.jar
│   │   ├── Chunky.jar  squaremap.jar
│   │   └── AoW*.jar            # 6 in-house packs (~21 third-party; ~27 total)
│   └── logs/                   # Paper rolling logs
│
├── aow-plugins/                # Source: in-house pack code
│   ├── AoWMythic/
│   ├── AoWWorldGen/
│   ├── AoWMobs/
│   ├── AoWQoL/
│   ├── AoWContent/
│   └── AoWInfra/
│       └── (each: pom.xml, src/, target/, config.yml)
│
└── backups/                    # Compressed world snapshots
    ├── world-YYYYMMDD.tar.zst
    └── archive/                # Old Velocity-network worlds (kept for history)
PathOwnerPurpose
/opt/mcserver/minecraftLive runtime; what systemd executes
/opt/aow-plugins/minecraftSource for the 6 in-house packs; built in-place with Maven
/opt/backups/rootWorld snapshots; old Velocity-network and Bedrock worlds archived here

8Resource use

Day-to-day load is well within the box. With a handful of players online, the Paper main thread sits around 20 TPS (the cap) and the JVM uses 3–5 GB of its 8 GB heap. Spare CPU goes to Geyser’s translation workers and ViaVersion’s packet rewrite.

20.0TPS typical
~3–5 GBheap in use
10–25%CPU at rest
~120 GBdisk free
What ate the old 2 vCPU / 8 GB box? Geyser converts Bedrock’s RakNet/UDP into Java protocol packets, and ViaVersion then translates those into native 1.21.8 packets. Each Bedrock player effectively gets two extra worker threads of work. Doubling cores and RAM eliminated the translation hitches.

9Monitoring

Three layers of visibility, in order of how often they get used:

In-game
/tps · /mspt · TAB
Paper’s built-in /tps + /mspt tell you instantly if the main thread is stressed. The TAB plugin shows ping & TPS in the tablist.
Plan
Plan 5.7
Player analytics web UI: playtime, retention, sessions, world activity. Runs in-process inside Paper.
squaremap
squaremap 1.3.12
Live top-down web map of the world. Public endpoint at map.aowmc.com; internal map service is not exposed.
System-level
# Quick health
systemctl status minecraft
journalctl -u minecraft -n 100 --no-pager
htop                     # CPU + memory at a glance
df -h /opt               # Disk free on the world volume

10Backups

World snapshots are written to /opt/backups/ as tar.zst archives (zstd compresses fast and well for region files). Old worlds from the previous incarnations of the box — the Velocity network and the brief native Bedrock period — are archived alongside, so nothing is ever truly gone.

Restoring is just: stop the service, untar over /opt/mcserver/world*, start the service. The world layout hasn’t changed since 1.21, so restores are forward-compatible within the current major.

11The history of this box

VPS3 has worn three hats. Knowing this helps explain a few odd paths and the archive/ folder under backups:

  1. Velocity network. Originally a 4-server Java setup — Velocity proxy fronting a lobby, factions, and kit-PvP. Stopped and disabled when scope got refocused.
  2. Native Bedrock Dedicated Server. Briefly ran the official BDS binary for a Bedrock-only experiment. Stopped — the plugin ecosystem is incompatible with what we wanted.
  3. Paper crossplay SMP (today). Single Paper process, Geyser + Floodgate + ViaVersion for Bedrock support, 6 consolidated in-house packs on top. This is the current shape.

The 2→4 core / 8→16 GB upgrade happened mid-session during the move to the crossplay architecture, once Geyser + ViaVersion made the CPU work obvious.

12Quick reference

QuestionAnswer
Where does the server live?/opt/mcserver/ on a Hostinger KVM 4 VPS
Public address?play.aowmc.com
What runs the JVM?systemd unit minecraft.service as user minecraft
Java version?Temurin OpenJDK 21
Server jar?PaperMC 1.21.8
Heap?Fixed 8 GB (-Xms8G -Xmx8G) with Aikar flags + 2 GB swap cushion
How to send a console command?/opt/mcserver/cmd.sh "<command>" (writes to FIFO)
How to tail logs?journalctl -u minecraft -f
Where are backups?/opt/backups/
Where is pack source?/opt/aow-plugins/<Pack>/

13What’s next

map.aowmc.com
squaremap public URL
Public live map already live; polish reverse-proxy and SSL renewal.
Terralith pre-gen
~30 min on 4 cores
Finish Chunky pre-generation so explorers don’t pay the gen-cost in real time.
Discord-linked verification
MC↔Discord identity
One-time codes from Discord to link in-game accounts and unify moderation.

See the Roadmap for the full list.