Plugins or mods: the only decision that really matters
Everything else follows from one question. Does the person joining your server have to install anything?
Plugins are server-side only. They run on Bukkit-family software (Paper, Spigot, Purpur) and change how the vanilla game behaves: land claims, economies, permissions, minigames, anti-cheat, warps. They do it with blocks and items that already exist in the game, so players connect with a stock client. That is why nearly every public server with a shop and a /home command runs Paper.
Mods add content. New blocks, machines, entities, dimensions, recipes. That content has to exist on both ends of the connection, so a mod loader (NeoForge, Forge or Fabric) loads it on the server and the same loader with the same mod list loads it on every client. There is no way around this for content mods. The server sends its registries at login and the client is rejected if it cannot match them.
A mod loader cannot run plugins and Paper cannot run content mods. They patch the game at different points and expose different APIs. If you want both, you are looking at hybrid software, which has real costs.
If your idea is really vanilla plus quality of life, check whether a datapack already does it. Datapacks are server-side, need no client install and cost nothing.
- Plugins: server-side only, vanilla clients, run on Paper, Spigot or Purpur
- Mods: content on both sides, need NeoForge, Forge or Fabric plus a matching mod list on every client
- Datapacks: server-side vanilla scripting, no client install, no extra server software
- Client-only mods such as Sodium, Iris and minimaps work on any server including Paper
Paper: the default answer for plugin servers
Paper is a fork of Spigot, which is a fork of Bukkit. It keeps the Bukkit plugin API, adds its own extended API on top, and patches the vanilla server heavily for performance: async chunk loading, cheaper entity handling and several hundred behaviour toggles split across config/paper-global.yml and config/paper-world-defaults.yml. Effectively every plugin on SpigotMC, Modrinth or Hangar targets it.
What Paper cannot do is load a mod. There is no Forge or Fabric compatibility layer inside it and there is not going to be one.
Two forks are worth knowing about. Purpur is Paper plus a large pile of extra gameplay toggles and is drop-in compatible with Paper plugins. Folia is Paper's regionised multithreading fork: it ticks independent regions of the world in parallel and genuinely scales to very high player counts, but plugins have to be written for its threading model and most are not. Do not run Folia until you have checked every plugin you depend on.
One migration trap. Paper dropped the versioned CraftBukkit package relocation during the 1.20.5 cycle, which broke plugins that reflected into versioned NMS packages. Abandoned plugins from before that often refuse to load on current builds, so look for an explicitly tested version on the plugin page rather than a vague "1.20+".
- Config lives in config/paper-global.yml and config/paper-world-defaults.yml since 1.19
- Purpur is Paper plus extra toggles and stays plugin-compatible
- Folia scales by regionising the tick loop but needs Folia-aware plugins
- Geyser plus Floodgate lets Bedrock clients join a Paper server; leave online-mode set to true
Forge and NeoForge: what the split changed
Forge is the oldest loader and the one most large content mods were originally written against. In 2023 the project split: most of the active development team formed NeoForge, and from Minecraft 1.20.2 onwards that is where the ecosystem moved. In practice 1.20.1 remains a very large Forge modpack version that people still run deliberately, while 1.21.x packs are overwhelmingly NeoForge. Forum threads titled "Forge vs Fabric" that predate the split are the ones to stop reading.
Installation is not a single jar. Since 1.17 the Forge and NeoForge installers in server mode produce a libraries tree, run.sh and run.bat scripts, and a user_jvm_args.txt where your memory flags belong. Panels and hosts that expect one server.jar need a startup command pointing at the generated argument file instead, and this is the most common reason a Forge server refuses to boot on managed hosting.
NeoForge version numbers track the game version: 21.1.x is for Minecraft 1.21.1, 20.4.x for 1.20.4.
Java matters here. Forge on 1.20.1 wants Java 17; 1.20.5 and later need Java 21. The wrong one gives UnsupportedClassVersionError with a class file version, where 61 means Java 17 and 65 means Java 21.
- 1.20.1 is Forge territory; 1.20.2 and later are NeoForge territory
- No fat server jar since 1.17: expect run.sh, run.bat and user_jvm_args.txt
- NeoForge 21.1.x targets Minecraft 1.21.1, and so on
- Forge and Fabric mods are never interchangeable, and Forge and NeoForge mods often are not either
Fabric: lighter loader, faster updates, a different mod set
People confuse two separate things here. Fabric Loader is small, version-agnostic and usually running on a new Minecraft snapshot within days of release. Fabric API is a separate mod that provides the hooks most Fabric mods actually call, and it is tied to the game version. If a mod fails with a missing dependency on "fabric", that is the API and not the loader.
The Fabric server launcher is a small jar that fetches the vanilla server on first run, which makes the install noticeably simpler than Forge's.
Fabric also has the strongest server-side performance mod set: Lithium for tick optimisation, FerriteCore for memory reduction, C2ME for parallel chunk generation. If you are pregenerating a large world, C2ME is the single biggest wall-clock saving available on any loader.
Watch sidedness. Fabric mods declare an environment in fabric.mod.json as client, server or both, and Modrinth shows client and server as required or optional on every project page. Uploading a client-only mod such as a shader loader or a minimap to the server is a very common crash cause.
Quilt still exists as a Fabric fork, but the ecosystem consolidated back on Fabric. Do not plan a new server around it.
- Fabric Loader is version-agnostic; Fabric API is not, and most mods need it
- Server jar is a launcher that downloads the vanilla jar on first start
- Lithium, FerriteCore and C2ME are the standard server-side performance trio
- Check the client/server flags on Modrinth before uploading anything to /mods
Hybrids and proxies: when you want plugins and mods together
Mohist, Arclight, Magma and Ketting bolt the Bukkit API onto Forge or NeoForge. Cardboard does something similar on Fabric. Sinytra Connector goes the other direction and runs Fabric mods on NeoForge. They all work, sometimes, for some combinations.
The cost is support. The Paper project does not support hybrids and will close your bug report, and plenty of mod authors take the same position. When a plugin writes to a block whose registry entry is owned by a mod and the two desync, you are debugging it yourself on a live world. That is a real operational cost, not a theoretical one.
If what you actually need is plugin-style administration on a modded server, look at mod-native tools first. LuckPerms ships builds for Bukkit, Fabric, NeoForge, Velocity and BungeeCord and can export and import its data between them. spark profiles on all of them. Ledger and similar mods cover block logging on Fabric.
The other architecture worth knowing: a Velocity proxy in front, a Paper hub for anything plugin-shaped, and a separate NeoForge or Fabric server for the modded world behind it. Players only need the mods while connected to the modded backend. More moving parts, but every piece is supported software.
- Hybrid software is unsupported by both upstreams; budget debugging time
- LuckPerms and spark exist on Bukkit, Fabric, NeoForge and Velocity, which covers most admin needs
- Velocity proxy plus a Paper hub and a modded backend is the maintainable version of "both"
What actually breaks when you switch
Nothing crosses the plugin and mod line. Not one jar in /plugins will load on a mod loader, and not one mod in /mods will load on Paper. Budget for finding replacements rather than porting anything.
Removing mods from a world that used them does not fail cleanly. Modded blocks in already-generated chunks become air, modded block entities disappear and modded items sitting in chests or player inventories are deleted on load. A tech-mod base becomes a hole in the ground. This is the most expensive surprise in a Forge-to-Paper move and the reason to test on a copy.
Version downgrades are impossible. A world opened once in 1.21 cannot go back to 1.20, because the data fixer only runs forwards. Upgrade a copy, always.
Modpacks need care. A CurseForge client zip is not a server install. Use the pack's published server pack if there is one, and strip client-only mods such as OptiFine, shader loaders, minimaps and ReplayMod. Skipping that step produces the classic "works in single-player, crashes on the server" report.
Permissions, economy balances and land claims do not migrate themselves either. Export the data before you rebuild anything, not after.
- Wrong Java version: UnsupportedClassVersionError, class file 61 = Java 17, 65 = Java 21
- Client mod list mismatch: the login handshake rejects the client before the world loads
- Missing Fabric API: mods fail with an unresolved "fabric" dependency
- Client zip uploaded as a server pack: crash on startup from client-only mods
Migrating an existing world between loaders
Stop the server before you copy anything. Region files are written lazily, so copying a running world gives you half-written chunks that fail to load days later.
The folder layouts differ, and this is where most migrations go wrong. A vanilla, Forge or Fabric server keeps everything under one folder: world/level.dat, world/region, world/entities and world/poi, with the Nether at world/DIM-1 and the End at world/DIM1. Modded dimensions sit under world/dimensions/modid/name.
Bukkit-family servers split them into three. Paper expects world/region for the overworld, world_nether/DIM-1/region for the Nether and world_the_end/DIM1/region for the End.
So moving to Paper means copying world, creating world_nether and moving the DIM-1 folder inside it, then creating world_the_end and moving DIM1 inside that. Moving from Paper to Fabric or NeoForge means reversing it and putting DIM-1 and DIM1 back inside world. Keep level.dat with the overworld and check level-name in server.properties.
If you are dropping mods, do not leave the old chunks as swiss cheese. Open the world in MCA Selector, delete the chunks that held modded content so they regenerate, then pregenerate the border with Chunky before players come back.
- Vanilla/Forge/Fabric: world/, world/DIM-1, world/DIM1, world/dimensions/modid/name
- Paper: world/, world_nether/DIM-1, world_the_end/DIM1
- entities/ and poi/ folders exist from 1.17 onwards and must travel with the world
- Java and Bedrock world formats do not convert to each other in either direction
RAM, CPU, and when more RAM does nothing
The Minecraft server ticks the world on a single thread. Paper moves chunk I/O and some work off it and Folia regionises it, but entity and block-entity ticking is fundamentally serial. Single-core clock speed and memory latency set your ceiling, not core count and not RAM. A high-clock desktop-class chip beats a many-core server part on this workload, which is why so much of the hosting industry runs consumer CPUs.
Watch MSPT rather than RAM. Twenty TPS is the ceiling; MSPT is the real work done per tick and anything under 50ms is healthy. Install spark before you need it, then use the profiler to find the mod or plugin actually eating the tick.
Rough heap sizing: Paper with a dozen plugins and 20 to 40 players is comfortable in 3 to 4GB. A 150-mod pack wants 4 to 6GB. A 300-plus mod kitchen-sink pack realistically wants 8 to 10GB.
Set -Xms equal to -Xmx and leave 0.5 to 1GB of the container outside the heap for metaspace, thread stacks and direct buffers. On a 4GB plan that means -Xmx3G. Exceeding the container limit gets the process killed with no Java stack trace, which reads as a random crash.
More RAM will not fix low TPS.
- view-distance 10 loads 21x21 = 441 chunks per player; view-distance 6 loads 13x13 = 169, a 62% cut
- simulation-distance drives mob, redstone and machine load; 4 is fine for most servers
- Aikar's G1GC flags are still the sane default; do not paste a 30-flag string from a 2016 thread
- Pregenerate with Chunky so exploration is not competing with the tick loop
Where to run it, including on hardware you already own
Honest answer first. For two to six friends playing a couple of evenings a week, you do not need to buy anything. A desktop with 16GB of RAM and a recent CPU will host a 100-mod pack while the owner plays on the same machine. Use Tailscale or a similar overlay network rather than port forwarding, so you are not publishing your home IP or opening a router port to the internet.
What paid hosting buys is the server being up when your PC is not, a stable address, DDoS filtering in front of it, automated backups, sub-user access for your admins and somebody else's upload bandwidth. A residential upload of around 10 Mbps carries six players comfortably. It does not carry forty at view-distance 10.
For a concrete reference point: Puranode's Minecraft plan is a single 4GB SKU at 10.99 EUR per month including VAT, with the panel handling Paper, Forge, NeoForge and Fabric jar selection, SFTP, a web file editor and scheduled backups, and a server usually online about 60 seconds after checkout. The signup price is the renewal price. If a pack outgrows 4GB, a RAM change is a manual same-day resize on request rather than a self-serve slider.
- Small private pack, occasional play: self-host and use an overlay network, no purchase needed
- Public server, 24/7 uptime, many players: rent hardware with filtered bandwidth
- Whatever you run on, take backups off the box itself, not just to the same disk