Use case · Game server

Luanti server on a whole Linux machine

A Luanti server is the luantiserver program (Luanti was called Minetest until its rename) running one world for one game, listening on UDP port 30000 by default; the project ships an official container image, and since 5.8.0 you supply the game yourself. This page sizes the machine from the Luanti docs, installs 5.18.0 with VoxeLibre, claims the admin account before strangers can, lists the world publicly, and backs it up without stopping it.

See the game servers

What the Luanti docs ask for

The Setting up a server page on docs.luanti.org gives one sizing, aimed at about 10 players on a game and mod set that is not demanding. It also says Luanti is heavily single-threaded, so core clock matters more than core count.

ResourceLuanti docs, about 10 playersWhen to go higher
CPUAt least 2 cores, preferably 44 to 8 cores if you turn on multithreaded map generation
Memory4 GBHeavier games such as VoxeLibre or Mineclonia, and big mod sets
Storage30 GB on SSD, preferably NVMeGrows as players explore; a map size limit slows it
Network20 MbpsSettings that send more map data to each player

The same page recommends a VPS or dedicated server for a public world over a home line: residential links are less reliable, upload is slower, and a server behind CGNAT cannot be reached at all. The in-game Host Server button is fine for a few friends while the client stays open; a public world wants the standalone server.

The machine for it

Our game server line is chosen by one-thread speed: every processor on it carries a PassMark single-thread rating of 2,000 or higher, the figure the Satisfactory dedicated server documentation asks for. Luanti publishes no score of its own; the docs only say faster clocks help.

  • One world for a group or a small community: the Intel Xeon E3-1585L v5 entry build, 4 cores and 8 threads, 32 GB, a 240 GB NVMe drive and a 1 Gbps port, sold in all five cities. That is well above the docs' 4 GB, on the NVMe they prefer. It is an HPE Moonshot m710x cartridge, one of the two cartridge lines with a full browser console.
  • Several worlds or a heavy modded game: the Intel Xeon E5-2667 v4 with 8 cores, or a Xeon Gold line with 12 to 20. Each server takes its own port and its own main thread.
  • Big maps: add NVMe or memory on the order form; storage is what grows.

The whole machine is yours: no other tenant on the cores, root over SSH, and an unmetered port, which matters when every joining player downloads the game's media. Every rack server and blade, plus the m510 and m710x cartridges, has its own out-of-band console, for the day a firewall rule leaves SSH unreachable. Pick the city from the test IPs and speed tests on the data centers page. Ready servers take about 30 minutes from payment approval to handover. Anything configured to your spec is built to order, and that takes 4 to 24 hours.

Installing with the official image

The docs' Linux page names four routes: the distribution's package (often several releases behind, they warn), prebuilt third-party headless binaries, a source build, or the container image at ghcr.io/luanti-org/luanti. Older packages and guides call the program minetestserver; current builds still create that name as an alias (src/CMakeLists.txt). The image is built by the project itself, so it is used here on Ubuntu 24.04, pinned to 5.18.0 rather than latest: an upgrade becomes a decision, not a surprise. The compose file's restart: unless-stopped relaunches luantiserver when it dies or the machine boots, the job a systemd unit or the docs' restart loop would otherwise do.

  1. Install Docker (the Engine and the Compose plugin, per docs.docker.com) and the git, sqlite3 and rsync packages.
  2. Edit YourName and Your world in the block below, then paste it as root (sudo -i). Its first line switches ufw on with SSH and 30000/udp allowed, before anything listens. It refuses to go further if a config already exists, since re-running it would generate a different join password.
  3. Note the password it prints: you need it for the next step.
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw && ufw allow OpenSSH && ufw --force enable && ufw allow 30000/udp &&
if [ -e /srv/luanti/config/minetest.conf ]; then echo "already set up: nothing changed"; else
mkdir -p /srv/luanti/data/.minetest /srv/luanti/config /srv/luanti/games /srv/luanti/mods &&
chown -R 30000:30000 /srv/luanti/data &&
git clone --depth 1 https://git.minetest.land/VoxeLibre/VoxeLibre /srv/luanti/games/voxelibre &&
p=$(openssl rand -hex 16) && [ -n "$p" ] &&
install -m 600 -o 30000 -g 30000 /dev/null /srv/luanti/config/minetest.conf &&
printf '%s\n' 'name = YourName' 'server_name = Your world' 'server_announce = false' \
  'port = 30000' 'disallow_empty_password = true' "default_password = $p" \
  'enable_rollback_recording = true' > /srv/luanti/config/minetest.conf &&
cat > /srv/luanti/compose.yaml <<'EOF' &&
services:
  luanti_server:
    image: ghcr.io/luanti-org/luanti:5.18.0
    restart: unless-stopped
    volumes:
      - ./config:/etc/minetest:ro
      - ./data:/var/lib/minetest
      - ./games:/var/lib/minetest/.minetest/games:ro
      - ./mods:/var/lib/minetest/.minetest/mods:ro
    ports:
      - "30000:30000/udp"
    command: --config /etc/minetest/minetest.conf --gameid voxelibre --world /var/lib/minetest/.minetest/worlds/world
EOF
docker compose -f /srv/luanti/compose.yaml up -d && echo "first-join password: $p"; fi

Paths, mounts, the UID 30000 ownership and the VoxeLibre clone follow the docs' Docker / Container page; the image's Dockerfile creates that minetest user and runs luantiserver as it. Our changes to the docs' compose file: the pinned tag, and the command line from their second example with --world and a full path in place of --worldname, which only finds a world that already exists, so a fresh install would stop there. The config settings are ours, all named in minetest.conf.example: with default_password set, a new account can only be created by someone who knows it.

On the firewall: Docker-published ports sidestep ufw for IPv4 traffic. Over IPv6 the same bypass happens only with Docker's IPv6 mode enabled; without it, ufw filters the port, and the 30000/udp rule lets players through.

Claiming admin before anyone else

Luanti gives every privilege, irrevocably, to the player whose name matches the name setting. Accounts are created on first join, so on an open server whoever gets there first with that name owns it. The join password closes that gap.

  1. In the Luanti client, join your server's IP on port 30000 as YourName with the printed password. Your account is created with that password and full privileges; /privs shows them.
  2. As root, edit /srv/luanti/config/minetest.conf: delete the default_password line, set server_announce = true, and add server_description and motd if you like. Set server_address only when you have a domain whose DNS resolves to this machine; the docs say a wrong one gets the server rejected from the list.
  3. Restart with docker compose -f /srv/luanti/compose.yaml restart, since minetest.conf is read again only on restart.
  4. New players now register with a password of their own; disallow_empty_password stops anyone joining without one.

Mods, privileges and moderation

Put a mod in /srv/luanti/mods, then enable it for the world with a line such as load_mod_areas = mods/areas in world.mt, which sits in /srv/luanti/data/.minetest/worlds/world. That file appears after the first start; edit it as root. Newly installed mods are disabled for every world until you do. Restart the server afterwards.

  • New players get interact and shout (default_privs). Grant more with /grant, and never because a name looks familiar: names are not reserved between servers.
  • The docs list a protection mod such as areas, a ban manager such as xban2 and a chat filter as the usual kit; the Server Moderation and Tools tag on ContentDB has more.
  • enable_rollback_recording lets you see and revert who placed a node. It cannot undo damage done by mods, so it does not replace backups.
  • Never disable mod security, and add a mod to secure.trusted_mods only if you trust its code.

Backups that do not stop the world

A default world is a folder of SQLite files (map, players, auth, mod storage) plus world.mt and a few text files. The Backup Solutions page warns that copying the database files while the server writes will likely give a corrupt copy, and recommends sqlite3 ... "VACUUM INTO ..." for live snapshots. The script below, ours, applies that to every database in the world and archives it with the config, compose file, games and mods. Run as root:

install -d -m 700 /root/luanti-backups
cat > /usr/local/sbin/luanti-backup.sh <<'EOF'
set -euo pipefail
shopt -s nullglob
umask 077
W=data/.minetest/worlds/world
S=$(mktemp -d)
trap 'rm -rf "$S"' EXIT
rsync -a --exclude='/*.previous' --exclude='/*.bad' --exclude='*.sqlite' --exclude='*.sqlite-*' /srv/luanti/ "$S/"
for db in /srv/luanti/$W/*.sqlite; do
  sqlite3 -cmd '.timeout 20000' "$db" "VACUUM INTO '$S/$W/$(basename "$db")'"
done
chown 30000:30000 "$S/$W"/*.sqlite
out=/root/luanti-backups/luanti-$(date +%F-%H%M%S).tar.gz
tar -czf "$out" -C "$S" .
tar -tzf "$out" > /dev/null
find /root/luanti-backups -name 'luanti-*.tar.gz' -mtime +13 -delete
EOF
printf '%s\n' '15 4 * * * root bash /usr/local/sbin/luanti-backup.sh >> /var/log/luanti-backup.log 2>&1' > /etc/cron.d/luanti-backup
bash /usr/local/sbin/luanti-backup.sh && ls -l /root/luanti-backups

Copy the archives off the machine: from another host, pull /root/luanti-backups with plain rsync -a, never --delete, and remove old archives on that host by its own schedule. The docs still advise a cold backup now and then, with the server stopped, for a copy that is consistent across all files.

To restore, as root, set B to the archive you want. The block checks it reads, refuses if an earlier set-aside copy is still there, stops the server, moves the current world and config aside as data.previous and config.previous, and unpacks both from the archive.

B=/root/luanti-backups/luanti-2026-10-30-041500.tar.gz
cd /srv/luanti && tar -tzf "$B" > /dev/null &&
if [ -e data.previous ] || [ -e config.previous ]; then echo "data.previous or config.previous exists: move it away first"
else docker compose -f /srv/luanti/compose.yaml stop &&
  mv data data.previous && mv config config.previous &&
  tar -xzf "$B" --numeric-owner ./data ./config && chown -R 30000:30000 data &&
  docker compose -f /srv/luanti/compose.yaml start && echo restored; fi

If the restored world is the wrong one, roll back as root: `cd /srv/luanti && [ -e data.previous ] && [ -e config.previous ] && [ ! -e data.bad ] && [ ! -e config.bad ] && docker compose -f /srv/luanti/compose.yaml stop && mv data data.bad && mv config config.bad && mv data.previous data && mv config.previous config && docker compose -f /srv/luanti/compose.yaml start`. Delete the .previous or .bad folders once you are sure, or the next restore will refuse to run.

Updates and limits

To move to a new Luanti release, run the backup, then edit the image tag in compose.yaml, then docker compose -f /srv/luanti/compose.yaml pull and up -d. Update the game with git -C /srv/luanti/games/voxelibre pull and restart. Read both projects' release notes before you upgrade.

Leave the map on SQLite unless you have a reason. The docs rate LevelDB's reliability as bad, with corruption on unclean shutdowns, and suggest a PostgreSQL database for very large maps, set through world.mt and moved with --migrate. A world meant for a handful of friends for one evening does not need a machine at all: the client's Host Server button does that. If the group really wants Minecraft or Vintage Story rather than a Luanti game, the Minecraft server and Vintage Story server pages cover those.

Questions

Is Luanti the same as Minetest?

Yes. The docs describe Luanti as the free and open-source voxel game engine formerly called Minetest. Current server builds are named luantiserver, while the config file is still minetest.conf and the container still keeps its data under /var/lib/minetest.

Which port does a Luanti server use?

UDP 30000 by default, set with port in minetest.conf. The docs suggest counting upwards (30001, 30002) for each extra server on the same machine. Nothing in the setup above needs a TCP port open.

Why does the server not start a game on its own?

Since 5.8.0 Luanti ships without a default game. You download one, such as VoxeLibre or Minetest Game from ContentDB, into the games folder and pass its folder name to --gameid when the server starts.

How many players can one machine hold?

The docs size 4 GB and 2 to 4 cores for about 10 players on a light game; max_users defaults to 15. Heavier games and mods raise memory use, and the main loop is single-threaded, so watch tick lag as numbers grow before adding players.

How do I get my server on the public list?

Set server_announce = true plus server_name and server_description, then restart. Add server_address only when you own a domain set up for this machine; otherwise leave it out, or the list rejects the entry.

Can one machine run several Luanti worlds?

Yes. The docs keep each server's world, config and log separately, one directory per server, and count ports upwards from 30000. With the image, that means one compose project per world, each with its own folder and port; that layout is ours. Each extra world mainly costs one more busy core and its share of memory and disk.

Run the world on its own machine

Single-tenant servers rated for one-thread speed, with root, an unmetered port and five cities to choose from.

See the game servers

Related on Server Room

Other use cases