← Back to Wiki
Game Servers / Minecraft

Self-Hosting Minecraft Java with a Modern Fabric Modpack

Running Minecraft Java on a current version with an actively-maintained superhero-themed modpack, as a companion to an older server frozen on a legacy Forge modpack that can never be updated (see that guide for why, and the Bedrock guide if you're running both editions). Fabric on 1.20.1 turned out to need real dependency-resolution work, and produced one crash with two completely unrelated causes that happened to share the exact same error string.

Share on X

Why a second server instead of upgrading an existing one

A legacy Forge modpack frozen on Minecraft 1.7.10 has no upgrade path — the mod that makes it worth running was never ported past that version, confirmed directly against the mod's own CurseForge and Modrinth project pages (third-party mod-listing sites can and do claim broader version support than is actually true). Rather than lose the theme entirely, this is a from-scratch build on a current version with the closest actively- maintained equivalent instead of an attempted in-place upgrade.

Storage: put I/O-heavy new builds on NFS, not local disk

This server's LXC was deliberately built on NFS-backed storage over a fast wired link to a NAS, rather than the same local ZFS pool other VMs and containers on the same hypervisor share. The reason wasn't theoretical: heavy sustained writes from an unrelated large-file export job on another VM sharing that local pool had, the same night, caused enough I/O contention to crash a completely different game server also sharing it — kicked connected players with a generic "server down" error even though the process itself never restarted.

Shared local storage is a real, easy-to-miss failure mode. Two VMs with zero logical relationship to each other can still take each other down if they share the same physical disk pool and one of them does a sustained heavy write. If you're about to build a new VM/LXC that will do meaningful sustained I/O (a game server's world saves, a model export, anything writing gigabytes continuously), consider putting it on separate storage from your existing always-on workloads rather than the default local pool everything else already lives on.

The server itself

Runs on the itzg/minecraft-server Docker image, no special Java-version pinning needed — 1.20.1 requires Java 17+ anyway, which the image's default tag already bundles:

services:
  mc:
    image: itzg/minecraft-server
    container_name: mc-modern
    ports:
      - "25565:25565"
    environment:
      EULA: "TRUE"
      TYPE: "FABRIC"
      VERSION: "1.20.1"
      MEMORY: "4G"
    volumes:
      - ./data:/data
    restart: unless-stopped
    stdin_open: true
    tty: true

Resolving a modpack's full dependency tree via the Modrinth API

Modrinth's CDN allows direct, unauthenticated downloads — unlike CurseForge, which blocks straightforward scraping. The project's version API resolves the latest compatible release and its declared dependencies:

curl -s "https://api.modrinth.com/v2/project/<slug>/version" \
  -H "User-Agent: your-app-name/1.0"
Gotcha: a mod's own platform dependency can have a confusingly generic name. One dependency in this pack's tree was literally just called the base name of the mod family, distinct from — but easy to confuse with — the addon mod itself. Resolve every dependency ID individually via /v2/project/<id> rather than assuming the version list's dependency names are self-evident; it's an easy way to end up one jar short.

The crash with two unrelated causes: Unknown recipe serializer

A player hit a decoder crash on connect referencing an unknown recipe serializer from a weapons-mod dependency. It looked like a client-side mod mismatch — the actual first cause was server-side.

Cause 1: a missing "extensions" directory, distinct from the mods folder

The weapons mod in question expects an optional third-party content-pack directory separate from the normal mods folder, and it didn't exist. The pack's own item tags reference dozens of specific weapon IDs that only exist if the matching content packs are installed — since the directory was missing, none of those items registered, breaking enough of the recipe/tag graph that syncing recipes to a connecting client threw a decoder error.

An ERROR-level log line at boot, not a crash, hid the real cause. The missing-directory error only appeared as a non-fatal stack trace during startup — easy to scroll past — and only surfaced later as an unrelated-looking client-side decode error. For any mod with an optional "extensions" or "content pack" system separate from the main mods folder, check the server log for this class of error before assuming a client-side mismatch.

Fix: created the expected directory and dropped the missing content-pack archives directly into it (as zips — some loaders scan the archives directly rather than expecting them unpacked), then restarted the container. Server log went from dozens of missing-reference errors to a clean boot.

Cause 2: the same error string, from a genuinely separate root cause

After the extensions fix, a different player hit the exact same error message again — but this was a second, independent cause that happened to produce identical output. A modded Fabric server syncs its recipe data to every connecting client automatically, but a custom recipe serializer (the code that knows how to parse a given recipe type) only exists if the client also has the mod that registers it. The extensions fix made the server's recipe graph more complete — which meant more valid recipes than before were now reaching clients, making a pre-existing gap in client-side setup instructions show up more reliably than it had before.

Lesson: don't assume "modern Minecraft version" means "no client-side setup." Whether a server needs matching client-side mods depends entirely on whether it's modded, not on how recent the Minecraft version is — those are two separate questions, and conflating them produced genuinely wrong setup instructions that stayed wrong until a second player hit the same crash for a different reason.

The real fix here wasn't a server change at all — it was writing correct client setup documentation: bundling the exact mod jars the server actually runs into a single distributable zip, matched to the exact Fabric loader version running server-side (pulled directly off the server, not guessed from an installer version number).

Benign warnings that aren't bugs

Expect to see @Mixin target ... was not found warnings on boot for client-only rendering classes that simply don't exist in a headless dedicated server — mods built for both client and server gracefully skip these mixins server-side. Not a sign of a broken install; only worth investigating if the server actually fails to reach a clean Done (...) boot line.