JVM Tuning
JVM Flags That Actually Matter
Default JVM flags are designed for enterprise Java apps that run for months. A Minecraft server is a real-time game with garbage collection pauses that visibly freeze players. These flags fix that.
Aikar's Flags (The Gold Standard)
Aikar (PaperMC core team) published JVM flags that became the de facto standard for Minecraft servers. Use these for Paper 1.16.5+:
java -Xms4G -Xmx4G \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+DisableExplicitGC \
-XX:+AlwaysPreTouch \
-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
Key things these flags do: MaxGCPauseMillis=200 tells the GC "try not to pause longer than 200ms" (default is 200 but other flags tune it to actually hit that target). InitiatingHeapOccupancyPercent=15 starts GC way earlier than default (45%) to avoid massive pause spikes. DisableExplicitGC stops plugins from calling System.gc() which would freeze everything.
Memory Allocation Rules of Thumb
- Set Xms and Xmx to the same value. If they differ, the JVM wastes time resizing the heap. Give it all the memory upfront.
- Don't allocate more RAM than you need. A 2GB heap with 10 players is fine. A 12GB heap with 10 players means fewer but longer GC pauses — worse performance. Rule of thumb: 1GB per 10-15 players for basic Paper, 2GB per 10-15 for modded.
- Leave 2-4GB for the OS. If your VPS has 8GB total, give the JVM 4-5GB max. The OS, MySQL, proxy, and other services need room too.
- For Velocity proxy: 128MB-256MB is plenty. Don't waste RAM on the proxy.
G1GC vs ZGC vs Shenandoah
G1GC is the safest bet for Minecraft — it's battle-tested and every PaperMC performance guide uses it. ZGC (Java 17+) has sub-millisecond pause times but higher CPU overhead. Shenandoah (Java 12+) is similar to ZGC. In practice, G1GC with Aikar's flags outperforms both for Minecraft workloads because Minecraft creates many short-lived objects (ideal for G1's young generation handling) and ZGC/Shenandoah's concurrent overhead doesn't pay off at heap sizes under 16GB.
# If you really want to try ZGC (Java 21+):
java -Xms4G -Xmx4G -XX:+UseZGC -XX:ZAllocationSpikeTolerance=2.0 -jar paper.jar nogui
Containerization
Docker for Minecraft Servers
Docker isolates each server into its own container with its own filesystem, CPU/memory limits, and network stack. This is how you run 5 servers on one VPS without them stepping on each other.
Minimal Dockerfile
FROM eclipse-temurin:21-jre-alpine
RUN adduser -D minecraft
USER minecraft
WORKDIR /server
COPY paper.jar .
COPY forwarding-secret.txt .
EXPOSE 25565
CMD ["java", "-Xms2G", "-Xmx2G", "-XX:+UseG1GC", "-jar", "paper.jar", "nogui"]
Docker Compose for a Network
version: '3.8'
services:
proxy:
image: eclipse-temurin:21-jre-alpine
ports: ["25565:25565"]
volumes: [./proxy:/server]
command: java -Xms128M -Xmx256M -jar velocity.jar
restart: unless-stopped
mem_limit: 512m
survival:
image: eclipse-temurin:21-jre-alpine
ports: ["25566:25565"]
volumes: [./survival:/server]
command: java -Xms2G -Xmx2G -jar paper.jar nogui
restart: unless-stopped
mem_limit: 4g
depends_on: [proxy]
creative:
image: eclipse-temurin:21-jre-alpine
ports: ["25567:25565"]
volumes: [./creative:/server]
command: java -Xms2G -Xmx2G -jar paper.jar nogui
restart: unless-stopped
mem_limit: 4g
depends_on: [proxy]
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: changeme
MYSQL_DATABASE: minecraft
volumes: [./mysql-data:/var/lib/mysql]
mem_limit: 1g
mem_limit is critical — without it, one server's memory leak can OOM kill every other container. Each server gets its own enforced limit.
Observability
Prometheus + Grafana Monitoring Stack
Guessing what's wrong is for amateurs. Set up real monitoring so you can see when something goes wrong and fix it before players ragequit.
Step 1: Spark Profiler
The Spark plugin (free, open source) exposes server metrics on an HTTP endpoint that Prometheus can scrape:
/spark prometheus enable
# Now available at http://your-server:4567/metrics
# Metrics: TPS, MSPT, memory, thread count, player count, tick duration histograms
Step 2: Prometheus Config
scrape_configs:
- job_name: 'minecraft'
scrape_interval: 15s
static_configs:
- targets:
- 'survival:4567'
- 'creative:4567'
- 'proxy:4567'
Step 3: Grafana Dashboard
Import community dashboard ID 13863 (Minecraft Server Performance) into Grafana. It shows TPS, MSPT, memory usage, GC pauses, and player count for all servers on one screen. Set up alerts: TPS < 18 for 5 minutes → Discord notification.
Alert Rules That Actually Help
- TPS below 15 for 5+ minutes — your server is dying, investigate now
- Memory usage above 85% — either a leak or too many players for your heap
- MSPT (ms per tick) above 40 — lag is visible to players, find the cause
- Player count drop > 50% in 2 minutes — server probably crashed
- Disk space below 10% — worlds eat disk fast, expand or prune
Database Engineering
MySQL at Minecraft Scale
SQLite works for one server with 10 players. When you have 5 backend servers sharing the same data, you need MySQL — set up properly.
Connection Pooling with HikariCP
Every plugin that connects to MySQL opens its own connections. Without pooling, you'll hit MySQL's max_connections (default 151) and get connection refused errors. Most modern plugins use HikariCP internally, but you need to configure it:
# In your plugin configs that support it:
pool-settings:
maximum-pool-size: 10 # per plugin per server
minimum-idle: 5
max-lifetime: 600000 # 10 min
connection-timeout: 5000 # 5 sec
idle-timeout: 300000 # 5 min
If CoreProtect, LuckPerms, and Essentials all pool 10 connections each, and you have 3 backend servers, that's 90 simultaneous connections. Set MySQL's max_connections to 200 to be safe.
MySQL Config Tweaks for Minecraft
# /etc/mysql/my.cnf
[mysqld]
innodb_buffer_pool_size = 1G # 25% of available RAM
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2 # faster, still crash-safe
max_connections = 200
performance_schema = ON # debugging slow queries
slow_query_log = 1
long_query_time = 2
Read Replicas for Large Networks
If you have 100+ players and 5+ servers, consider a primary-replica setup: one MySQL primary handles writes (player data, CoreProtect logs), and replica(s) handle reads (EssentialsX balances, LuckPerms data). This spreads load. Most plugins don't natively support read/write splitting, but you can use ProxySQL as a middleware that routes queries automatically.
Development
Writing Your Own Plugins
At some point, no existing plugin does exactly what you want. Time to write your own. Here's how to start.
Project Setup (Maven)
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
Minimal Plugin (Java)
package com.yourserver.yourapp;
import org.bukkit.plugin.java.JavaPlugin;
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
getLogger().info("Plugin enabled!");
getServer().getPluginManager()
.registerEvents(new MyListener(), this);
this.getCommand("hello").setExecutor(new HelloCommand());
}
}
Component-Based Approach (Folia-Compatible)
Paper 1.20+ supports Folia — regionized multithreading that splits the world into independent regions, each ticked on its own thread. Old plugins break on Folia. Write your plugin to be Folia-compatible from the start:
// Instead of using BukkitRunnable or getScheduler():
// Use the regionized scheduler:
Task task = Bukkit.getGlobalRegionScheduler()
.runAtFixedRate(plugin, (t) -> {
// runs every tick, safely
}, 1, 1);
// For entity-specific tasks:
entity.getScheduler().run(plugin, (t) -> {
entity.teleport(location);
}, null);
Plugin Dev Best Practices
- Never use static singletons — they break when plugins reload. Use dependency injection or pass your plugin instance.
- Always use async where possible — database queries, HTTP requests, file I/O should never block the main thread. Use
Bukkit.getScheduler().runTaskAsynchronously() or Paper's DataSaveEvent handling.
- Cache expensive operations — reading from config every event call is wasteful. Load once, cache, invalidate on reload.
- Log with context — include player names, world names, and coordinates in debug logs. You'll thank yourself when debugging.
- Test on Folia early — if your plugin design is incompatible with regionized threading, it's much harder to fix later.
Automation
CI/CD for Minecraft Servers
You shouldn't be manually uploading JARs to your server via FTP in 2025. Set up a pipeline.
GitHub Actions — Build + Deploy Plugins
# .github/workflows/build.yml
name: Build and Deploy
on:
push: { branches: [main] }
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: 21, distribution: 'temurin' }
- run: mvn package
- name: Deploy via SCP
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
source: "target/MyPlugin.jar"
target: "/server/plugins/"
- name: Reload plugin via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: "screen -S mc -p 0 -X stuff 'plugman reload MyPlugin^M'"
Now every time you push to main, your plugin is built, deployed, and reloaded automatically. No FTP. No manual restart.
Git for Server Configs
Put your entire server directory (except worlds and plugins) in a Git repo. When you SSH in to change a config, commit and push. When you break something, git revert. This is the single best operational change I've made for my servers:
cd /server
git init
echo "world/" >> .gitignore
echo "plugins/*.jar" >> .gitignore
git add .
git commit -m "Initial server state"
# Now every config change is tracked forever
Worlds
World Management at Scale
Big worlds = big problems. Here's how to keep them manageable.
Chunk Pre-Generation
When a player walks into new terrain, the server generates chunks in real-time, causing lag spikes. Pre-generate chunks around spawn so players see smooth terrain:
# Install Chunky plugin
/chunky world world
/chunky radius 500 # pre-gen 500 blocks around spawn
/chunky start
# This will take a while. Run it when server is empty.
# For a 500-block radius: ~31,000 chunks, ~30-60 minutes
World Pruning
Players explore in all directions and the world file balloons to 50GB+ full of empty chunks. Prune unvisited chunks to reclaim disk:
# Install ChunkyBorder plugin
# Set a world border first:
/worldborder set 5000
# Then prune chunks outside the border:
/chunky border pregen # generates border
# Use a pruning tool like MCA Selector (desktop app)
# to remove chunks outside your border
World Border Optimization
- Set a hard world border:
/worldborder set 5000 limits the world to 10km x 10km. Player can't go past it.
- Set the border on day one before players explore. A 5000-block border generates 78 million possible chunks. That's plenty.
- For large networks, use a void world generator for hub/lobby — zero disk usage, zero lag.
- Use Multiverse-Core to separate worlds into different folders for easier backup and pruning.
Backups
Backup Strategies That Don't Fail
"I had backups" — said every admin who restored a backup from 2 weeks ago and lost everything. Here's how to do it right.
3-2-1 Rule
3 copies of your data, on 2 different media, 1 off-site. For a Minecraft server:
- Copy 1: Live data on your VPS (the server itself)
- Copy 2: Hourly backup to a separate volume/disk on the same VPS
- Copy 3: Daily backup to cloud storage (Backblaze B2, S3, rsync.net)
Restic — Backups That Work
Restic is a modern backup tool that does deduplication, encryption, and incremental backups:
# Install: restic
# Init repo:
restic init --repo b2:my-bucket:mc-backups
# Hourly backup script:
#!/bin/bash
restic backup /server/world/ /server/world_nether/ \
--tag hourly --exclude="playerdata/*.tmp" \
--repo b2:my-bucket:mc-backups
# Cleanup old backups:
restic forget --keep-hourly 24 --keep-daily 7 \
--keep-weekly 4 --prune \
--repo b2:my-bucket:mc-backups
# Restore:
restic restore latest --target /restore/mc-world \
--repo b2:my-bucket:mc-backups
# Or mount as FUSE filesystem:
restic mount /mnt/backups --repo b2:my-bucket:mc-backups
Restic's deduplication means your 50GB world with only 2GB of changes backs up in seconds and stores only the delta. Backblaze B2 costs ~$1/TB/month.
What to Actually Back Up
- World folders — world/, world_nether/, world_the_end/ — these are irreplaceable
- MySQL databases — use mysqldump before backup to get consistent snapshots
- Plugin configs — small files, but hours of work if lost. Include in every backup
- Don't bother backing up plugin JARs (re-download), logs (optional), or cache folders
Network
Linux TCP Stack Tuning
If your server is on Linux, default TCP settings are optimized for file servers, not real-time game traffic. These sysctl tweaks reduce latency and improve throughput for Minecraft connections:
# /etc/sysctl.d/99-minecraft.conf
# BBR congestion control (requires Linux 4.9+)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# Increase TCP buffer sizes
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# Faster TCP connection handling
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_syn_backlog = 2048
# Reduce keepalive timings for faster dead-connection detection
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 3
# Apply immediately:
sysctl --system
BBR (Bottleneck Bandwidth and Round-trip time) is Google's congestion control algorithm. It significantly improves throughput for connections with packet loss compared to the default CUBIC. Players on WiFi or mobile networks will see noticeably lower latency spikes.
Hardening
Linux Security Hardening
A Minecraft server is a high-value target. Your VPS will be scanned within hours of going public. These are the hardening steps that actually prevent real attacks.
SSH Hardening (Do This First)
# /etc/ssh/sshd_config
Port 2222 # change from default 22
PermitRootLogin no # never allow root SSH
PasswordAuthentication no # keys only
PubkeyAuthentication yes
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 0
AllowUsers minecraft # only minecraft user can SSH
Fail2Ban — Auto-Ban Attackers
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 14d # ban for 2 weeks
findtime = 10m
[minecraft]
enabled = true
logpath = /server/logs/latest.log
filter = minecraft-auth
maxretry = 5
bantime = 7d
findtime = 10m
# This bans IPs that fail AuthMe login 5 times in 10 min
Auditd — Know Who Did What
# /etc/audit/rules.d/minecraft.rules
-w /server/ -p wa -k minecraft-changes
-w /etc/ssh/sshd_config -p wa -k ssh-change
-a exit,always -S execve -k command-run
If someone compromises your server, auditd tells you exactly what commands were run, what files were changed, and when. This is how you figure out what the attacker did after you kick them out.
Automatic Security Updates
Configure unattended-upgrades to install security patches without manual intervention. Most Minecraft server compromises start with an unpatched SSH vulnerability or a known plugin CVE:
# On Debian/Ubuntu
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
# That's it. Kernel and critical security patches auto-install.
Deployment
Ansible — Infrastructure as Code
When you have multiple VPSs, manually SSHing into each to update configs doesn't scale. Ansible lets you define your entire server setup in YAML files and apply it to any number of servers.
Example Ansible Playbook
# deploy-mc.yml
---
- hosts: mc_servers
become: yes
tasks:
- name: Install Java 21
apt:
name: openjdk-21-jre-headless
state: present
- name: Create minecraft user
user:
name: minecraft
home: /server
shell: /bin/bash
- name: Deploy Paper JAR
copy:
src: paper-1.21.3.jar
dest: /server/paper.jar
owner: minecraft
notify: restart server
- name: Deploy server.properties
template:
src: server.properties.j2
dest: /server/server.properties
- name: Deploy plugins
synchronize:
src: plugins/
dest: /server/plugins/
handlers:
- name: restart server
systemd:
name: minecraft
state: restarted
Run ansible-playbook deploy-mc.yml and all your servers are configured identically. Change one YAML file, re-run, and every server updates. This is how professionals manage infrastructure.
Edge
Cloudflare Spectrum & CDN for Minecraft
Cloudflare's free plan proxies HTTP/HTTPS traffic. Minecraft uses raw TCP (and WebSocket for Eaglercraft), so the free plan won't help. Spectrum is Cloudflare's paid TCP/UDP proxying service.
Cloudflare Spectrum Setup
- Cost: ~$200/month (Spectrum Pro, any protocol)
- Proxies TCP traffic through Cloudflare's global edge network
- Hides your origin IP completely
- DDoS filtering at Cloudflare's edge — scrubs attacks before they reach you
- Works with both raw TCP (Java Edition) and WebSocket (Eaglercraft)
TCPShield (Budget Alternative)
TCPShield offers Minecraft-specific DDoS protection with a free tier:
- Free tier: up to 20 concurrent connections per IP
- Paid tier: unlimited connections, ~$10-20/month
- Supports Eaglercraft WebSocket connections
- Provides a proxy IP that players connect to — yours stays hidden
- Built-in DDoS mitigation for Minecraft protocols
Nginx TCP Reverse Proxy (DIY, Free)
If you can't afford Spectrum or TCPShield, nginx can proxy TCP connections:
# /etc/nginx/nginx.conf
stream {
upstream mc_backend {
server 127.0.0.1:25566; # backend server
}
server {
listen 25565;
proxy_pass mc_backend;
proxy_connect_timeout 30s;
}
}
# This sits in front of your server, adding a layer of
# TCP-level protection and hiding backend ports.
Scaling
Scaling Beyond One Machine
What happens when your VPS can't handle 200 concurrent players? You need distributed architecture.
Horizontal Scaling
- Multiple proxy instances — run 2+ Velocity proxies behind a load balancer (HAProxy or nginx with TCP load balancing). Players connect to the load balancer, which distributes them across proxies.
- Shard your worlds — split your survival world into multiple servers using Multiverse, each responsible for a region. Players move between shards via portals/commands.
- Separate servers by function — minigame servers, PvP arenas, and creative plot servers can each run on different VPSs. The proxy doesn't care where they are — it just routes by server name.
Redis for Cross-Server Data
When servers are on different machines, they can't share an in-memory cache. Redis solves this:
# Install Redis on a lightweight VPS or the same machine
apt install redis-server
# Plugins that support Redis:
# - RedisSync: sync inventories, ender chests, health across servers
# - PremiumVanish: vanish status syncs globally
# - LiteBans: bans sync instantly to all servers
# - CMI: player data stored in Redis for instant access
When to Know You've Outgrown Your Setup
- TPS consistently below 18 even with optimization flags
- Average MSPT above 40ms
- GC pauses longer than 500ms happening every few minutes
- MySQL queries taking longer than 100ms
- You're getting "Out of Memory" kills even with heap within limits
- Players on one side of the world lag while the other side is fine
At this point, it's time to split your server across multiple machines. Start with moving MySQL to its own VPS — that alone fixes most scaling issues.
Pro Gripes
Things Experienced Admins Wish They Knew Sooner
These are the undocumented rules of running a serious server, learned through painful experience:
- Never trust plugin auto-updaters. Always test updates on a staging server first. An auto-update broke CoreProtect's logging for a week on a major network because the new version changed the database schema without warning.
- Your first backup strategy will fail. Test restores monthly, not yearly. Schedule a cron job that actually restores the backup to a temporary directory and verifies the checksums.
- Paper's "anti-xray" is not security. It only hides ores from texture packs. A determined cheater can still find diamonds. Use OreAnnouncer or CoreProtect to detect x-ray instead.
- Don't use Spigot in 2025. Paper is strictly better in every metric. Spigot hasn't been the recommended choice in years. If a plugin requires Spigot, it's almost certainly unmaintained.
- Players lie about lag. When someone says "server is lagging," check TPS before doing anything. 90% of the time it's their internet, not your server. But always check — that 10% is your problem.
- Document everything. When you SSH in at 3AM to fix a critical issue, you won't remember which config file had the log rotation setting. Keep a server wiki or at least a README in your config git repo.
- Your VPS provider will notice high outbound traffic. Minecraft sends a lot of data per player (~50KB/s per player). If you have 50 players, that's ~2.5MB/s outbound. Some budget providers throttle or flag this as abuse. Check your provider's ToS.
- Update Java. Every major Java release has performance and security improvements. Java 21 is significantly faster than Java 17 for Minecraft workloads. Don't be the person running Java 8 because "it's always worked."
- Don't use /reload. In production,
/reload causes memory leaks, duplicate registrations, and undefined behavior. Use a plugin manager like PlugManX for targeted reloads, or restart the server entirely. I've seen a server that used /reload daily for months and it leaked 200MB/day until it crashed every 36 hours.
- If you make your server publicly visible on any list (PlanetMinecraft, etc.), you will get attacked within 24 hours. Have your AuthMe, CoreProtect, DDoS protection, and daily backups ready before you submit. Not after.
What GC flags should I use for a modded server (150+ mods)?
Modded servers have significantly more objects in memory and longer GC pauses. Aikar's flags still apply, but increase MaxGCPauseMillis=300 (modded servers can tolerate slightly longer pauses), set -Xms6G -Xmx6G minimum, and add -XX:G1HeapRegionSize=32M. For large modpacks (200+ mods), consider ZGC with -XX:+UseZGC -XX:ZAllocationSpikeTolerance=2.0 — the concurrent nature of ZGC handles the constant object churn better than G1GC at heap sizes above 8GB.
How do I profile what's causing lag?
Use the Spark plugin: /spark profiler start — let it run for 2-3 minutes while the lag is happening, then /spark profiler stop. It generates a webpage with a flame graph showing exactly which methods are taking the most CPU time. Common culprits: entity AI (too many mobs), redstone (compactors, clocks), paper's chunk system (view-distance too high), or a specific plugin with inefficient loops. Spark also has a heap dump viewer (/spark heapdump) to find memory leaks.
What's the deal with view-distance, simulation-distance, and render-distance?
Three different things: view-distance (server.properties) controls how many chunks the server ticks and sends to players. simulation-distance (Paper config) controls how many chunks around players actually tick (entities, redstone, growth). render-distance (client setting) controls how many chunks the player's client renders. Set view-distance to 6-8, simulation-distance to 4-5, and let players set their own render-distance up to 16. Huge view-distance multipliers server load — 8 is the sweet spot for performance.
How many players can a single server handle?
On a modern CPU (Ryzen 9 7950X, 5.7GHz): ~100-150 players on optimized Paper with Aikar's flags and proper view-distance settings. On a cheap VPS (2 vCPU, 3.5GHz): ~30-50 players. Minecraft server performance is almost entirely single-threaded for player tick handling — clock speed matters more than core count. For 200+ players, you need a proxy-based network that distributes players across multiple backend servers.
Should I use a RAM disk for my world?
Generally no. Minecraft keeps the most active chunks in memory (the chunk cache in Paper). Disk I/O for world loading is mostly sequential and not a bottleneck unless you're teleporting players across unloaded chunks constantly. The risk of a RAM disk is losing the entire world if the server crashes or loses power. SSDs are fast enough — the difference between a RAM disk and NVMe SSD for Minecraft world I/O is less than 5% in practice.
What's the best filesystem for Minecraft servers?
EXT4 on Linux is the safe default. XFS has slightly better performance with large files but Minecraft worlds are many small files (region files are ~1MB each, player data is ~2KB). The filesystem choice matters far less than whether you're on an SSD (mandatory) vs HDD (unacceptable). On Windows, NTFS is fine. Avoid ZFS without ECC RAM — ZFS's checksumming and copy-on-write add overhead that isn't worth it for Minecraft workloads. Avoid Btrfs on production — it's still prone to edge-case filesystem corruption under high write loads.
How do I migrate a world from one server to another?
Stop both servers. Copy the world folder (world/, world_nether/, world_the_end/) to the new server. If you're changing server software (CraftBukkit → Paper), run the new server once, let it convert the world format, then stop and copy over any plugin data. For cross-VPS migration, use rsync -avP --progress user@old-server:/server/world/ /server/world/. For large worlds (50GB+), use rsync --partial so you can resume if the transfer fails. Never copy a world while the source server is running — you'll get a corrupted region file.
How do I handle 10,000+ players on a network?
At that scale, you're not running a Minecraft server anymore — you're running a distributed systems operation. You need: multiple proxy entry points behind Anycast DNS (Cloudflare), auto-scaling backend servers (Kubernetes with Minecraft-specific operators), Redis cluster for cross-server data, MySQL cluster with read replicas, distributed file system for shared worlds (or shard worlds by region), centralized logging (ELK/Loki), and a dedicated DevOps team. Nobody running a network this size is asking Reddit for advice — they've hired people. Start small, scale incrementally, and don't build for 10,000 players until you have 1,000.