Server lag in Minecraft almost always has a root cause β it's rarely "just bad hardware." This guide walks through the complete process of diagnosing and fixing lag on a Paper 1.21 server, from measuring TPS with Spark to fine-tuning Paper's config files. Every recommendation here has been tested on servers with 20-60 concurrent players.
TPS stands for Ticks Per Second. Minecraft runs at 20 TPS β every game tick advances time, moves entities, processes redstone, runs mob AI, and so on. When the server can't keep up with the workload, TPS drops below 20 and the game feels laggy.
The fastest way to check TPS is with the Spark plugin. Run /spark tps in-game or in console to see current TPS averaged over 5 seconds, 1 minute, and 5 minutes. The 5-minute average is the most useful β a brief TPS dip from a large explosion doesn't mean you have a chronic lag problem.
You can also check with Paper's built-in /tps command, but Spark gives much more detail including CPU usage, memory breakdown, and tick duration graphs.
The JVM's garbage collector (GC) is responsible for cleaning up unused memory. By default, Java uses a GC configuration suited for desktop applications, not for a game server that allocates thousands of objects per second. Aikar's flags tune the G1 Garbage Collector specifically for Minecraft's memory patterns.
Create a start.sh (Linux/Mac) or start.bat (Windows) with these flags:
# start.sh β Linux/macOS
#!/bin/bash
java \
-Xms4G -Xmx4G \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+DisableExplicitGC \
-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
:: start.bat β Windows
java -Xms4G -Xmx4G -XX:+UseG1GC -XX:+ParallelRefProcEnabled ^
-XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions ^
-XX:+DisableExplicitGC -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
pause
-Xms and -Xmx to the same value. This prevents JVM from requesting more memory at runtime (which causes brief pauses). Rule of thumb:-Xms2G -Xmx2G-Xms4G -Xmx4G-Xms8G -Xmx8GPaper exposes hundreds of configuration options not available in vanilla Minecraft. The file you'll edit most is config/paper-world-defaults.yml (Paper 1.19+). Here are the most impactful settings:
# config/paper-world-defaults.yml entities: spawning: # Lower mob spawn attempts per chunk per tick spawn-limits: monsters: 50 # Default: 70 β reduce on busy servers animals: 8 # Default: 10 water-animals: 3 ambient: 5 environment: # How many chunks ahead of players are SIMULATED (mob AI, redstone, etc) # This is different from view-distance (rendering only) simulation-distance: 6 # Default: 10 β HUGE performance impact chunks: # How many chunks players can SEE (less simulation overhead) view-distance: 8 # Default: 10 # In paper.yml (older config location) entity-per-chunk-save-limit: experience_orb: 50 # Limit XP orbs per chunk (huge lag source) arrow: 100 snowball: 20 tick-rates: grass-spread: 4 # Default: 1 β grass spread ticks every 4 ticks instead of 1 container-update: 1 # Don't change this # Entity activation ranges (mobs outside range tick less frequently) entity-activation-range: animals: 24 # Default: 32 β animals only activate fully within 24 blocks monsters: 32 # Default: 32 raiders: 48 misc: 12 tick-inactive-villagers: false # BIG win β villagers are expensive
simulation-distance too much breaks farm mechanics (mob spawners, redstone clocks stop working for players far away). Test changes on a development server first.
Guessing where lag comes from is inefficient. Spark gives you a CPU flamegraph that shows exactly which code is consuming the most time. Here's the workflow:
In console, run: spark profiler --timeout 120
This profiles 120 seconds of server activity. Run during peak hours when you have players online β profiling an empty server shows nothing useful.
Spark uploads the report to https://spark.lucko.me/ and prints the URL in console. Open it. You'll see a horizontal flame graph where the width of each block = time consumed.
Look for wide blocks high in the call stack. Common culprits:
β EntityAI.tick() β too many mobs with complex AI
β BlockListener (plugin name) β a plugin doing work every block change
β WorldguardPlugin β WorldGuard checking regions too often
β AsyncTabCompleter β plugin with expensive tab completion
Run spark health in console. It shows memory usage, GC activity, disk I/O, and CPU load with traffic-light indicators. If GC is shown in red, the JVM flags or RAM allocation needs fixing.
Every time a player explores a new area, the server must generate that chunk from scratch β computing terrain, structures, ores, and biomes. This is one of the most expensive operations Minecraft performs. If you don't pre-generate, every explorer triggers a TPS spike.
The solution is to pre-generate the world before players log in using the Chunky plugin:
# In server console β pre-generate a 5000-block radius (10000x10000 area) /chunky start radius 5000 # For a 2500-block radius (more reasonable for small servers) /chunky start radius 2500 # Check progress /chunky status
This will take time β anywhere from 30 minutes to several hours depending on world seed and hardware. Run it before players join or during a maintenance window. The TPS improvement for new player exploration is immediate and dramatic.
Also set a world border to prevent infinite world growth:
# Set world border to 10000 x 10000 (5000 blocks each direction from 0,0)
/worldborder set 10000
Entities β mobs, animals, item frames, armor stands, dropped items β are one of the heaviest per-tick costs. Each entity with active AI ticks every game tick. Here's how to control entity lag:
# Kill all dropped items in all worlds /kill @e[type=item] # Kill arrows (accumulate from skeleton farms) /kill @e[type=arrow] # Kill all mobs except players /kill @e[type=!player]
/spark entities
This shows a breakdown of entity counts per chunk and per world. If you see "5000 zombies in chunk (32, -17)" β someone has a mob farm that's out of control.
Villagers are the single most expensive entity in Minecraft. Each villager runs pathfinding AI, commerce state machines, and gossip updates. If your server has a large trading hall with 50+ villagers, that's a significant lag source.
In paper-world-defaults.yml:
# Villager optimizations entity-activation-range: tick-inactive-villagers: false # Villagers > activation range tick at 1/20 speed villager: brain-ticks: 2 # Villager AI runs every 2 ticks instead of 1 search-radius: acquire-poi: 48 # Default 48 nearest-bed: 48 # Reduce if villagers can't find beds (cosmetic issue)
Plugins can cause lag in non-obvious ways. Here's a list of common patterns that cause performance issues:
PlayerMoveEvent β this fires every time a player moves a single block. Plugins that do expensive operations here (database queries, region checks) are extremely costly.Bukkit.getScheduler().runTaskTimer(1) β running a task every single tick. Should be at least every 5-20 ticks.In the Spark flamegraph, plugin method calls show as co.aikar.timings.PluginTimingsTask or the plugin's own package name. Look for plugin-named classes consuming >5% of server time.
Also run Paper's built-in timings:
/timings report
This generates a web report showing exactly how much time each plugin's event listeners are consuming per second. Sort by "total time" to find the worst offenders.
/spark tps shows 20.0simulation-distance: 6 in paper-world-defaults.ymlview-distance: 8 in server.properties/spark profiler weekly during peak hours/spark entities if TPS dropsOliveerF Network servers run Paper 1.21 with Aikar flags, optimal Paper config, and Chunky pre-generation already set up. Free tier available.
View Hosting Plans