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
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 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
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=-Xmx | Set min and max heap identical so the JVM doesn't waste time growing the heap. AlwaysPreTouch below makes this fast at startup. |
UseG1GC | Garbage-first collector. Pause-time focused, ideal for a tick-sensitive workload. |
MaxGCPauseMillis=200 | Ask 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=40 | Minecraft allocates heavily in young gen. Giving young gen 30–40% of heap reduces minor GC frequency. |
InitiatingHeapOccupancyPercent=15 | Start mixed GCs early at 15% occupancy instead of waiting until 45%. Prevents catch-up stalls. |
AlwaysPreTouch | Commits the full heap to RAM at startup so you never see a first-allocation stall mid-tick. |
DisableExplicitGC | Ignores System.gc() calls from poorly behaved plugins — those calls cause full GC pauses. |
MaxTenuringThreshold=1 | Promote 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
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:
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-distance | 8 | Each +1 increases loaded chunks per player by ~30%. 8 keeps the game looking fine while halving load vs the default 10. |
simulation-distance | 6 | Controls 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-percentage | 75 | Scales how far entities are sent to clients. 75% reduces network and tick load with no visible difference for most gameplay. |
network-compression-threshold | 256 | Default 256 is fine for internet servers. LAN-only? Set to -1 to disable compression entirely (saves CPU). |
max-tick-time | -1 | Disable the watchdog. Default 60000 will kill the server on any long GC pause — more annoying than helpful. |
sync-chunk-writes | false | Async chunk writes. Small risk of corruption on unclean shutdown — on a properly-managed VPS the perf win is worth it. |
enable-jmx-monitoring | true | Lets 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
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 = -Xmx | Pre-allocate the full heap on startup. Stops the JVM from slowly growing and triggering extra GCs as it expands. |
+AlwaysPreTouch | Touches every page of the heap at startup so Linux maps it immediately. Eliminates mid-game page-fault stalls. |
G1NewSizePercent=30 / G1MaxNewSizePercent=40 | Makes 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=200 | Tells 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. |
+DisableExplicitGC | Some plugins call System.gc() directly, which triggers an expensive full GC. This flag ignores those calls. Safe on Paper. |
InitiatingHeapOccupancyPercent=15 | Starts concurrent GC cycles at 15% heap use instead of the default 45%. More frequent small GCs > occasional long ones. |
G1HeapRegionSize | 8M for < 16 GB heap. For heaps between 16 GB and 32 GB, Aikar recommends 16M. Above 32 GB, 32M. |
| ZGC alternative | For 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. |
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-triggersto 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.
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 packs | 6 GB typical, 4 GB viable for light packs | Lithium (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. |
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
MEStorageBusorStorageChannel. - 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-band | Network, 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 terrain | Chunk generation on main thread. | Run Chunky pre-gen (section 11). Set a world border after. |
| Periodic 1–3 second freezes every few minutes | Full 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 idle | Plugin 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 load | Client-side rendering issue, not server. | Lower client render distance. Or install Sodium/Lithium on client side. |
| TPS tanks during world save | Synchronous chunk writes. | Set sync-chunk-writes: false. Stagger auto-saves. |
| High memory usage, OutOfMemoryError crashes | Memory 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 lag | AI ticking for inactive villagers. | Set tick-inactive-villagers: false in paper-world-defaults.yml (section 7). |
| TPS drops to 10 with only 5 players | Almost 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 combat | Packet 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 running | JVM 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:
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 →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 →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 →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
- Already setting up a new server? — Follow our server setup pattern and apply these optimizations from day one.
- Need a panel for easier management? — Pterodactyl or AMP both handle JVM flag management in a GUI.
- Running Bedrock instead? — See the Bedrock dedicated server guide — different engine, different tuning.
- Want a one-click managed option? — Windows hosting guide covers local hosting if you don't want a VPS at all.
- Check our tools — port reference, VPS sizing calculator, and more utilities for server owners.