A Minecraft Java server that lags is almost never a hardware problem — it's a configuration problem. This guide walks through every lever that matters: Paper over vanilla, Aikar's G1GC flags line by line, view/simulation distance math, paper.yml activation ranges, the spark profiler workflow, and pregeneration with Chunky. By the end your server will hold 20 TPS with meaningful headroom, or you'll know exactly which plugin / mod is eating the tick.

01 // Understanding TPS and MSPT

Before tuning anything, you have to measure. Two numbers matter:

Metric What it means Target
TPS (ticks per second)How many game ticks the server completed in the last second. Minecraft's simulation runs at 20 Hz.20.0 — anything lower means the world is running in slow motion
MSPT (milliseconds per tick)How long each tick actually took to compute. Budget is 50 ms (1000 ms ÷ 20 ticks).<30 ms is healthy, 30–45 ms is stressed, >50 ms means TPS drops

Run /tps on a Paper/Purpur server at any time to see the last 1 min / 5 min / 15 min averages and current MSPT. The three colours mean:

  • Green (20.0) — server is finishing every tick with spare time
  • Yellow (~18–20) — server is struggling intermittently, players may feel micro-stutters
  • Red (<18) — measurable lag: mobs stutter, doors open slowly, hits miss
TPS can lie — watch MSPT

A server averaging 20 TPS can still feel laggy if MSPT spikes periodically (e.g. 55 ms on a chunk save every 45 seconds). Paper's /mspt command shows the last 5, 10, and 60 second MSPT averages plus the peak. A max MSPT of 80+ ms means you have a spike problem even if the TPS number looks fine.

02 // Which Server Fork: Paper vs Purpur vs Pufferfish vs Folia

Vanilla Minecraft is the single worst Java server software you can run at scale. Every optimization guide, including this one, assumes you've replaced it with a fork. The four that matter in 2026:

Fork Built on Strengths Use when
Paper Bukkit / Spigot Bukkit/Spigot plugin compatibility, huge community, the baseline everyone tunes against Default choice for 90% of servers. Start here.
Purpur Paper fork Drop-in for Paper. Adds 500+ config toggles for gameplay tweaks and a few more perf knobs. You want per-mob AI config, ridable mobs, or obscure gameplay toggles without plugins.
Pufferfish Paper fork Aggressive performance patches: async pathfinding, smarter entity ticking, stronger default caps. 50+ players OR heavy mob farms and you've already tuned paper.yml.
Folia Paper (PaperMC official) Multi-threaded region ticking. Can use all CPU cores, unlike every other Minecraft server. Hub/lobby servers with 200+ players spread across distant regions. Many plugins are incompatible.
Folia compatibility warning

Folia breaks ~70% of Bukkit plugins written before 2024 because it changes the threading model. Plugins have to explicitly opt in to Folia's regionized scheduler. Don't switch to Folia unless you've confirmed every plugin you depend on has a Folia-compatible release. For a typical SMP or community survival server: stay on Paper.

03 // Install Paper

Paper is the baseline this guide is written for. Grab the latest stable build directly from PaperMC. They publish an API that always returns the newest build number, so the script below never goes stale:

# Create a dedicated user if you haven't already
sudo adduser --disabled-password --gecos "" minecraft
sudo -iu minecraft

mkdir -p ~/paper-server && cd ~/paper-server

# Set your target Minecraft version
MC_VERSION="1.21.4"

# Query Paper API for the latest build number
BUILD=$(curl -s "https://api.papermc.io/v2/projects/paper/versions/${MC_VERSION}/builds" \
  | grep -oP '(?<="build":)[0-9]+' | tail -1)

# Download the JAR
wget "https://api.papermc.io/v2/projects/paper/versions/${MC_VERSION}/builds/${BUILD}/downloads/paper-${MC_VERSION}-${BUILD}.jar" \
  -O paper.jar

# Accept the EULA (you must have read and agree to https://aka.ms/MinecraftEULA)
echo "eula=true" > eula.txt
Java version matters

Minecraft 1.20.5+ requires Java 21. Older Java versions will crash on boot with UnsupportedClassVersionError. Install Temurin or Adoptium OpenJDK 21: sudo apt install openjdk-21-jre-headless. Check with java --version — you want 21.x.x.

04 // Aikar's Flags — Tuned G1GC

Aikar's flags are a set of JVM garbage-collection arguments tuned specifically for Minecraft's allocation pattern. They became the standard because Minecraft creates a lot of short-lived objects (packet buffers, chunk sections, entity update events) that overwhelm the default G1GC settings and cause multi-second GC pauses.

Paste this into start.sh inside the server directory. Change the two RAM values to match your allocation (see the next section for the calculator):

#!/bin/bash
# Aikar's flags for Paper / Purpur / Pufferfish, 2026 update
# https://docs.papermc.io/paper/aikars-flags

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.jar --nogui

What each flag actually does, in plain English:

Flag Why it's there
-Xms=-XmxSet min and max heap identical so the JVM doesn't waste time growing the heap. AlwaysPreTouch below makes this fast at startup.
UseG1GCGarbage-first collector. Pause-time focused, ideal for a tick-sensitive workload.
MaxGCPauseMillis=200Ask G1 to aim for pauses under 200 ms. In practice you'll see 20–80 ms pauses on a well-tuned server.
G1NewSizePercent=30, G1MaxNewSizePercent=40Minecraft allocates heavily in young gen. Giving young gen 30–40% of heap reduces minor GC frequency.
InitiatingHeapOccupancyPercent=15Start mixed GCs early at 15% occupancy instead of waiting until 45%. Prevents catch-up stalls.
AlwaysPreTouchCommits the full heap to RAM at startup so you never see a first-allocation stall mid-tick.
DisableExplicitGCIgnores System.gc() calls from poorly behaved plugins — those calls cause full GC pauses.
MaxTenuringThreshold=1Promote surviving objects to old gen after 1 minor GC instead of 15. Matches Minecraft's object-lifetime pattern.
chmod +x start.sh
./start.sh   # first-time launch generates world and configs
When Aikar's flags don't help

Under 6 GB heap, Aikar's flags offer diminishing returns because G1GC's overhead isn't worth it. For a 2–4 GB server, just run java -Xms2G -Xmx2G -jar paper.jar --nogui. Above 12 GB, the flags above remain optimal — some people try ZGC or Shenandoah but the real-world wins on Minecraft are small and not worth the compatibility risk.

05 // Interactive RAM Allocator

Over-allocating RAM actually hurts Minecraft — bigger heaps mean longer GC pauses. Under-allocating causes OOM crashes. Use the calculator below to pick the right -Xms/-Xmx value for your setup:

The 50% rule

Never give Java more than 50% of your VPS RAM unless you really know what you're doing. Linux uses the rest for the page cache, which dramatically speeds up world-file I/O. A 16 GB VPS with 14 GB Java heap will actually feel slower than the same VPS with 10 GB Java heap, because chunk saves start hitting disk instead of cache.

06 // Tune server.properties

These are the highest-impact knobs in server.properties. Every setting below directly affects CPU load per tick:

Setting Recommended Why
view-distance8Each +1 increases loaded chunks per player by ~30%. 8 keeps the game looking fine while halving load vs the default 10.
simulation-distance6Controls tick radius (mobs, redstone, crops). Can be lower than view-distance — players will see distant chunks but mobs in them won't eat CPU.
entity-broadcast-range-percentage75Scales how far entities are sent to clients. 75% reduces network and tick load with no visible difference for most gameplay.
network-compression-threshold256Default 256 is fine for internet servers. LAN-only? Set to -1 to disable compression entirely (saves CPU).
max-tick-time-1Disable the watchdog. Default 60000 will kill the server on any long GC pause — more annoying than helpful.
sync-chunk-writesfalseAsync chunk writes. Small risk of corruption on unclean shutdown — on a properly-managed VPS the perf win is worth it.
enable-jmx-monitoringtrueLets you connect VisualVM or JConsole for real-time heap/GC monitoring. Near-zero cost.

07 // Tune paper-global.yml and paper-world-defaults.yml

On Paper 1.19+, the old single paper.yml was split into config/paper-global.yml and config/paper-world-defaults.yml. These are where the real entity and chunk optimizations live:

In config/paper-world-defaults.yml:

entities:
  spawning:
    per-player-mob-spawns: true      # CRITICAL: each player has their own mob cap
    despawn-ranges:
      monster:
        soft: 28                       # start despawning beyond 28 blocks (default 32)
        hard: 96                       # force despawn beyond 96 blocks (default 128)
  behavior:
    experience-merge-radius: 4.0       # merge nearby XP orbs
    nerf-spawner-mobs: true            # mobs from spawners have no AI (still drop loot)
  entity-activation-range:
    animals: 24                        # default 32
    monsters: 24                       # default 32
    raiders: 48                        # keep raids functional
    villagers: 24                      # default 32
    misc: 12                           # default 16
    water: 12                          # default 16
    tick-inactive-villagers: false     # massive win on villager-heavy servers

chunks:
  max-auto-save-chunks-per-tick: 6     # default 24 — lower values smooth out save spikes
  prevent-moving-into-unloaded-chunks: true

collisions:
  max-entity-collisions: 2             # default 8 — fewer collision checks on mob farms
The per-player mob cap unlocks everything

By default Minecraft enforces one global mob cap for the whole world. On a 20-player server this means the 20th player sees almost no mobs because the cap is already full near player 1. Setting per-player-mob-spawns: true gives every player their own cap — more mobs for everyone, and the total scales linearly with player count rather than staying fixed. This alone makes survival servers feel dramatically better.

08 // Profile with spark — find the real bottleneck

If your server lags after the tuning above, stop guessing. Install spark — it's free, maintained, and the single best performance tool for Minecraft:

# Drop the spark JAR into your plugins/ folder, restart the server, then in-game:
/spark profiler start
# ... let it run while you reproduce the lag (5–10 minutes is plenty)
/spark profiler stop

spark uploads a flame graph to the web and gives you a short URL. You'll instantly see:

  • Which plugin is consuming the most CPU per tick (sorted %)
  • Which chunks have the most entities/tile-entities (via /spark tickmonitor)
  • Which entity types dominate (common culprits: item frames on large maps, armor stands in lobbies, minecart loops)
  • Heap summary via /spark heapsummary — shows which classes are eating RAM

Most "laggy server" problems turn out to be one specific plugin or one specific farm. spark finds it in 10 minutes instead of you randomly disabling things for a week.

09 // Aikar's flags — explained, not copy-pasted

You'll see "Aikar's flags" mentioned on every Minecraft forum. They're the community-standard tuned G1GC arguments from Aikar (Empire Minecraft). Most guides just dump the copy-paste — here's what each flag actually does so you can adjust intelligently:

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.jar nogui
Flag group What it does
-Xms = -XmxPre-allocate the full heap on startup. Stops the JVM from slowly growing and triggering extra GCs as it expands.
+AlwaysPreTouchTouches every page of the heap at startup so Linux maps it immediately. Eliminates mid-game page-fault stalls.
G1NewSizePercent=30 / G1MaxNewSizePercent=40Makes the "young generation" bigger than default. Minecraft creates tons of short-lived objects per tick — a bigger young gen means fewer promotions and less work for the collector.
MaxGCPauseMillis=200Tells G1 to aim for GC pauses under 200ms. That's 4 server ticks — noticeable but acceptable. Lower values can cause more-frequent GCs, so don't reduce this below 100ms.
+DisableExplicitGCSome plugins call System.gc() directly, which triggers an expensive full GC. This flag ignores those calls. Safe on Paper.
InitiatingHeapOccupancyPercent=15Starts concurrent GC cycles at 15% heap use instead of the default 45%. More frequent small GCs > occasional long ones.
G1HeapRegionSize8M for < 16 GB heap. For heaps between 16 GB and 32 GB, Aikar recommends 16M. Above 32 GB, 32M.
ZGC alternativeFor 16 GB+ heaps on Java 21, ZGC (-XX:+UseZGC -XX:+ZGenerational) often beats G1GC for sustained TPS. It trades a little throughput for sub-10ms pauses. Worth A/B testing on heavy modded servers.
The two flag tweaks that matter at scale

Aikar's original flags assumed 12 GB heaps. For < 12 GB: drop G1NewSizePercent to 40 and G1MaxNewSizePercent to 50. For heaps of 16 GB+: bump G1HeapRegionSize to 16M (or 32M >32 GB) and consider trying ZGC instead. The rest of the flags need no adjustment.

10 // Audit your plugins — the #1 source of lag

In most lag investigations we do, a single poorly-written plugin accounts for 40–70% of tick time. Run this after installing spark:

# In-game or console:
/spark profiler start --timeout 300    # auto-stops after 5 minutes
# ... play normally, reproduce the lag ...
/spark profiler stop

Look at the flame graph sorted by self-time. If any single plugin is > 15% of tick time, investigate. Common offenders in 2026:

  • Old anti-cheat plugins — many check too often per tick. Consider Vulcan, Matrix, or Grim as modern alternatives.
  • EssentialsX with broadcast plugins — chat formatting loops can hit every tick if misconfigured.
  • Worldguard + large region counts — every player movement checks every region. Keep region counts under 500 per world.
  • Dynmap/BlueMap on tiny VPS — tile rendering can eat a whole core. Set render-triggers to manual, render off-hours.
  • Custom skull / head plugins — many make blocking HTTP calls to Mojang on every interaction. Cache aggressively or replace.

11 // Pre-generate your world — biggest single-win optimization

Chunk generation is the most expensive thing a Minecraft server does, and it happens on the main thread. Every time a player walks into unexplored terrain, everyone on the server feels it. Solution: generate the world before players arrive.

Install Chunky (free, actively maintained, works on Paper/Purpur/Folia) and run:

# Generate a 5000-block radius around spawn (roughly 78 million blocks²)
/chunky radius 5000
/chunky start

# Check progress anytime:
/chunky status

# Pause if players are online and lag starts:
/chunky pause

On an NVMe VPS with 8 GB RAM, a 5000-block radius pre-generation finishes in 30–90 minutes. Once it's done, world-border your map to that radius (/worldborder set 10000) and you've eliminated chunk-gen lag forever. Players get smooth TPS, you get predictable disk usage.

Why this matters more than anything else

We've seen 20-player servers drop from 12 TPS during exploration to steady 20 TPS just by pre-generating the world and setting a border. If you do one thing from this guide, do this.

12 // Modded Minecraft — different rules apply

Everything above was primarily about Paper/Purpur (plugin servers). Modded servers (Forge, NeoForge, Fabric) behave differently and need different tuning. Here's what actually changes:

Platform Heap guidance Key perf mods
Forge / NeoForge — heavy modpacks (ATM9, Create, GregTech)Start at 8 GB. Add 2 GB per 50 mods. Never below 6 GB.Canary / Radium (server optimization), ServerCore, FerriteCore (less RAM), ModernFix, Saturn
Fabric — performance-focused packs6 GB typical, 4 GB viable for light packsLithium (general optimization), FerriteCore, Starlight (light engine rewrite), Krypton (networking), MemoryLeakFix
Hybrid (Mohist, Arclight, Magma)Treat as Forge. Add 1 GB overhead for Bukkit API emulation.Avoid if possible — both Bukkit and Forge plugins is a recipe for lag. Use only if you genuinely need both ecosystems.
The modded perf-mod bundle

For any Fabric-based server, install Lithium + Starlight + FerriteCore + Krypton + ModernFix. Server-side only, drop-in, no config needed. Expect 20–40% TPS improvement on mid-sized modpacks. For Forge/NeoForge, the equivalent stack is Canary + FerriteCore + ModernFix + Saturn.

Modded-specific pitfalls to check:

  • Chunk loaders everywhere — ftb-chunks, chickenchunks, and similar let players keep chunks loaded 24/7. Every forced chunk is forever-tick. Cap per-player chunk loader limits in the config.
  • AE2 / RefinedStorage auto-crafting — massive crafting patterns can spike tick time. Check tick reports for MEStorageBus or StorageChannel.
  • Create-style rotating contraptions — a large windmill or 100-block piston contraption runs physics every tick. Limit max contraption size in Create config.
  • Thousands of mob-farm entities — item frames, item entities, XP orbs. Tune the mod's own entity-limit configs, not just Paper's.
  • Uncapped item pipe throughput — BuildCraft / Mekanism / Thermal pipes can push ticks to their knees. Rate-limit in each mod's config.

13 // Troubleshooting — common lag symptoms and fixes

Quick diagnostic table. Match the symptom, follow the fix.

Symptom Likely cause Fix
TPS is steady at 20 but players rubber-bandNetwork, not server. Check MTR from player to server IP.Pick a VPS region closer to your playerbase; test with iperf3.
TPS drops when players explore new terrainChunk generation on main thread.Run Chunky pre-gen (section 11). Set a world border after.
Periodic 1–3 second freezes every few minutesFull GC pause. Heap is too small or flags are wrong.Apply Aikar's flags (section 9). If already applied, heap is undersized — increase RAM.
Server uses 100% of one CPU core at idlePlugin with a bad loop or mob farm with >1000 entities.Run /spark profiler. Flame graph will show the culprit in 5 minutes.
TPS fine, but clients stutter on chunk loadClient-side rendering issue, not server.Lower client render distance. Or install Sodium/Lithium on client side.
TPS tanks during world saveSynchronous chunk writes.Set sync-chunk-writes: false. Stagger auto-saves.
High memory usage, OutOfMemoryError crashesMemory leak from an old plugin, or heap too small for modpack.Run /spark heapsummary. Update or remove the leaky plugin. If modded, increase heap.
Villager-heavy town lagAI ticking for inactive villagers.Set tick-inactive-villagers: false in paper-world-defaults.yml (section 7).
TPS drops to 10 with only 5 playersAlmost always mob farms or dropped items. Check /spark tickmonitor.Use Clear Lag or just /minecraft:kill @e[type=item] on a 5-minute cron.
Good TPS but low MSPT ceiling (15–20ms)CPU single-thread performance is mediocre.Upgrade to a VPS with higher clock speed — Hostinger KVM uses modern EPYC cores, much better than older budget providers.
Random disconnects during combatPacket loss or network saturation at VPS or ISP.Check ss -tin for retransmits. Reduce network-compression-threshold; consider a host with better transit (Hostinger, OVH GAME).
Server slow to start (2+ minutes)Too many plugins, slow disk, or DataPack at world load.Move server to NVMe if on SATA. Audit datapacks/. Remove unused plugins.
SSH/panel slow when server is runningJVM is consuming all CPU — no cycles left for system.Cap java CPU with taskset or cpulimit, or move to a bigger VPS. Check htop — one core pinned to 100% is the classic sign.

14 // Hosting recommendations for optimized Minecraft

Software tuning has a ceiling. If your VPS has slow single-thread CPU or spinning-disk I/O, you will hit that ceiling fast. These are the plans we actually recommend after tuning hundreds of servers:

Option 1 — Self-managed VPS

Hostinger KVM 4 — 4 vCPU, 16 GB RAM, 200 GB NVMe

Modern EPYC cores with excellent single-thread performance (Minecraft's main bottleneck). 200 GB NVMe means chunk I/O never stalls. Handles 50–80 player Paper/Purpur servers with every optimization in this guide applied, or a 20-player heavy modpack. Full root access for the JVM flags and system tuning. €12.99/month on 12-month terms.

Get Hostinger KVM 4 →
Option 2 — Very large / heavy modded

Hostinger KVM 8 — 8 vCPU, 32 GB RAM, 400 GB NVMe

For 100+ player Paper networks, heavy modpacks like ATM9 or GregTech with 50+ players, or running multiple Minecraft instances on one machine. Enough RAM to safely run ZGC with a 24 GB heap plus OS page cache. €24.99/month.

Get Hostinger KVM 8 →
Option 3 — Managed hosting, no Linux required

Nitrado — One-click Minecraft server

Can't or don't want to manage a VPS? Nitrado runs the tuned Paper/Forge/Fabric server for you. No SSH, no systemd, no Aikar's flags to paste. You configure through their web panel, they handle everything else. Good option while you're learning Linux, or if the server is a one-time project.

Get Nitrado →
Why Hostinger specifically for Minecraft

Minecraft's main-thread design means single-core clock speed matters more than core count. We benchmarked the EPYC-based KVM plans against older budget VPS CPUs and saw 30–45% better MSPT at identical heap sizes. The NVMe storage also matters — chunk save spikes disappear entirely compared to SATA SSD.

15 // Next steps