Vanilla Minecraft is one app ID and a JAR. A modpack server is a whole stack — modloader, 200+ mod JARs, config files for each, a JVM tuned for the RAM bloat. Most "modpack hosting" tutorials skip the parts that actually break.

This is what I run for a small group on a 200-mod NeoForge pack. Same pattern applies to Forge.

TL;DR:

mkdir /opt/modpack && cd /opt/modpack
wget <server-pack-zip-url-from-curseforge-or-modrinth>
unzip server-pack.zip
chmod +x run.sh   # ships with most modern packs
./run.sh

If the pack ships a run.sh (most modern ones do), it handles modloader install. If not, install Forge/NeoForge yourself first.

Get the right files

CurseForge and Modrinth distinguish between client packs (what your players install) and server packs (what you install). Always grab the server pack. The client pack contains the visual / audio mods you don't need (and which crash a headless server).

On CurseForge: pack page → Files → look for "Server Pack" download button. Modrinth: pack → Versions → download the .mrpack and use Packwiz to bootstrap, OR look for the project's pre-built server zip.

Some packs don't ship server packs at all — they expect you to use ATLauncher or BCC-style auto-bootstrap. In that case, download the modlist manually and copy mods marked "server-side" to your install. Slow but works.

Install — the modern path (run.sh)

NeoForge and recent Forge ship the modpack with an install.sh or run.sh that handles modloader install:

sudo adduser --system --group --home /opt/modpack modpack
cd /opt/modpack
sudo -u modpack unzip server-pack.zip -d .
sudo -u modpack chmod +x run.sh

# First run — bootstraps the modloader, accepts EULA, then exits or runs
sudo -u modpack ./run.sh

First boot downloads the actual modloader libraries (Forge / NeoForge / Mojang server JAR), accepts the EULA prompt (echo "eula=true" > eula.txt if it asks), then either starts the server or exits.

If it exits cleanly, you're set up but haven't started yet — the systemd unit below boots properly.

Install — the manual path (legacy Forge)

If the pack doesn't ship run.sh:

  1. Download the Forge installer for the pack's MC version + Forge version. Pack manifest tells you both.
  2. Run installer:
    java -jar forge-1.20.1-47.4.0-installer.jar --installServer
    
  3. Drop pack mods into mods/. Drop config files into config/.
  4. Start with the generated run.sh or build one:
    #!/bin/bash
    cd "$(dirname "$0")"
    exec java @user_jvm_args.txt @libraries/net/minecraftforge/forge/<version>/unix_args.txt $@
    

The @argfile syntax is Forge's way of separating user args from massive class-path args. Don't try to inline.

RAM — the part nobody warns you about

Vanilla Minecraft server is happy with 2 GB. A 200-mod pack needs 8 GB minimum, comfortable at 12-16 GB. Mods don't only add features — they add startup-time class loading, eager world generators, ticking entities, and dimensional cache.

The classic mistake is -Xmx4G because the box is 8 GB. After 30 minutes of play with 4 players, the JVM is GC-thrashing, TPS drops to 5, players blame "lag". Give it 12 GB:

java -Xms6G -Xmx12G [...]

Ratio matters: Xms near Xmx (within 2x) avoids heap-resize stalls. JVMs grow heap reluctantly under modded load.

JVM flags — Aikar's are still the move

Standard tuned flags for Minecraft (originally Aikar's, evolved by various contributors):

-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

Drop them into user_jvm_args.txt next to run.sh, or paste into the systemd ExecStart. They tune G1GC for "low pause, high throughput" — exactly the workload modded MC is.

Systemd unit

/etc/systemd/system/modpack.service:

[Unit]
Description=NeoForge Modpack Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=modpack
Group=modpack
WorkingDirectory=/opt/modpack
ExecStart=/opt/modpack/run.sh nogui
Restart=on-failure
RestartSec=15
RuntimeMaxSec=86400
MemoryMax=14G

[Install]
WantedBy=multi-user.target

RuntimeMaxSec=86400 — daily restart. Modded JVMs accumulate metaspace pressure that GC doesn't reclaim. After ~30 hours, world generation lags noticeably. Daily restart prevents.

MemoryMax=14G (slightly above your -Xmx) — hard cap. JVMs occasionally exceed heap due to off-heap allocations; this stops the kernel OOM-killer from picking the wrong process. systemd unit reference.

Enable and watch:

sudo systemctl daemon-reload
sudo systemctl enable --now modpack
sudo journalctl -u modpack -f

First boot of a 200-mod pack takes 5-15 minutes. Don't panic. Look for Done (XXXs)! For help, type "help" — that's actually-ready.

Ports

sudo ufw allow 25565/tcp

Minecraft Java is TCP only. UFW rules.

If you're running multiple modpack servers behind a Velocity proxy, bind each backend to 127.0.0.1 and let the proxy be the only public-facing port.

Backups — bigger than you think

Modded saves are huge. World folders for a 200-mod pack hit 5-10 GB after a month. The files that matter:

  • world/ — the entire directory
  • config/ — pack config (don't lose tweaks)
  • defaultconfigs/ — pack-distributed defaults

Nightly:

systemctl stop modpack
rsync -a --delete /opt/modpack/world/ /backup/modpack/world/$(date +%F)/
rsync -a --delete /opt/modpack/config/ /backup/modpack/config/$(date +%F)/
systemctl start modpack

Stop-rsync-start is mandatory for modded saves. A live rsync of a 5 GB modded world catches mid-write chunk files about 1 in 20 attempts. The 60-second downtime beats restoring a corrupt backup. rsync rotation pattern.

Keep 14 days. Modded worlds are precious — players invested 100+ hours in those bases.

Updates

Modpack updates are not Minecraft patches. They're the modpack author bumping mod versions, sometimes the modloader version. The procedure:

sudo systemctl stop modpack
cd /opt/modpack
# Backup current state
sudo -u modpack tar czf ../modpack-pre-update-$(date +%F).tar.gz mods/ config/

# Replace mods/ and config/ with the new server pack contents
# (each pack ships an update procedure — read it before)

sudo systemctl start modpack

After updating, watch the log carefully — mod incompatibilities fail at JVM init, not at runtime. If anything's wrong, the server doesn't start. Roll back from the tarball, you're back in 30 seconds.

The two failure modes you'll hit

"Server crashed on startup with mixin errors." Mod conflict — a dependency mod is missing or wrong version. Look at the latest crash log under crash-reports/. Mixin failures are loud and tell you which mod crashed which.

"TPS drops to 5 after 20 minutes." Mod doing dumb tick work — heavy entity (mob farms, chunkloaders), inefficient block tick, world generator looping. mods console command on most modloaders shows tick time per mod. Find the offender, ban or replace.

For comparison with vanilla and a per-modloader breakdown, see Minecraft server software comparison — that post covers when Forge / NeoForge / Fabric / Paper each make sense.

When to host this yourself vs pay for hosting

A 12 GB modpack server on Hetzner CPX31 runs €15/month — same machine as my ARK and Enshrouded game-server pricing. Managed modpack hosting is €10-15/month for a single 8-12 GB instance, with patch latency and limited mod control.

If you want to run your mod tweaks (curated mods, custom recipes via KubeJS, etc.), self-host. If you just want vanilla-plus pack hosted, paid hosting saves you the systemd unit. For homelab + friends, self-host wins on price and flexibility.


Related posts