Mindustry Server
Overview
Mindustry is an open-source factory-building tower-defence game. Players mine resources, lay down conveyor and power networks, automate production, and defend what they have built against waves of enemies. The game is at its best in multiplayer, where a group builds one factory together on a shared, persistent map. A dedicated server hosts that map.
This guide explains how to deploy and use Mindustry Server v159.7 on Ubuntu 24.04 on Azure with the SecureAnyCloud image from the Azure Marketplace. The image ships the dedicated server, not the game client.
Each Mindustry release publishes four artefacts, and only one of them is a server: server-release.jar. Mindustry.jar is the desktop client and cannot serve as a hosted server. Before the image is captured, the build asserts that the installed artefact is the server. It must carry mindustry/server/ServerLauncher.class, its manifest Main-Class must be mindustry.server.ServerLauncher, and it must carry no desktop-client classes.
Your players do not need this image. They install Mindustry normally on their own machines and choose Join Game, then Add Server, and enter this VM's address. This VM is the world they all share, and it keeps running when they log off.
What you get
- A persistent world. The map, the factory and the wave count live on the server. Players come and go, and the world does not reset.
- Automatic saves. The server autosaves every five minutes and keeps the five most recent saves, so a crash costs minutes, not a session. A reboot resumes the most recent autosave by itself.
- A real admin console. mindustryctl drives the server's own console. You can host and stop maps, list players, grant admin, ban and unban, run waves and change settings.
- Anti-abuse defaults. A player limit, chat rate limiting, strict mode, spam kicks and modified-client rejection are configured. Several of these are off in the upstream defaults.
- Closed administration. The console is bound to loopback and requires a per-VM token. The game port is the only port published.
Licence
Mindustry is free software licensed under the GNU General Public License, version 3. That is upstream's licence, verified from the LICENSE file at tag v159.7 rather than from a badge. It applies to the game itself, which SecureAnyCloud does not modify.
The image ships the licence obligations with the binary:
/usr/share/doc/mindustry/LICENSEholds the full GPL-3.0 text./usr/share/doc/mindustry/SOURCE.txtrecords the exact upstream tag and artefact digest of the installed binary, a link to the corresponding source tree, and a written offer for that source.
SecureAnyCloud's own additions are the console bridge, the first-boot bootstrap, the protocol probe and the audit scripts. They are kept in separate files under /usr/local and are themselves GPL-3.0-or-later. The protocol probe's source ships next to its compiled classes. Mindustry is a trademark of its authors. SecureAnyCloud is not affiliated with or endorsed by the Mindustry project.
Before you start
- Recommended VM size: Standard_B2s (2 vCPU / 4 GiB), which is comfortable for about 30 players on a standard map.
- Game port: 6567 TCP and UDP. This port must be reachable from the internet.
- Admin console: 127.0.0.1:6859, loopback only and never published.
- SSH: port 22, restricted to your own address.
- OS disk: 30 GiB or more. Saves and logs are capped, but maps and mods add up.
Measured on this image, the server used 162 MB of RSS with a 256x256 world hosted and a client connected, against the 4 GiB of a Standard_B2s. The JVM heap is capped explicitly at 1024 MB rather than left at the JVM's default share of host RAM. The image has no swap, because Azure manages swap on the ephemeral resource disk.
Network security group
The game port is the only port that must be public, and nothing else should be. Mindustry uses 6567 for both TCP and UDP, so you need two rules:
# Run from your workstation, not the VM. Replace the names with your own.
az network nsg rule create --resource-group <your-rg> --nsg-name <your-nsg> \
--name mindustry-game-tcp --priority 1010 \
--access Allow --protocol Tcp --direction Inbound \
--source-address-prefixes Internet --destination-port-ranges 6567
az network nsg rule create --resource-group <your-rg> --nsg-name <your-nsg> \
--name mindustry-game-udp --priority 1011 \
--access Allow --protocol Udp --direction Inbound \
--source-address-prefixes Internet --destination-port-ranges 6567Do not open 6859. That port is the admin console. It is bound to loopback and reached over SSH, as described in Administering the server.
First boot
On first boot, the VM generates its own console token, prepares a clean state directory, starts the server, applies the shipped configuration and hosts a map. It then proves that a real Mindustry protocol client can join before it declares itself ready. Nothing is baked into the image: no token, no world, no save, no admin list and no ban list.
Wait two or three minutes after the VM reports Running, then check the services:
systemctl is-active mindustry-firstboot.service mindustry.service mindustry-console.serviceAll three should report active. Then read this VM's credentials:
sudo cat /root/mindustry-credentials.txtThat file is 0600 root:root. It contains your console token, this VM's addresses and the address your players connect to. Treat it as you would any other secret.
Checking the server is up
sudo mindustryctl statusThe output shows Playing on map <name> / Wave <n>, the seconds until the next wave, and the connected players. If it says server closed, no world is hosted. See Hosting a map.
Connecting from the game
Your players need only the server address from this guide. Ask each player to follow these steps:
- Open Mindustry on their own machine (Steam, itch.io or the official build), on the same version, v159.7.
- Choose Play, then Join Game, then Add Server.
- Enter the address from
MINDUSTRY_GAME_ADDRESSin the credentials file. This is your VM's public IP, with:6567if the client asks for a port separately. - Select the server, which appears with its name, map and player count, to join.
Version matters. By design, the server rejects clients on a different build, which is how Mindustry avoids desynchronised worlds. It also rejects modified clients, because allowCustomClients is set to false in this image.
The image carries a Mindustry protocol probe built from upstream's own classes: arc-net's client, Mindustry's packet serializer and Mindustry's ConnectPacket. The probe completes the real handshake and sends connectConfirm, which is what makes the server register a player. It then reports what happened.
You can run that check yourself at any time. See Verifying your deployment.
Administering the server
Mindustry is administered from a console. Upstream also offers a remote console over TCP (socketInput), but it has no authentication of any kind. Anyone who can reach that socket can grant admin, ban players and stop the server. This image therefore leaves the remote console off, and the appliance refuses to start if it is ever switched on.
Instead, administration goes through a SecureAnyCloud console bridge with these properties:
- It binds only
127.0.0.1:6859, and is restricted at the kernel withIPAddressAllow=localhoston its own systemd unit. - It refuses to forward any command without this VM's console token.
- It is driven by
mindustryctl, which is root-only (0750 root:root).
sudo mindustryctl helpEveryday commands
sudo mindustryctl status
# what is hosted, wave, players
sudo mindustryctl players
# who is connected, with their IDs
sudo mindustryctl maps all
# the built-in maps you can host
sudo mindustryctl playerlimit
# the current player capMaking someone an admin
Mindustry grants admin rights by player ID. The ID is shown in the output of players while the player is connected:
sudo mindustryctl playersCopy the value after ID: for the player you want to promote, then run:
sudo mindustryctl admin add <their-player-id>
sudo mindustryctl adminsThe player must be connected when you run admin add, and should reconnect afterwards so the client picks up the new status. Run admin remove <id> to reverse it.
Bans and anti-grief
sudo mindustryctl bans
# everyone currently bannedTo act against a griefer, run:
sudo mindustryctl kick <player-name>
sudo mindustryctl ban id <their-player-id>
sudo mindustryctl unban <id-or-ip>The server also ships with enableVotekick on, so your players can remove a griefer themselves without waiting for you. It ships with strict mode on as well, which corrects positions and rejects duplicate player IDs.
Why administration is closed, and how that is proven
Two details of the audit separate a working security control from a decorative one:
- The refusal is proven by its effect, not by a message. The check reads the player limit and sends a limit-changing command with a wrong token. It confirms that the limit did not move, then sends the same command with the real token and confirms that it did.
- The negative result has a positive control. A console port that is unreachable from off-box could also be explained by a broken probe, so the same check confirms that the game port on that same address is reachable.
An authentication failure costs a fixed one-second penalty and deliberately not a lockout, because a lockout would make that positive control ambiguous.
Hosting a map
The game port exists only while a world is hosted. Mindustry opens the listener when you host a map and closes it when you stop. If status says server closed, your players cannot connect.
sudo mindustryctl maps allHost one of the listed maps:
sudo mindustryctl host Ancient_Caldera survivalLoading a large map can take a minute or two on a Standard_B2s. Wait for it to finish, then confirm that the world is hosted:
sudo mindustryctl statusYou can use other game modes in place of survival: attack, pvp, sandbox and editor. To end the current world, run sudo mindustryctl stop, then host another map. To force the next wave immediately, run sudo mindustryctl runwave.
Saves, restarts and retention
The world is persistent state, and the image protects it as such:
- Autosave is on (upstream ships it off). It runs every 300 seconds and keeps the five most recent saves. Older autosaves are replaced, so saves cannot fill the disk.
- A reboot resumes the most recent autosave automatically. If there is nothing to resume, the server hosts the default map.
- Named saves are yours to create and keep.
sudo mindustryctl save my-world
sudo mindustryctl savesSaves live in /var/lib/mindustry/config/saves as .msav files. Copy them off the VM with scp if you want a backup that you control. Run sudo mindustryctl load my-world to restore one.
The world survives a full service restart, not just a reload. The map and the wave number come back unchanged.
Logs
The server log is at /var/log/mindustry/mindustry.log. It is rotated daily or at 32 MB, keeping seven compressed generations for at most 30 days. Upstream's second log stream under the state directory is switched off, so there is exactly one log to read.
sudo tail -20 /var/log/mindustry/mindustry.logMindustry writes colour escapes to its console. mindustryctl strips them, so its output is plain text. The raw log keeps them, and both cat and less -R render them correctly.
Tuning the server
Configuration is a plain text file, /etc/mindustry/server-commands.conf, which the service replays into the server's console on every start. Mindustry's own settings.bin is a binary file that also holds the admin list, the ban list and the player database. The image deliberately ships none, so configuration stays auditable as text and no build-time state is carried into it.
sudo cat /etc/mindustry/server-commands.confThe file sets these anti-abuse defaults. For comparison, each entry also shows what upstream ships:
- playerlimit: 30 in this image. Upstream has no limit at all.
- messageRateLimit: 2 seconds in this image. Upstream uses 0, which disables chat rate limiting.
- messageSpamKick: 3 in this image. Upstream uses 3.
- allowCustomClients: false in this image. Upstream uses true on desktop builds.
- autosave: true, with 5 saves kept, in this image. Upstream uses false.
- socketInput: false in this image. Upstream also uses false, but the socket is unauthenticated when switched on.
To change a setting, edit the file and restart the service:
sudoedit /etc/mindustry/server-commands.conf
sudo systemctl restart mindustry.serviceYou can also change a setting live. The setting reverts to the value in the file on the next restart, so the file stays the single source of truth:
sudo mindustryctl config messageRateLimitMaking the server private
To run an invite-only server, turn the whitelist on. Players must connect once to become known, and you then whitelist them by ID:
sudo mindustryctl config whitelist true
sudo mindustryctl whitelist add <their-player-id>Add config whitelist true to /etc/mindustry/server-commands.conf so that the setting survives a restart.
Verifying your deployment
The image carries its own audit. It does not merely check that a service is running or that a port is open. It proves the behaviours that matter to you, and it takes a few minutes because it restarts the server and reconnects a client:
sudo /usr/local/sbin/mindustry-roundtrip-check.shThe audit proves the following, in order:
- The admin credential is required, and a wrong one executes nothing. The real credential is proven again immediately afterwards, so the refusals are not a lockout.
- The console answers on loopback and not on this VM's routable address, while the game port on that same address does.
- A genuine Mindustry protocol client completes the handshake, and the server reports the connected player.
- Malformed clients are rejected.
- The world survives a full restart.
- logrotate rotates the log.
The audit also reports peak server memory with a world hosted and a client connected. It ends with MINDUSTRY_ROUNDTRIP_OK and exits non-zero if anything failed.
Troubleshooting
Players cannot find or join the server. Check that a world is hosted. sudo mindustryctl status must not say server closed, because the game port exists only while a world is up. Then confirm that both NSG rules exist. Mindustry needs 6567 on TCP and UDP, and opening only TCP is the most common cause of a server that shows up but will not connect.
A player is refused. The usual cause is a version mismatch. The client must be on v159.7, the same build as the server. By design, modified clients are refused too. Check the log:
sudo tail -20 /var/log/mindustry/mindustry.logmindustryctl says the console is unreachable. The bridge follows the server, so check both units:
systemctl is-active mindustry.service mindustry-console.serviceIf mindustry.service is not active, find out why. The committed-secret guard runs before the server opens a listener, and it refuses to start the server if this VM's console token is missing or weak:
sudo /usr/local/sbin/mindustry-secret-guard.shThe server was restarted and the world is gone. Resume the most recent autosave or load a named save:
sudo mindustryctl loadautosaveMemory. The server is modest, at 162 MB measured with a world hosted and a client connected, but a very large map with many players increases its usage. The heap is capped at 1024 MB. If you need more, raise -Xmx in /usr/local/sbin/mindustry-run.sh and move to a larger VM size rather than adding swap, which Azure images must not carry.
Support
SecureAnyCloud images come with 24/7 support. Include the output of the audit above and the last lines of /var/log/mindustry/mindustry.log with any support request.
Mindustry itself is community software. For gameplay questions, mods and map making, contact the upstream project and its community.
Deploy on Azure
Launch Mindustry Server v159.7 on Ubuntu 24.04 on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com