TPS, ping and FPS are three separate failures
Lag is not one thing. Three unrelated systems produce the same complaint, and fixing the wrong one wastes money.
TPS is how fast the server simulates the world. The target is 20 ticks per second, which gives each tick a budget of 50 milliseconds. In that window the server has to move every mob, run redstone, tick furnaces and crops, process hoppers, load chunks and push packets out. Overrun the budget and the tick rate drops for everyone at the same instant.
Ping is the round trip between your machine and the server. It is a property of distance, routing and your own connection. A server holding a flat 20 TPS still feels broken at 250 ms, and no amount of RAM moves that number by a single millisecond.
FPS is how many frames your graphics card draws. It is purely local. Shaders, render distance and your client's own memory settings decide it. Nothing bought from a hosting company affects it.
Measure all three before you touch a config file or a billing page. It takes about a minute.
- Server TPS and MSPT: run /spark tps. Spark runs on Paper, Fabric, Forge and Velocity, and recent Paper builds already bundle it.
- Ping: open the player list with Tab and hover the connection bars, or read it from the F3 screen.
- Client FPS: press F3 and read the counter in the top left.
- On vanilla there is no /tps command. Install Spark, or watch the console for "Can't keep up! Is the server overloaded?"
Match the symptom to the culprit
The tell is not how bad it feels, it is what specifically misbehaves.
When TPS drops, the world slows down but your own movement stays smooth for a moment before the server pulls you back. Mobs stand still. Furnaces smelt at half speed. The day cycle visibly stalls, water flows slowly, and every player online sees it simultaneously.
When ping or packet loss is the problem, the world keeps running normally. Crops grow, mobs walk, the clock advances, other players move. What breaks is you specifically: blocks break and pop back, arrows miss targets you clearly hit, you rubber-band. Minecraft Java runs over TCP, so one lost packet stalls the whole stream until it is retransmitted. One per cent packet loss feels exactly like a lagging server and is not one.
When it is your client, the simulation is fine and only rendering is not. The stutter follows your mouse. Frame rate collapses when you look towards your base and recovers when you look at the sky, while chat and hit registration stay crisp.
- Everyone freezes at once, mobs stop, day cycle stalls: server TPS.
- Only you rubber-band while the world keeps moving: your connection.
- Stutter tracks your view direction but hits still register: your GPU or client memory.
- Lag on a strict five-minute rhythm: world autosave, not load.
- Lag only when someone explores new terrain: chunk generation.
- Lag at night or when a farm is running: entity ticking.
MSPT tells the truth; TPS lags behind it
TPS is a lagging indicator, and a server sitting on exactly 20.0 can still be one mob farm away from collapse. The number worth watching is MSPT, milliseconds per tick, the actual time the server spent doing work.
The budget is 50 ms. Below that, the tick loop sleeps for the remainder and TPS reads 20. Above it, TPS falls proportionally. So 20 TPS at 8 ms MSPT is an idle server with most of its capacity untouched, while 20 TPS at 46 ms is a server that will crater the moment someone lights a nether portal. Both report the same TPS.
Percentiles matter more than averages. The /spark health command reports median and 95th percentile MSPT. A median of 18 ms with a 95th percentile of 70 ms means the server is fine most of the time and freezes for a fifth of a second several times a minute, which players report as lag while /tps insists everything is at 20.
Aim for a median under 25 ms and a 95th percentile under 45 ms. That leaves headroom for a full moon, a new player and a plugin misbehaving.
- MSPT under 50 ms means TPS reads 20. It does not mean the server is healthy.
- Watch the 95th percentile, not the average. Players feel the spikes.
- Record the numbers before each change so you can prove whether the change helped.
Fix one: simulation distance, the largest win for two minutes of work
View distance and simulation distance are the two settings in server.properties with the largest effect on tick cost, and almost nobody changes them.
Simulation distance controls how far from a player the world is actually ticked: mob AI, redstone, crop growth, block entities, random ticks. The ticking area scales roughly with the square of the value, so 10 simulates about four times as much world as 5. The default is 10.
View distance only controls how far chunks are sent to clients for rendering. It costs bandwidth and memory, not tick time, so players can keep a long horizon.
For most servers, keep view distance at 8 to 10 and drop simulation distance to 5 or 6. Mobs stop thinking 160 blocks away, which nobody notices, and mob spawn limits scale with the ticking area, so entity counts fall as a side effect.
One caveat: large AFK farms depend on ticking range. Test with the farm owner rather than surprising them. Then restart, re-run /spark health, and compare. On an entity-heavy survival world, 10 to 5 frequently halves MSPT.
- A sane starting point for a 10 to 30 player survival server: view-distance=8, simulation-distance=5.
- Paper can override both per world in config/paper-world-defaults.yml.
- Change one setting at a time and re-measure. Two changes at once teach you nothing.
Profile instead of guessing: Spark in three commands
Disabling plugins one at a time is the slowest possible way to find a hot spot. A profiler answers the question in two minutes.
Install Spark if it is not already present, reproduce the lag, and capture a profile while it is happening. The output is a link to a flame graph of where main-thread time went, broken down by plugin, by entity type and by vanilla subsystem.
Read it from the top. Time under chunk generation points at exploration. Time under entity ticking points at farms, dropped items or villagers. A plugin name near the top with a database call underneath it means someone wrote a synchronous SQL query on the main thread, and that single finding is often the entire problem. Autosave and chunk writes appear as periodic spikes rather than steady cost.
Two notes on older advice. Timings has been removed from recent Paper builds, so forum threads telling you to run /timings paste may simply fail. And a profile captured while the server is healthy tells you nothing, so capture during the lag, not after it.
- /spark health for a quick MSPT, CPU and garbage collection summary.
- /spark profiler start --timeout 120, then open the link it prints when it finishes.
- /spark tps after each change to confirm the fix actually landed.
- /spark profiler start --thread * when you suspect async work is saturating the CPU.
Entities, hoppers and the periodic spike
Once chunk settings are sane, entities are usually the next largest line in the profile.
Villagers are the most expensive vanilla entity by a wide margin: pathfinding, gossip, workstation lookups and restocking. A 60-villager trading hall can cost more tick time than every mob elsewhere on the map combined. Dropped items come next, because a farm that produces faster than anyone collects leaves thousands of item entities ticking forever. Boats, minecarts and armour stands are cheap individually and expensive in bulk.
Paper's entity activation range decides how far away entities keep thinking. Defaults are 32 blocks for monsters and animals and 16 for miscellaneous entities, in config/paper-world-defaults.yml. Pulling monsters and animals down to 16 to 24 cuts entity cost noticeably and changes very little that players can see.
Hoppers only cost what they poll. If no plugin listens for inventory move events, setting the hopper disable-move-event option to true removes a per-item event dispatch.
If the lag is rhythmic rather than constant, it is not entities. A spike every five minutes is the world autosave, governed by ticks-per autosave in bukkit.yml and by Paper's cap on chunks saved per tick.
- Count entities before tuning: a profile that shows item entities at the top means the fix is a hopper, not a config.
- Item merge radius reduces entity counts without deleting anyone's drops.
- Do not raise mob spawn limits to compensate for lower simulation distance. That undoes the win.
Pregenerate the world and give it a border
The single most common cause of random, unreproducible lag spikes on survival servers is one player flying into terrain that has never been generated. Chunk generation is expensive, and modern terrain generation is more expensive than it used to be.
The fix is free. Install Chunky, generate the terrain in advance while nobody is playing, and set a world border so nobody walks past the generated area.
A 3,000 block radius is roughly 110,000 chunks, which is a large world for a small community. Depending on CPU and world type, expect anywhere from under an hour to several hours to generate, and a few hundred megabytes to a couple of gigabytes on disk. Generate the nether too, at about one eighth of the overworld radius, since nether travel covers eight times the ground.
Afterwards, exploration costs almost nothing, because chunks are read from disk rather than built from scratch. This is the change that most often makes people cancel a planned upgrade: the server was never short of resources, it was building terrain on demand while players waited.
- /chunky world world, then /chunky radius 3000, then /chunky start.
- Run it with the server empty, and expect high CPU for the duration.
- Set the vanilla world border to the same radius so the work is not wasted.
- Repeat for world_nether at radius 375 to cover the same overworld area.
Why more RAM almost never fixes low TPS
This is the upgrade people buy first and it is usually the wrong one.
Minecraft's tick loop is single threaded. TPS is decided by how quickly one CPU core can chew through one tick. RAM does not make a core faster. It matters only up to the point where garbage collection stops interfering, and beyond that a larger heap does nothing, or slightly hurts, because bigger heaps can mean longer collection pauses.
A Paper survival server with 20 players, sane simulation distance and a handful of plugins usually fits comfortably in a 3 to 4 GB heap. Heavy modpacks are a genuinely different workload and do need 6 to 12 GB.
Check before buying. The /spark health output includes garbage collection statistics. If total time in collection is a low single-digit percentage of wall clock and pauses are tens of milliseconds, memory is not the bottleneck and a bigger plan will change nothing.
Two settings that do matter: set the minimum heap equal to the maximum, and use Aikar's G1 flags rather than JVM defaults.
- Symptoms RAM does fix: repeated multi-second freezes with high garbage collection time, and out-of-memory crashes in the log.
- Symptoms RAM never fixes: high ping, low client FPS, a world that feels fine in single player, or a steady 45 ms MSPT with no memory pressure.
- On a 4 GB container, set -Xms3G -Xmx3G rather than 4G, leaving room for direct buffers, metaspace and threads. Matching the container limit exactly is a common cause of the process being killed.
When the machine really is the problem, and when you need no host at all
Sometimes the config is fine and the box is the issue. The signals are specific: MSPT that swings wildly at a constant player count, a profile with no single hot spot, or Spark reporting system CPU near saturation while your own process uses a fraction of it. That last one means the machine is shared with busy neighbours.
What matters in hardware, in order: single-thread CPU performance, then NVMe storage for chunk reads and writes, then RAM. A modern high-clock desktop CPU can be two to three times faster per tick than an older low-clock server chip on the identical world, and no config file closes that gap.
The honest alternative: if you play with five friends and have a spare machine on a wired connection, self-hosting works and costs nothing. A host buys uptime, a stable address, DDoS filtering in front of the game port, and backups that run without anyone remembering to make them. Check the support hours before you assume someone is awake when it breaks. It does not buy TPS you could have had by fixing simulation distance.
For context on how this maps to a plan: Puranode runs EPYC CPUs with NVMe in Finland, and RAM resizes are a manual same-day change on request rather than something to buy pre-emptively.
- Compare process CPU against system CPU in /spark health before blaming the host.
- Chunk-heavy worlds are storage-bound. Spinning disks or network storage show up as save spikes.
- Test with a pregenerated world before concluding the hardware is slow.
If TPS is fine: fixing ping and client FPS
Two problems remain that no server-side change will touch.
Ping has a floor set by distance. As a rough rule, a round trip costs about one millisecond per 100 km of fibre before any routing overhead, so western Europe to a central-European server lands around 10 to 40 ms, the US east coast around 85 to 110 ms, and Australia around 250 ms and upwards. If your ping sits near that floor, it is physics, and the only fix is a server closer to the players.
If it is well above the floor, or unstable, test properly. Ping the address for 60 seconds and look at packet loss and the gap between average and maximum. Anything above zero per cent loss is your answer regardless of what TPS says. Wi-Fi is the usual cause, and a cable removes jitter that a web page never reveals but PvP does.
Client FPS is fixed on the client. On Fabric, Sodium and Iris typically transform frame rates. Lower render distance, turn off fancy leaves and clouds, and allocate 4 GB to the client rather than 12, for the same collection-pause reason as the server.
- Run a 60 second ping or mtr to the server address and record loss, average and maximum.
- Wired beats wireless for jitter every time, even on a fast Wi-Fi connection.
- Client render distance is separate from server view distance. Lowering yours costs nobody else anything.