The Complete 2026 Guide to Running Your Own Minecraft Server (And Not Having It Lag)
Complete 2026 guide: server software selection, Java 26 flags, G1GC tuning with Aikar's Flags, Paper config, pre-generation, Spark profiling, and every setting that actually moves the needle on TPS.
You've been there. You boot a vanilla server for your friends. Eight people join. Someone builds a farm. The console starts printing Can't keep up! Is the server overloaded? Every five minutes. TPS drops to 12. Blocks reappear after you break them. Mobs freeze. Players leave.
The problem isn't your hardware. Well — sometimes it's your hardware. But most of the time the problem is that you're running the wrong server software with the wrong JVM flags and you've never touched the default configuration files that Mojang ships with settings tuned for a single-player creative world, not a 20-player survival server with four active farms.
This guide fixes that. All of it.
Stop Running Vanilla
The single most impactful decision you'll make is which server JAR to run. Never run vanilla Minecraft in production. Not even for a small server. Vanilla has no async chunk loading, no entity activation ranges, no tick optimization of any kind. It runs everything in a single thread and calls it a day.
We've covered every major Minecraft server software variant in detail — Bukkit, Spigot, Paper, Pufferfish, Purpur, Folia, and Leaf. Read that for the full comparison. Here's the practical 2026 decision tree:
Paper — Run this. Default choice for the vast majority of servers. It implements async chunk loading, entity activation ranges, and a hundred other optimizations that directly translate to better TPS under load. Fully compatible with Spigot/Bukkit plugins. Free, actively maintained by PaperMC. If you're not running Paper, you're leaving performance on the table for no reason.
Purpur — Paper fork with additional configuration options and quality-of-life features. If Paper doesn't have a setting you need, Purpur probably does. Fully compatible with Paper plugins. Good choice for servers that want more granular control.
Folia — Experimental Paper fork that introduces regionized multithreading — different regions of the world tick on different threads simultaneously. Massive theoretical performance ceiling for large servers. Practical reality: most Bukkit/Paper plugins are not thread-safe and will break under Folia's concurrency model. Only viable if your entire plugin stack has been updated for Folia compatibility. Worth watching but not the default in 2026.
Pufferfish / Leaf — Performance-focused forks of Paper targeting higher player counts. Leaf in particular has additional GC tuning options and SIMD optimizations for entity processing. Worth evaluating if you're running 100+ players and Paper is hitting its ceiling.
Download Paper from papermc.io. Direct link to the latest build, specific to your Minecraft version.
You Need Java 21 or 26
Minecraft 1.20+ requires Java 21 minimum. Java 26 is out and has measurable performance improvements — particularly around garbage collection and JIT compilation. We looked specifically at whether upgrading Minecraft servers from Java 21 to Java 26 makes sense — short answer: for most servers Java 21 LTS is the safer production choice, but Java 26 shows real improvements in GC pause times if you're willing to run a non-LTS version.
Install on Ubuntu/Debian:
# Java 21 (LTS — recommended for stability)
sudo apt install openjdk-21-jdk-headless
# Java 26 (latest — better GC but non-LTS)
# Download from https://adoptium.net/
wget https://github.com/adoptium/temurin26-binaries/releases/latest/download/OpenJDK26U-jdk_x64_linux_hotspot.tar.gz
tar -xzf OpenJDK26U-jdk_x64_linux_hotspot.tar.gz
export JAVA_HOME="$(pwd)/jdk-26/"
# Verify
java -version
On Windows: download the installer from adoptium.net, run it, verify in PowerShell with java -version.
The Part That Everybody Ignores
The default Java startup flags are garbage for a Minecraft server. Minecraft's memory allocation pattern is extremely specific — it allocates enormous quantities of small, short-lived objects (chunk data, entity data, packet data) at very high rates. The JVM's default garbage collector is not tuned for this. The result is GC pauses — the server freezes for 100-500ms while the garbage collector runs — which kill TPS and cause those rubberbanding, block-reappearing, mob-freezing events your players complain about.
Aikar's Flags are a set of JVM startup parameters specifically tuned for Minecraft's allocation pattern. They've been the gold standard for Paper servers for years and PaperMC officially recommends them. Use them.
The command (replace 10G with your allocated RAM):
java -Xms10G -Xmx10G \
-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
Let me explain what's actually happening here, because "magic words" that you paste without understanding will bite you when something goes wrong:
-Xms10G -Xmx10G — Set minimum and maximum heap to the same value. Always do this. If Xms < Xmx, the JVM starts with less memory and expands as needed. That expansion triggers full GC pauses. Lock them both to your target RAM and G1GC handles allocation much more smoothly. Leave 1-2GB for the OS — on a 16GB machine, set this to 12G or 13G, not 16G.
-XX:+UseG1GC — Enable the G1 (Garbage First) collector. G1 operates on heap regions rather than the traditional young/old generation split, letting it pause for shorter intervals and handle Minecraft's high object allocation rate better than the default Parallel GC.
-XX:MaxGCPauseMillis=200 — This is a target, not a guarantee. Tells G1 to try to keep pauses under 200ms. In practice it usually hits 10-50ms with a properly configured heap. Don't set this below 100ms — G1 will spend too much time trying to hit an unrealistic target and throughput suffers.
-XX:+AlwaysPreTouch — Forces the OS to actually allocate all the heap memory pages at JVM startup instead of lazily. Without this, the JVM requests memory from the OS on demand, which causes page fault latency during operation. Pre-touching means the memory is ready and contiguous.
-XX:+DisableExplicitGC — Prevents plugins from triggering System.gc() which causes full GC pauses. Some plugin developers, with all the good intentions in the world, call this manually and cause server-wide lag spikes. This flag ignores them.
-XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 — Minecraft allocates objects constantly. The "new generation" is where fresh allocations live. Increasing its size gives these short-lived objects more room to die young, which means they don't get promoted to the "old generation" where they require more expensive collection. This is the flag that eliminates the majority of GC-related lag spikes.
-XX:MaxTenuringThreshold=1 — Objects in the survivor space (newly allocated objects that survived one GC cycle) get promoted to old gen after just 1 survival instead of the default 15. Minecraft's objects are almost always either very short-lived or very long-lived. The in-between survivor space doesn't help much and wastes regions.
-XX:+PerfDisableSharedMem — Prevents GC from writing performance data to a shared memory mapped file. On servers with high disk I/O, this write can cause measurable GC pause increases. Disable it.
RAM allocation guidelines:
- Under 15 players, vanilla map: 4-6GB
- 15-30 players, some farms: 6-10GB
- 30-60 players, complex farms: 10-14GB
- 60+ players: 14GB+ and consider Folia or a split-server setup
Do not allocate more RAM than 12G without measuring first. G1GC scales well with large heaps, but "more RAM = better performance" stops being true somewhere around 12-16GB for most server setups. Give the OS headroom. Memory swap is a TPS killer.
For high-player-count servers (150+), consider switching from G1GC to Generational ZGC:
# Replace the GC flags with:
-XX:+UseZGC -XX:+ZGenerational
ZGC has near-zero pause times (sub-millisecond) at the cost of higher throughput overhead. For servers where allocation rate is so high that even G1GC's optimized pauses are causing issues, GZGC is the answer. Requires Java 21+. Use /spark gcmonitor in-game to measure before making this decision — don't switch blindly.
Server Configuration Files
This is where most guides stop — they hand you the JVM flags and call it done. The configuration files are where you get the other half of your performance.
server.properties (the basics):
# The main lever for performance — how many chunks to keep loaded per player
# Default is 10. Drop to 6-8 for better performance.
# Players won't notice below 8. They will notice below 5.
view-distance=8
# Separate from view-distance — controls how far entities tick
# 5 is the sweet spot. This has major impact on entity load.
simulation-distance=5
# Don't use rcon in production without firewall rules
enable-rcon=false
# Sync chunk writes — turn off for better disk performance
# Paper has its own async chunk I/O, so this is redundant overhead
sync-chunk-writes=false
spigot.yml (these matter):
world-settings:
default:
# How far from a player entities tick. This is a massive performance lever.
# 32 blocks is aggressively low. 48-64 is good for most servers.
entity-activation-range:
animals: 32
monsters: 32
raiders: 48
misc: 16
water: 16
villagers: 32
flying-monsters: 32
# Delay between entity activation checks
entity-tracking-range:
players: 48
animals: 48
monsters: 48
misc: 32
other: 64
# Merge nearby item drops into one entity — huge for farms
merge-radius:
item: 3.5
exp: 6.0
# How often hoppers check for items above them (ticks)
# Default 1 is insane for any server with hopper-heavy redstone
# 8 is good, 3-4 if you have players who care about hopper speed
hopper-amount: 1
ticks-per:
hopper-transfer: 8
hopper-check: 1
paper-world-defaults.yml (Paper-specific, high-impact):
entities:
spawning:
# These defaults are too high for multiplayer
# Cutting monster and animal limits reduces entity processing significantly
# but watch for complaints about mob rarity
monster-spawn-max-light-level: -1 # default behavior
behavior:
# Remove nearby mob pathfinding to far-away players
# Dramatically reduces pathfinding computation
disable-mob-spawner-spawn-egg-transformation: false
chunks:
auto-save-interval: 6000 # 5 minutes (ticks)
max-auto-save-chunks-per-tick: 24 # How many chunks to save per auto-save tick
# Prevents infinite chunk loading chains
prevent-moving-into-unloaded-chunks: true
misc:
# Group XP orbs — critical for any server with killing farms
# Without this, boss farms create thousands of individual XP entities
use-dimension-type-for-custom-spawners: false
The entity activation range deserves special attention. This setting tells the server to stop ticking entities — running their AI, pathfinding, physics — when no player is nearby. At default settings, every mob in your entire loaded world runs its full AI every tick. With entity activation ranges properly set, mobs far from players do minimal work. This single setting can recover 20-40% of your tick time on a mob-heavy server.
Pre-generate Your World
The biggest source of TPS spikes on a new server is chunk generation. When a player walks into unexplored territory, the server has to generate the terrain, structure, biome, and all associated data for new chunks in real time. This is expensive. It causes lag spikes that happen exactly when they're most noticeable — when players are exploring.
Pre-generate before your players ever join. Use ChunkyPaper:
# In-game or console:
/chunky radius 5000
/chunky start
This generates all chunks within a 5000-block radius of world spawn. For a server expecting 20-30 players with mostly local building, 5000 blocks covers most of what you'll ever need. Generation takes 1-4 hours depending on hardware. Run it once, never deal with exploration lag again.
For a server with many explorers or an unlimited world border, set a world border and pre-generate to that border:
/worldborder set 20000
/chunky worldborder
/chunky start
The disk space for pre-generated chunks is real — a 5000-block radius generates roughly 8-15GB of region files depending on the version and biome complexity. Budget for it.
The Tool You Actually Need for Diagnosis
All the configuration in the world means nothing if you don't know what's actually eating your tick time. We have a full guide on using Spark profiler to debug Minecraft server lag — read that for the deep dive. Here's the essential workflow:
Install Spark — Download from spark.lucko.me, place in your plugins folder, restart server.
When you're lagging, run:
/spark profiler start
# Wait 2-3 minutes while the server is actively laggy
/spark profiler stop
Spark generates a report URL. Open it. You'll see a flame graph showing exactly which method calls are consuming your tick time, broken down by plugin, by entity type, by world.
What to look for:
- Chunk loading at the top of the flame graph → pre-generate more, reduce view-distance
- Entity ticking (mob AI) → lower entity activation ranges, reduce mob spawn limits
- Plugin methods taking significant tick time → that plugin has a performance problem, find an alternative or configure it
- Hopper ticking → increase
hopper-transferticks in spigot.yml - Redstone → there's a complex farm that's eating tick budget; find it with
/spark tps --interval
The other critical Spark command:
/spark health
This shows you MSPT (milliseconds per tick). MSPT under 50 = stable 20 TPS. MSPT of 60 = ~16 TPS. MSPT of 100 = ~10 TPS. TPS is not a precise performance metric — MSPT is. A server can show 20 TPS average but still feel laggy if individual ticks spike to 200ms (MSPT spikes). Spark's health report shows you the spike distribution, not just the average.
Also: /spark gcmonitor watches garbage collection in real-time. Run this for 10 minutes after changing JVM flags to verify your GC pause times are actually improving. GC pauses showing up in the monitor that are longer than 200ms mean your flags need work or your heap is undersized.
The Entities Problem
This deserves its own section because it's the most common cause of lag on survival servers and the most misunderstood.
Minecraft's entity AI is singlethreaded. Every loaded entity — every mob, every item, every XP orb, every armor stand — gets processed every tick on the main server thread. A world with 3,000 entities in loaded chunks means 3,000 AI cycles every 50ms. At some point that number exceeds what one thread can process in time, and your TPS falls off a cliff.
The entity sources:
Mob farms that run unattended accumulate mob stacks. An AFK player next to a mob farm that's been running for 6 hours can build up thousands of entities in that chunk column. Set max-entity-cramming in server.properties (default 24) — entities beyond this limit in one block start taking damage and die, naturally capping mob density.
# server.properties
max-entity-cramming=24 # entities beyond this in one space start dying — tune downward if farms are too dense
Set per-chunk entity limits in paper-world-defaults.yml:
# paper-world-defaults.yml
entities:
spawning:
per-player-mob-spawns: true # distribute mob spawn budget per player instead of globally
spawn-limits:
monsters: 50 # default 70, lower = fewer mobs = less AI load
animals: 8 # default 10
water-animals: 3
water-ambient: 10
ambient: 8
XP orbs are a special category of problem. Each XP orb is an entity. A player killing 1,000 mobs in a farm creates 1,000+ individual XP orb entities. Install a plugin that merges XP orbs in the same area, or use Paper's built-in XP orb grouping:
# paper-world-defaults.yml
misc:
# Group nearby XP orbs — massively reduces entity count in farms
# This is on by default in recent Paper versions
xp-orb-group-per-area: true
Villagers deserve their own warning. Villager AI is one of the most expensive entity computations in the game. They pathfind to beds, to work stations, to other villagers. A village of 30 villagers in a loaded chunk is several times more expensive than 30 zombies in the same chunk. If players have large villager trading halls, cap villager count per-world or use VillagerOptimiser plugin.
Every Plugin Has a Cost
Every plugin is code that runs on your main server thread. Some plugins are well-written and negligible. Some plugins are disasters that eat 20% of your tick time doing something that should take microseconds.
Plugins worth running:
- Spark — profiling, no performance cost when idle
- LuckPerms — well-optimized permissions, async where possible
- EssentialsX — the standard utility plugin, reasonably efficient
- CoreProtect — block logging is async, minimal TPS impact
- WorldEdit — be careful with large operations on live servers; use
//perflimits
Plugins to be suspicious of: - Any plugin that claims to "optimize" Minecraft by scheduling things itself — most of these conflict with Paper's own optimizations
- Economy plugins with database backends that do synchronous SQL queries on the main thread — check their async support
- Anti-cheat plugins that scan every player action — these can be expensive; profile before and after installation
- Crates/rewards plugins that spawn lots of visual particles — particles are cheap but become expensive at high player counts
Before installing any plugin, install Spark first. Profile your server baseline. Install the plugin. Profile again. If MSPT increased, investigate. If it increased significantly, either configure the plugin to be less aggressive or find an alternative.
The Setup Nobody Talks About
Your server software and JVM flags can be perfect and you'll still have terrible player experience if your networking is wrong.
Connection count and bandwidth: Each Minecraft player uses roughly 20-100KB/s of bandwidth depending on activity. 20 players = 400KB-2MB/s upload. On a home connection, check your upstream bandwidth. Minecraft is not as bandwidth-intensive as video streaming, but a home connection with 10MB/s upload starts feeling the pressure at 30-40 players if they're all in the same area exploring.
Port setup:
# Default Minecraft port — open this in your router/firewall
# TCP and UDP both
ufw allow 25565/tcp
ufw allow 25565/udp
# If you want a custom port for a separate test server
ufw allow 25566/tcp
For dedicated server hosting (Linux VPS/dedi): Optimize network buffer sizes in /etc/sysctl.conf:
# Increase TCP buffer sizes
net.core.rmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 87380 16777216
Apply with sysctl -p. These settings matter on servers handling 50+ simultaneous connections. Below that the defaults are fine.
For home hosting: The bigger issue is usually NAT/firewall. Set a static IP on the machine running the server. Forward port 25565 in your router to that IP. Don't use DHCP for the server machine — if the IP changes after a router reboot, port forwarding breaks and everyone gets disconnected.
Backups Before You Need Them
Running a server without backups is how you lose months of player progress to a disk failure, a corrupted world file, or the one plugin that overwrites your spawn region. You need automatic backups. This is non-negotiable.
Simple rsync backup on Linux:
#!/bin/bash
# backup.sh
SERVER_DIR="/opt/minecraft/survival"
BACKUP_DIR="/opt/minecraft/backups"
DATE=$(date +%Y%m%d_%H%M)
BACKUP_FILE="$BACKUP_DIR/survival_$DATE.tar.gz"
# Stop the server gracefully before backup, then restart
# Or use live backups with CoreProtect which handles this for you
tar -czf "$BACKUP_FILE" \
--exclude="$SERVER_DIR/logs" \
--exclude="$SERVER_DIR/cache" \
"$SERVER_DIR"
# Keep only the last 14 backups (2 weeks daily)
ls -t "$BACKUP_DIR"/*.tar.gz | tail -n +15 | xargs rm -f
echo "Backup complete: $BACKUP_FILE"
Add to crontab for daily execution:
crontab -e
# Add:
0 4 * * * /opt/minecraft/backup.sh >> /opt/minecraft/backup.log 2>&1
4 AM daily, keeping two weeks of history. Adjust retention based on your disk space. A full world backup for a 5000-block radius server is 8-30GB compressed, depending on how built-up the world is.
For plugins, use CoreProtect for block-level logging — it records who placed and broke every block, letting you roll back griefing without restoring from backup. It runs async and the performance impact is minimal.
The Real Performance Checklist
After all of this, here's what to actually verify before opening your server to players:
Run /spark tps and let the server idle with nobody on it. MSPT should be under 5ms at idle. If it's over 10ms at idle, something is wrong — a plugin is doing work it shouldn't be when nobody's on.
Join with one player and start walking in an unexplored direction. Watch MSPT. If it spikes to 100ms+ during exploration, your world isn't pre-generated and chunk generation is killing your tick.
Set up a mock farm — 50 mobs in a loaded chunk — and watch /spark health in another window. See how MSPT changes as entities accumulate. This tells you your entity processing budget and helps you set reasonable mob caps before 20 players find the limits the hard way.
Read /spark health output carefully. The JVM GC section shows you whether your flags are actually working. GC pauses under 20ms, infrequent, means your heap is tuned well. GC pauses over 100ms multiple times per minute means your flags need work or your heap is too small.
The full picture comes together when you run Spark's profiler output for a real 20-player session and understand what you're looking at. Idle configuration tuning only tells you so much. Player behavior — the farms they build, the redstone they wire, the entities they accumulate — is the actual workload.
Related reading from CoderOasis: The complete Minecraft server software guide covers Paper vs Purpur vs Folia vs Leaf in full detail. Debugging Minecraft server lag with Spark profiler is the follow-on to this guide — once your server is running, that's how you diagnose what's actually causing your TPS issues. Java 26 new features covers the GC improvements that make upgrading worth evaluating. Should you upgrade from Java 21 to Java 26? answers that question for Minecraft servers specifically.