/ for the greybeards

Advanced Server Engineering (for people who like pain)

You already know how to set up a server. You've done it before. This section is for when you're ready to build something that doesn't fall apart at 50 players and doesn't make you look like an amateur.

JVM Tuning
Docker
Monitoring
DB Scaling
Plugin Dev
CI/CD
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.

/ faq

Advanced FAQ

Questions people ask when they've already outgrown the beginner guides.

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.