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.11 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.
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.
| Spec | Value | Notes |
|---|---|---|
| Provider | Hostinger | KVM 4 plan |
| vCPU | 4 | Used for: Paper main thread, Geyser, ViaVersion, async chunk gen / I/O |
| RAM | 16 GB | 8 GB JVM heap, ~2 GB OS + Geyser/Floodgate overhead, 2 GB swap cushion |
| Disk | 200 GB NVMe | World, backups, plugin source all fit comfortably |
| Network | Public IPv4 | play.aowmc.com is the public-facing address for players |
| OS | Ubuntu | Long-lived LTS, kept patched with unattended-upgrades |
2OS & runtime
Ubuntu on the host. Standard package layout, systemd as PID 1, ufw for firewall,
journalctl for logs. Everything Minecraft-related lives under a dedicated service directory so the
rest of the system stays vanilla.
The JVM is Temurin / OpenJDK 21 — the current LTS line. Paper 1.21.11 targets Java 21, and all six in-house packs are built with Java 21 release bytecode.
3Server software
The server jar is PaperMC 1.21.11. 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.
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.11.jar nogui
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.
$MC_HOME to stand in for the server’s install
directory. The real absolute path is kept off the public site for security; operators substitute their own.
[Unit]
Description=AoW SMP - PaperMC 1.21.11
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=$MC_HOME
ExecStart=$MC_HOME/start.sh
Restart=on-failure
RestartSec=10
StandardInput=file:$MC_HOME/console.in
StandardOutput=journal
StandardError=journal
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
The server’s STDIN is wired to a named pipe (FIFO) at
$MC_HOME/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
$MC_HOME/cmd.sh "say Server reboot in 5 minutes"
$MC_HOME/cmd.sh "lp user .Steve parent add member"
$MC_HOME/cmd.sh "save-all"
# 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
kill -9 Paper. A clean SIGTERM via systemctl stop
lets Paper flush the world to disk. Hard-kills risk chunk corruption.
6Network
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.
| Hostname | Type | Target | Proxy | Purpose |
|---|---|---|---|---|
play.aowmc.com | A | VPS public IP | DNS-only | Java + Bedrock connect address |
aowmc.com | A | VPS1 | Proxied | Apex; public website |
map.aowmc.com | A | VPS public IP | DNS-only | Public squaremap endpoint |
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.
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.11.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)
| Location | Owner | Purpose |
|---|---|---|
| Service directory | minecraft | Live runtime; what systemd executes |
| Plugins directory | minecraft | Source for the 6 in-house packs; built in-place with Maven |
| Backup directory | root | World 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.
9Monitoring
Three layers of visibility, in order of how often they get used:
/tps + /mspt tell you instantly if the main thread is stressed. The TAB plugin shows ping & TPS in the tablist.map.aowmc.com; internal map service is not exposed.# 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
Live world snapshots are taken automatically every night at 04:00 by
$MC_HOME/ops/backup-live-smp.sh. The script cleanly pauses disk writes
(save-off), forces a full save (save-all), creates a timestamped
tar.gz under $MC_HOME/backups/auto/, then re-enables saves
(save-on). Logs, caches, libraries, and crash reports are excluded, so each
archive is roughly world-only. The last 7 nightly snapshots are kept in a
rolling window.
# Run the same script by hand (as root or minecraft)
$MC_HOME/ops/backup-live-smp.sh
# List recent automatic snapshots
ls -lht $MC_HOME/backups/auto/ | head -8
$MC_HOME/world*, start the service. The world layout is forward-compatible
within the 1.21.x line.
11Source control & dev standards
The six in-house packs live in a single Git repository at $MC_HOME/plugins.
This closes a long-standing foundation gap: plugin source is now version-controlled with
tracked docs, tooling, and build scripts.
docs/PDC-KEYS.md— every persistent data container namespace/key and its owner.docs/MIGRATION-MANIFEST.md— where every legacy plugin moved or retired.tools/validate-descriptors.py— cross-plugin command/permission duplicate checker.tools/server-health-check.py— SSH-based live-server smoke test.
12Discord bridge
A Node.js 18 Discord bot runs as a dedicated systemd service and is wired into AoWInfra so the server can post events (first LEGEND/MYTHIC/CATACLYSM kills, Legend Ledger milestones, etc.) to Discord. It is configured with a bot token and channel/guild IDs kept in server-side config, never on the public site.
13The history of this box
VPS3 has worn three hats. Knowing this helps explain a few odd paths and the
archive/ folder under backups:
- Velocity network. Originally a 4-server Java setup — Velocity proxy fronting a lobby, factions, and kit-PvP. Stopped and disabled when scope got refocused.
- 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.
- 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.
14Quick reference
| Question | Answer |
|---|---|
| Where does the server live? | $MC_HOME/ 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.11 |
| Heap? | Fixed 8 GB (-Xms8G -Xmx8G) with Aikar flags + 2 GB swap cushion |
| How to send a console command? | $MC_HOME/cmd.sh "<command>" (writes to FIFO) |
| How to tail logs? | journalctl -u minecraft -f |
| Where are backups? | $MC_HOME/backups/auto/ (7-night rolling) |
| Where is pack source? | $MC_HOME/plugins/<Pack>/ |
15What’s next
See the Roadmap for the full list.