Will a modest server keep up?
Yes, for most groups. The Luanti docs aim their suggested specs at roughly 10 players at once on a game and mods that are not heavy, and the numbers are small:
- Processor: 2 cores at least, 4 preferred. Luanti does most of its work on one thread, so a faster core helps more than extra ones. If you let map generation use several threads, the docs suggest 4 to 8 cores.
- Memory: 4 GB, more for heavier games like Mineclonia or VoxeLibre and for big mod lists.
- Disk: 30 GB on an SSD, NVMe if you can. It keeps growing as players explore new ground.
- Connection: 20 Mbps, more if you raise how much map each player is sent.
Those docs also steer public servers away from home connections, which tend to be less reliable with slower upload, and point out that behind CGNAT nobody outside can reach you at all.
Why your world wants a machine of its own
Because the main loop leans on one core, that core is what your players feel. On a dedicated server it is never shared with another customer, and neither are the memory or the disk. The port is unmetered, so when a new player arrives and downloads the game's textures and sounds, nothing is counted. And you have root, so the game, the mods and the version are your call.
The server that fits
Start from the ranked game server table. Each processor in that table scores 2,000 or more in PassMark's one-thread benchmark, the bar Satisfactory's dedicated server guide sets; Luanti itself names no score, but it rewards the same fast core.
- A world for your friends, or a modest public one: the E3-1585L v5 from Intel in its entry build, sold in all five cities: four cores (eight threads), which meets the docs' preferred four, with far more memory, disk and bandwidth than they ask for.
- A busy server, a few worlds at once, or a modpack-heavy game: a processor lower in the table with more cores, so each world gets its own fast thread and plenty of memory.
- A map that keeps growing: add disk space on the order form, or add a drive later as an upgrade.
Pick a machine that is already racked and it is yours about 30 minutes from the moment your order is approved; the payment check comes before that and is the part that varies. Building one to order means 4 to 24 hours if we already have the parts, and more when a part is bought in for you. You can open a remote console yourself on rack servers and blades, and on two cartridge lines, m510 and m710x, handy if a firewall rule ever locks you out. To choose a city, the in-browser timer on the game servers page measures your round trip, and our data centers page has a test IP and a speed test for each city.
Up and running in an evening
The Luanti docs list four ways to get the server on Linux: your distribution's package, which can lag several releases (older ones name the program minetestserver), prebuilt headless binaries from a third party, building from source, and the container image. The project's own container image is the simplest current one, so these steps use it on Ubuntu 24.04, fixed at version 5.18.0 so nothing upgrades behind your back. You will not need a systemd unit: restart: unless-stopped in the compose file starts the game again if it stops unexpectedly or the server reboots.
- Get Docker and Compose onto the server using Docker's guide for Ubuntu, plus git, sqlite3 and rsync.
- Change
AdminNameand the world name in the block, then run it as root (sudo -i). It starts by switching the firewall on, letting in only SSH and the game's UDP port, before the game is running. If a config already exists it stops there, so pasting it twice can never swap out your join password. - Keep the password it prints for the next part.
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw && ufw allow OpenSSH && ufw --force enable && ufw allow 30000/udp &&
cd /opt && if [ -e luanti/config/minetest.conf ]; then echo "found an existing setup, stopping here"; else
mkdir -p luanti/data/.minetest luanti/config luanti/games luanti/mods &&
chown -R 30000:30000 luanti/data &&
git clone --depth 1 https://git.minetest.land/VoxeLibre/VoxeLibre luanti/games/voxelibre &&
pw=$(openssl rand -hex 16) && [ -n "$pw" ] &&
install -m 600 -o 30000 -g 30000 /dev/null luanti/config/minetest.conf &&
cat > luanti/config/minetest.conf <<EOF &&
name = AdminName
server_name = My Luanti world
server_announce = false
port = 30000
max_users = 15
disallow_empty_password = true
default_password = $pw
EOF
cat > 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 /opt/luanti/compose.yaml up -d && echo "Join password: $pw"; fi
The folders, the 30000:30000 ownership, the mounts and the VoxeLibre download come from the Docker / Container page of the docs, and the Dockerfile shows the image running as that minetest user. What we changed: the version tag instead of latest; the start options from the docs' second compose example, but with --world and the full world path, because the docs' --worldname only picks up a world that already exists and would stop a brand-new server; and the config file, whose settings all come from minetest.conf.example.
Why the firewall rule for the game? Ports that Docker publishes are not held back by ufw for IPv4 visitors, and for IPv6 visitors only when IPv6 is turned on in Docker itself. In every other case ufw decides, so the game's port needs its own allow rule.
Making sure you are the admin
Whoever logs in under the name set in name gets every privilege, and those cannot be taken away. Accounts are made the first time a name joins, so without a guard a stranger could take that name before you do. With default_password set, nobody can create an account without knowing it.
- Open Luanti and join port 30000 at your server's address, using the name AdminName and the printed password. That creates your account with full privileges; type
/privsto check. - As root, open /opt/luanti/config/minetest.conf and remove the
default_passwordline so new players can sign up with their own passwords.disallow_empty_passwordstill makes them choose one. - Restart:
docker compose -f /opt/luanti/compose.yaml restart. Changes to the config only apply after a restart.
Getting on the public server list
Once you are admin, set server_announce = true in the config, add a server_description, and a motd greeting if you want one, then restart. Leave server_address empty unless you own a domain aimed at this server; the docs warn that a wrong value makes the list reject you. Before inviting the public, add a protection mod like areas and a ban tool like xban2 from ContentDB. New mods go in /opt/luanti/mods and stay off until you add a line like load_mod_areas = mods/areas to world.mt, which the first start creates in /opt/luanti/data/.minetest/worlds/world. Edit it as root, then restart the server.
Keeping the world safe
Your world lives in SQLite databases, and the Luanti docs warn that copying them while the server is running will likely produce a broken copy. Their advice is the sqlite3 VACUUM INTO command, which takes a clean snapshot without kicking anyone. This script of ours does that for each database in the world, then packs the snapshots with the rest of the world folder, the config, the compose file, the game and the mods. As root:
install -d -m 700 /var/backups/luanti
cat > /usr/local/sbin/luanti-snapshot.sh <<'EOF'
set -euo pipefail
shopt -s nullglob
umask 077
cd /opt/luanti
WD=data/.minetest/worlds/world
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
rsync -a --exclude='/*.old' --exclude='/*.rejected' --exclude='*.sqlite' --exclude='*.sqlite-*' ./ "$TMP/"
for f in "$WD"/*.sqlite; do
sqlite3 -cmd '.timeout 20000' "$f" "VACUUM INTO '$TMP/$f'"
done
chown 30000:30000 "$TMP/$WD"/*.sqlite
dest=/var/backups/luanti/world-$(date +%F-%H%M%S).tar.gz
tar -czf "$dest" -C "$TMP" .
tar -tzf "$dest" > /dev/null
find /var/backups/luanti -name 'world-*.tar.gz' -mtime +13 -delete
EOF
printf '%s\n' '40 3 * * * root bash /usr/local/sbin/luanti-snapshot.sh >> /var/log/luanti-snapshot.log 2>&1' > /etc/cron.d/luanti-snapshot
bash /usr/local/sbin/luanti-snapshot.sh && ls -l /var/backups/luanti
Keep copies somewhere else too: have another computer fetch /var/backups/luanti with rsync -a (leave out --delete, so a wiped server can never wipe your copies) and clear old ones there. Now and then, also take a backup with the server stopped; the docs call that the way to be sure every file matches.
To bring a backup back, as root, set ARCHIVE to the file you want. First the block makes sure the archive opens and that no earlier set-aside copy is left over. Then it stops the game, renames today's world and settings to data.old and config.old, and unpacks the backup's versions.
ARCHIVE=/var/backups/luanti/world-2026-10-30-034000.tar.gz
cd /opt/luanti && tar -tzf "$ARCHIVE" > /dev/null &&
if [ -e data.old ] || [ -e config.old ]; then echo "an earlier data.old or config.old is in the way: move it first"
else docker compose -f /opt/luanti/compose.yaml stop &&
mv data data.old && mv config config.old &&
tar -xzf "$ARCHIVE" --numeric-owner ./data ./config && chown -R 30000:30000 data &&
docker compose -f /opt/luanti/compose.yaml start && echo "restore done"; fi
Changed your mind? Put the old copy back, as root: `cd /opt/luanti && [ -e data.old ] && [ -e config.old ] && [ ! -e data.rejected ] && [ ! -e config.rejected ] && docker compose -f /opt/luanti/compose.yaml stop && mv data data.rejected && mv config config.rejected && mv data.old data && mv config.old config && docker compose -f /opt/luanti/compose.yaml start`. Once you are sure, remove the .old or .rejected folders yourself, because the restore refuses to run while a .old copy exists.
When you do not need any of this
For an evening with two or three friends, the Host Server button in the Luanti menu is enough while your game stays open. And if your map is enormous, the docs suggest PostgreSQL over SQLite and warn against LevelDB, which can corrupt a world after an unclean shutdown. For small servers the docs call SQLite, the default, a good fit. And if your friends are set on Minecraft or Vintage Story instead, see running a Minecraft world or hosting Vintage Story.
Questions people ask
- Did Minetest change its name?
- Yes: Luanti is the new name of Minetest, the same free, open-source voxel engine. A few old names survive, such as minetest.conf for the settings and /var/lib/minetest as the container's home folder.
- Do I need to open any TCP ports?
- Not for players. Luanti listens on UDP, port 30000 unless you change
port. For each extra world on that machine, the docs suggest counting up: 30001, then 30002. They also give every server its own directory for world, config and log; with the container image, we would give each world its own compose project. - What game should I put on my server?
- Whatever your players want. Luanti stopped shipping a default game in 5.8.0, so you pick one from ContentDB, for example Minetest Game or VoxeLibre, place it in the games folder and name its folder with
--gameid. - How much memory will my Luanti world use?
- The docs put it at 4 GB for roughly ten players when the game and mods are light, and more for heavier ones. Watch the real figure with your own players and mods, and leave room to spare.
- Can I limit how many people join?
- Yes, with
max_users, which is 15 by default. Add a whitelist mod if only invited players should get in. - Can I move an existing world onto the server?
- Yes. Stop the server, copy the world folder to data/.minetest/worlds/world, the path the server starts, give it to UID 30000 with chown, and put the game it was made with in the games folder. The
gameidline in the world's world.mt names that game; if it is not VoxeLibre, change--gameid voxelibrein compose.yaml to that game's folder name, because the server runs whatever game the command line names. Then start the server again.