What you are setting up
You are setting up one copy of OpenTTD that runs with no screen and no player of its own, so the world keeps going when your PC is off. OpenTTD's man page (openttd.6) calls the -D option the way to start a dedicated server. There is no separate server package: the official download page for 15.3 offers a single Linux build, a generic 64-bit x86 archive, and says the game needs a base graphics set on top. A server has no screen to offer that download on, so bootstrap_gui.cpp makes it quit, saying it could not find a graphics set; you install OpenGFX yourself.
Friends find the world in one of three ways, set by server_game_type. local keeps it to your own network, public puts it on the server list, and invite-only hides it but lets anyone with the code in. According to docs/game_coordinator.md, the last two register the server with OpenTTD's Game Coordinator, which checks whether players can reach the game port directly and, if they cannot, tries a STUN handshake and finally a relay run by openttd.org. Since your server sits on a public IP with the game port reachable, players should normally connect straight to it; that is our expectation, not a promise from the docs.
Will a small machine keep up?
For a few friends on a normal-sized map, almost certainly, though OpenTTD itself publishes no minimum. What it does publish is a time budget: docs/logging_and_performance_metrics.md says the game aims for 30 milliseconds per tick, and the fps command (handy on a server, it notes) shows how much of that each part uses. Pathfinding, the economy and world updates all count against the game loop; cargo distribution's link graph usually works in a background thread. In our reading that leaves most of the work on one thread, which is why a quick core matters more than a big core count as your network of trains grows.
| What to plan for | Figure | Where it comes from |
|---|---|---|
| Traffic per player | About 1.5 KB/s each way; roughly 1 KB/s with frame_freq at 1 | docs/multiplayer.md |
| Players and companies | Up to 25 clients and 15 companies by default | network_settings.ini |
| Queued packet memory | max_clients² × bytes_per_frame × 10 KiB at most; about 49 MiB at the defaults, by our sum | docs/multiplayer.md, our arithmetic |
| Autosaves | Every 10 minutes, 16 kept | gui_settings.ini defaults |
One thing no server can fix: docs/desync.md explains that each player's own computer runs the whole simulation and only gets player actions from the server. If a friend's laptop struggles with a huge map, a faster server won't help them.
Getting OpenTTD onto the machine
Sign in to your Ubuntu 24.04 server with your sudo account and paste this. It makes an openttd account that cannot log in, gives the world its own folder at /srv/openttd-world, switches on the firewall with only SSH and the game port allowed, and then downloads OpenTTD and OpenGFX, installing them only if both match the SHA-256 checksums printed on openttd.org:
sudo apt update && sudo apt install -y curl xz-utils unzip ufw
sudo useradd --system --create-home --home-dir /home/openttd --shell /usr/sbin/nologin openttd
sudo install -d -o openttd -g openttd -m 750 /srv/openttd-world /srv/openttd-world/baseset
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw allow OpenSSH
sudo ufw --force enable
sudo ufw allow 3979/tcp && sudo ufw allow 3979/udp
d=$(mktemp -d) && cd "$d" \
&& curl -fLO https://cdn.openttd.org/openttd-releases/15.3/openttd-15.3-linux-generic-amd64.tar.xz \
&& curl -fLO https://cdn.openttd.org/opengfx-releases/8.0/opengfx-8.0-all.zip \
&& printf '%s %s\n' f49eb25d61b00f8f4d332fee02b530ad75552d1efb8f2bb01e7ca5e6540fe059 openttd-15.3-linux-generic-amd64.tar.xz 43a0c1dabf39cb865394f3a6cc36d4da5c10ecfaaf55652043104806810903be opengfx-8.0-all.zip | sha256sum -c - \
&& sudo install -d /opt/openttd \
&& sudo tar -xJf openttd-15.3-linux-generic-amd64.tar.xz -C /opt/openttd --strip-components=1 \
&& sudo unzip -o opengfx-8.0-all.zip -d /srv/openttd-world/baseset \
&& sudo chown -R openttd:openttd /srv/openttd-world && echo installed
If you don't see installed at the end, stop and read the error. OpenGFX arrives as one .tar inside the zip; leave it packed, since docs/directory_structure.md notes that uncompressed tar files are read directly. The world lives outside the account's home on purpose: the same guide says OpenTTD skips the starting folder when that folder is your home directory. The admin port, 3977, gets no firewall rule, so it stays shut to the internet.
Name it, lock it, make it invite-only
OpenTTD looks for openttd.cfg in the folder it starts from before anywhere else (the directory guide gives the order), and fileio.cpp keeps its two companion files, private.cfg for things like the server name and secrets.cfg for passwords, right next to it. So an empty openttd.cfg in the world folder keeps everything together. openttd.cpp saves every setting when the main loop ends (its PostMainLoop step calls SaveToConfig), so a short first run creates the files. This block lets the server start once for two minutes, then fills in your choices only if the files appeared:
sudo -u openttd touch /srv/openttd-world/openttd.cfg
sudo -u openttd bash -c 'cd /srv/openttd-world && timeout -s INT 120 /opt/openttd/openttd -D'
if sudo test -s /srv/openttd-world/secrets.cfg; then
pw=$(openssl rand -hex 16) && [ -n "$pw" ] \
&& sudo -u openttd sed -i "s/^rcon_password = .*/rcon_password = $pw/" /srv/openttd-world/secrets.cfg \
&& sudo -u openttd sed -i -e 's/^server_game_type = .*/server_game_type = invite-only/' \
-e 's/^min_active_clients = .*/min_active_clients = 1/' \
-e 's/^autosave_on_exit = .*/autosave_on_exit = true/' /srv/openttd-world/openttd.cfg \
&& sudo -u openttd sed -i 's/^server_name = .*/server_name = Our railway world/' /srv/openttd-world/private.cfg \
&& [ "$(sudo grep -cxE 'server_game_type = invite-only|min_active_clients = 1|autosave_on_exit = true' /srv/openttd-world/openttd.cfg)" = 3 ] \
&& sudo grep -qx "rcon_password = $pw" /srv/openttd-world/secrets.cfg \
&& echo "settings ok, rcon password: $pw"
else
echo "STOP - the first run did not write its config"
fi
Write the rcon password down; while sed runs, it shows briefly in the process list. Here is what each change does. min_active_clients = 1 makes the game wait while nobody is playing in a company (network.cpp doesn't count spectators), so your rivals can't race ahead overnight. autosave_on_exit saves the map whenever the server is stopped properly. Invite-only only hides the server from the list; it is not a lock, because port 3979 is open and anybody who knows the address can connect. To keep it to friends, stop the server, put a password after server_password = in secrets.cfg, and share it along with the code. The server rewrites these files every time it shuts down, so only edit them while it is stopped.
Keeping the world across restarts
Left alone, openttd -D starts a brand-new map every time (video/dedicated_v.cpp), which is the last thing you want after a reboot. This start script, our own addition, loads the most recent autosave with -g instead, and only makes a fresh map when there is none; the systemd unit starts it with the machine and relaunches it whenever it crashes:
sudo tee /usr/local/bin/openttd-world-start >/dev/null <<'EOF'
cd /srv/openttd-world || exit 1
latest=$(ls -t /srv/openttd-world/save/autosave/*.sav 2>/dev/null | head -n 1)
[ -n "$latest" ] && exec /opt/openttd/openttd -D -g "$latest"
exec /opt/openttd/openttd -D
EOF
sudo tee /etc/systemd/system/openttd-world.service >/dev/null <<'EOF'
[Unit]
Description=OpenTTD world
Wants=network-online.target
After=network-online.target
[Service]
User=openttd
Group=openttd
WorkingDirectory=/srv/openttd-world
ExecStart=/bin/bash /usr/local/bin/openttd-world-start
TimeoutStopSec=120
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now openttd-world
When you run sudo systemctl stop openttd-world, systemd sends SIGTERM, and dedicated_v.cpp answers it by writing exit.sav before quitting; the two-minute stop timeout leaves room for that on a big map. Find your invite code with journalctl -u openttd-world | grep -i 'invite code': network_coordinator.cpp logs it once registration succeeds, and the man page says -D turns network logging up to level 6, so it should be there. Share that code with your friends. The service has no keyboard attached, so manage the game from your own copy of OpenTTD: join, open the console and type rcon yourpassword "command", as the OpenTTD wiki explains on its dedicated-server page. One trap: if the newest save is damaged, openttd.cpp makes the server log that loading the requested map failed and shut down, and systemd keeps retrying that same file. Look with journalctl -u openttd-world -n 50; if you see it, stop the service, move the newest file from save/autosave up into /srv/openttd-world/save/, and start again so the next newest save loads.
Saving the world somewhere safe
Take backups while the server is stopped, because stopping is what writes the exit save. Once, beforehand: sudo mkdir -m 700 -p /var/backups/openttd-world. Then, at a quiet hour: `sudo systemctl stop openttd-world; b=/var/backups/openttd-world/world-$(date +%F-%H%M%S).tar.gz; sudo tar -C / -czf "$b" srv/openttd-world usr/local/bin/openttd-world-start etc/systemd/system/openttd-world.service && sudo tar -tzf "$b" >/dev/null && echo "backup ok: $b"; sudo systemctl start openttd-world. That captures the saves, all three settings files, the graphics and any content you downloaded. Copy the archives to another computer you own with rsync -a, and leave out --delete` so a mistake on the server can't wipe your copies.
Bringing one back takes four steps, run with sudo:
- Stop the world:
sudo systemctl stop openttd-world. - Check the file reads:
sudo tar -tzvf ARCHIVE >/dev/null && echo good. Without good, go no further. - Swap it in:
[ ! -e /srv/openttd-world.before ] && sudo mv /srv/openttd-world /srv/openttd-world.before && sudo tar -xzf ARCHIVE -C / srv/openttd-world && sudo chown -R openttd: /srv/openttd-world && echo swapped. Your current saves and settings move aside as one folder; if an older.beforefolder is still there, it refuses until you deal with that one. - Start it only after swapped:
sudo systemctl start openttd-world, then join and look around.
Changed your mind? `sudo systemctl stop openttd-world && [ -d /srv/openttd-world.before ] && [ ! -e /srv/openttd-world.discarded ] && sudo mv /srv/openttd-world /srv/openttd-world.discarded && sudo mv /srv/openttd-world.before /srv/openttd-world && sudo systemctl start openttd-world puts things back. When you are happy, delete /srv/openttd-world.before (or, after an undo, /srv/openttd-world.discarded) with sudo rm -rf so the next restore isn't blocked. For a new OpenTTD release, back up, check its checksum and unpack it into /opt/openttd-next, stop the service, swap the folders with [ ! -e /opt/openttd.prev ] && sudo mv /opt/openttd /opt/openttd.prev && sudo mv /opt/openttd-next /opt/openttd`, and start again. Tell your friends to update too: the multiplayer guide marks a server running another version with a red dot, and they can't join it. The README promises that newer versions open older saves and makes no promise the other way, so a rollback needs the old binary and your pre-upgrade backup.
The server that fits
Start with our game servers: a whole physical machine that only you use, picked by its single-thread score. Every processor in that page's ranked table clears 2,000 on PassMark's one-thread benchmark, the score Satisfactory's server guide asks for; the live list of racked machines below it also holds slower ones, so check the processor against the table. OpenTTD asks for no particular score, and in our view a friends' world is happy on the cheapest machine in that table sold in all five cities, the page's own pick for a world with friends, with a faster row worth it once the map fills with trains. Leave out the two ARM families, X-Gene and Ampere Altra: the official Linux archive is for x86 only.
Pick the city nearest most of your players; the game server page times a round trip from your browser to each one, and our data center pages describe the five rooms. Choose Ubuntu on the Linux servers page and you get full root. One IPv4 address comes with the machine, and IPv6 costs nothing extra if you want it. Once your order is approved, a server already sitting in the rack is yours in about 30 minutes; the payment check comes first and its time varies. Built to your own spec, allow 4 to 24 hours when we hold the parts, and longer when one has to be bought in. Every rack server and blade has its own management controller: add a console login in the dashboard and it gives you a browser console. Among Moonshot cartridges two models (the m510 and the m710x) also get a console; every remaining cartridge has power control only, with no console. If you get stuck, our AI on chat answers in seconds and a person takes over whenever you ask.
We don't install or update OpenTTD for you, and there is no game panel: the machine is yours to set up. OpenTTD is free software under GPL v2. Our general reading, offered as such and not as legal advice: hosting a game for friends is fine, but if you hand out copies of the game, changed or not (for example a patched build your friends must use to join), you must offer them the source as well.
Questions people ask
- Do my friends need to open ports on their routers?
- No. Only the server needs port 3979 open, for TCP and UDP, and this setup opens it in ufw. Your friends connect out to it, either from the server list when it is public or by entering the invite code under Add Server.
- Will the trains keep running when nobody is online?
- Not with
min_active_clients = 1. OpenTTD pauses whenever the number of players inside companies drops below that, and carries on once someone takes one over. Leave the setting at its default of 0 if you want the world to carry on alone. - Can I move a game I started at home onto the server?
- Yes, if it was saved by the same OpenTTD version as the server or an older one. Stop the service, copy your
.savfile into/srv/openttd-world/save/autosave/, then runsudo chown openttd:andsudo touchon it: copying withscp -porrsync -akeeps the old timestamp, and the script loads the file with the newest one. Start the service again.