Lag on a Minecraft server is one of those problems that ranges from mildly annoying to completely unplayable, and the fix is almost never the same twice. Sometimes it is a single plugin running a sync database query on every tick. Sometimes it is a player who built a 20,000-entity chicken farm. Sometimes it is the hardware the server is running on. This guide walks through how to diagnose the problem and apply the fixes that actually make a difference.

Diagnosing Lag: TPS, the /tps Command, and Spark Profiler

The first step is understanding what kind of lag you are dealing with. Minecraft servers target 20 ticks per second (TPS). When the server is healthy, each tick completes in 50ms or less. When something is taking too long, ticks stack up and TPS drops below 20. Players experience this as mobs freezing, blocks taking time to break, and items not landing in the right inventory slot.

If you are running Paper, Purpur, or any Paper fork, type /tps in the console or as an admin in-game. You will see three numbers: the average TPS over the last 1, 5, and 15 minutes. Anything consistently below 19.5 is worth investigating. To find out what is causing the problem, install Spark, the sampling profiler for Paper-based servers. Run /spark profiler start, wait 60 to 120 seconds during the lag, then run /spark profiler stop. Spark generates a shareable URL with a flame graph that breaks down exactly which code paths are eating the most tick time. This removes the guesswork from lag diagnosis entirely.

Hardware Matters More Than Anything Else

Software optimizations help, but they cannot overcome a fundamental hardware ceiling. Minecraft's main tick loop is single-threaded, which means the server can only do one thing at a time per tick. What matters is how fast that one thread can execute: single-core clock speed, not core count.

A server running on a shared hosting plan with throttled CPU performance or a low-clock-speed processor will lag regardless of how many plugins you remove or how low you set view distance. Consumer-grade processors from Intel's Core i9 or AMD's Ryzen 9 line run at significantly higher clock speeds under sustained load than the Xeons and EPYCs found in typical shared hosting environments. If you are already running Paper with tuned settings and still seeing TPS drops under five players, hardware is likely the bottleneck.

RAM speed also plays a role. DDR5 memory reduces latency on data-heavy operations like chunk loading and entity tracking. Fast NVMe storage reduces the time to load and save chunk data from disk, which shows up during exploration and server startup.

Switch from Vanilla to Paper

If you are still running the official Minecraft server JAR, switching to Paper is the single highest-impact change you can make. Paper is a fork of Spigot that adds hundreds of performance patches, async chunk loading, configurable entity activation ranges, and a large library of configuration options for tuning server behavior.

Paper is fully compatible with Spigot plugins and most Bukkit plugins. The switch is a drop-in replacement: download the Paper JAR, replace your current server JAR, and start the server. Your world, plugins, and configuration files will all work as before. After switching, you will have access to paper.yml (or paper-world-defaults.yml on newer versions) which exposes tuning options that do not exist in vanilla or Spigot. If you want to go further, Purpur builds on Paper and adds even more configuration options without a meaningful performance tradeoff.

Lower View Distance and Simulation Distance

View distance in server.properties controls how many chunks the server sends to each player. Simulation distance controls how many chunks around each player have active game logic running. Both settings have a direct and significant impact on server load.

The default view distance of 10 means the server is maintaining 441 chunks per player (a 21x21 grid). At 10 players that is up to 4,410 chunks being tracked simultaneously. Dropping view distance to 6 or 7 reduces that to around 1,500 to 2,000 chunks and makes an immediate difference to TPS on populated servers. Simulation distance can often be set lower than view distance without players noticing, since most gameplay happens close to the player. A setup of view-distance=8 and simulation-distance=4 is a good starting point for servers with more than five concurrent players.

Paper also lets you configure per-world view distances in paper-world-defaults.yml, which allows you to use a lower distance in resource worlds while keeping a higher distance in the main survival world.

Audit Your Plugins and Remove Dead Weight

Every plugin that registers a repeating task adds to the tick budget. A plugin that runs every tick looking for nearby players, recalculating permissions, or firing database queries can quietly consume a substantial fraction of your available tick time. The Spark profiler output will show you which plugins are eating the most CPU time, but it is also worth doing a manual review.

Go through your plugin list and ask: is this plugin actually being used? Is it maintained? Does it have a known performance issue? Plugins that have not been updated in several years often have inefficiencies that have been fixed in modern replacements. EssentialsX, CMI, and LuckPerms are all actively maintained and generally efficient. Old economy plugins, unmaintained protection plugins, and cosmetic plugins with high entity counts are common offenders. Removing one badly-written plugin often recovers more TPS than all other optimizations combined.

Also check for plugins that run sync database calls. Any plugin that queries MySQL or SQLite on the main thread blocks the entire tick while it waits for the database response. This shows up clearly in Spark as a long call stack with JDBC at the bottom. Either find a replacement that uses async database access or configure the plugin to use async mode if it supports it.

JVM Startup Flags: The Aikar Flags Explained

The Java Virtual Machine running your server can be tuned through startup flags that control garbage collection behavior. The default JVM settings are not optimized for long-running server processes with large heaps. Garbage collection pauses happen when the JVM needs to reclaim memory, and without tuning, these pauses can cause sudden TPS drops even when nothing is logically wrong with the server.

The most widely used optimization is the Aikar flags, a set of G1GC tuning parameters designed specifically for Minecraft servers. The core flags tell the JVM to use G1 garbage collection, set the maximum GC pause target to 200ms, and configure the garbage collection regions to match Minecraft's allocation patterns. A baseline set for a 6 GB heap looks like this:

java -Xms6G -Xmx6G -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 -jar server.jar nogui

Setting -Xms and -Xmx to the same value prevents the JVM from dynamically resizing the heap, which reduces GC overhead. The -XX:+AlwaysPreTouch flag allocates all heap memory at startup so it is available immediately rather than being allocated on demand. Aikar's full flag set is documented at docs.papermc.io.

Chunk Loading and Pre-Generation with Chunky

One of the most consistent sources of lag spikes on active servers is players exploring new areas. Every time a player walks into an ungenerated chunk, the server has to generate it on the spot: terrain, structures, caves, ore veins, and biome data all get computed in real time. On complex terrain generators or with certain mods installed, this can take 100ms or more per chunk and causes noticeable stuttering during exploration.

The solution is pre-generating a defined world border before players start exploring. Chunky is a Paper plugin that pre-generates chunks asynchronously in the background, either while the server is running or during a maintenance period. A typical setup pre-generates a 5,000 to 10,000 block radius around spawn, covering the area most players are likely to explore in the first weeks of the server. This converts expensive on-demand generation into fast disk reads and eliminates almost all exploration-related lag spikes.

Chunky is non-destructive and can be resumed if interrupted. Set a world border to match the pre-generated radius using the vanilla /worldborder command so players are not pushed into un-generated territory once the pre-gen is complete. For larger servers, combining Chunky pre-gen with Paper's async chunk loading produces a noticeably smoother experience for everyone.