The vanilla Terraria dedicated server works, but TShock is what you actually want: REST API, granular permissions, a plugin system, and sane bans. It's the SourceMod of Terraria.
TL;DR:
sudo adduser --system --group --home /opt/terraria terraria
cd /opt/terraria
# grab the latest 5.x self-contained linux build from GitHub releases
curl -LO https://github.com/Pryaxis/TShock/releases/download/v5.2.0/TShock-5.2-for-Terraria-1.4.4.9-linux-x64-Release.zip
sudo -u terraria unzip TShock-*.zip -d tshock-server
cd tshock-server
sudo -u terraria ./TShock.Server -port 7777 -autocreate 3 -world /opt/terraria/worlds/main.wld
Details below.
Get TShock and pick the right build
TShock 5.x is .NET-based (it runs on the .NET runtime, not Mono anymore). The releases on the Pryaxis/TShock GitHub page ship in two flavors per platform:
- Self-contained — bundles the .NET runtime. Bigger download, zero host dependencies. This is the one I use.
- Framework-dependent — smaller, but you must install the matching .NET runtime yourself.
Grab the latest 5.x linux-x64 zip. Match the Terraria version in the filename to the client version your players run, or nobody connects.
sudo mkdir -p /opt/terraria
cd /opt/terraria
curl -LO https://github.com/Pryaxis/TShock/releases/download/v5.2.0/TShock-5.2-for-Terraria-1.4.4.9-linux-x64-Release.zip
The exception: if you want the framework-dependent build to save disk, install the runtime first:
sudo apt update
sudo apt install -y dotnet-runtime-8.0
Check the release notes for the exact .NET version — 5.2 is on .NET 8. Don't guess.
Run as a non-root user
Never run a game server as root. Create a dedicated system user, unzip under it, fix perms. If you haven't hardened the box yet, do my Ubuntu initial setup checklist first.
sudo adduser --system --group --home /opt/terraria terraria
sudo chown -R terraria:terraria /opt/terraria
cd /opt/terraria
sudo -u terraria unzip TShock-*.zip -d tshock-server
sudo chmod +x /opt/terraria/tshock-server/TShock.Server
The binary is TShock.Server. The first launch creates a tshock/ config dir, a ServerPlugins/ dir, and a logs/ dir next to it.
First run and config
Do one interactive run to generate the config and a world. -autocreate 3 makes a large world if none exists; sizes are 1 small, 2 medium, 3 large.
sudo -u terraria mkdir -p /opt/terraria/worlds
cd /opt/terraria/tshock-server
sudo -u terraria ./TShock.Server \
-port 7777 \
-autocreate 3 \
-world /opt/terraria/worlds/main.wld
On first start TShock prints a setup code in the console and logs — use it in-game with /setup <code> to grant yourself superadmin, then /user group <yourname> superadmin and disable the setup code. Ctrl+C to stop once the world is generated.
Now edit tshock/config.json. The two things that actually matter:
{
"Settings": {
"ServerPassword": "change-me",
"RestApiEnabled": true,
"EnableTokenEndpointAuthentication": true,
"ApplicationRestTokens": {
"REPLACE_WITH_A_LONG_RANDOM_TOKEN": {
"Username": "rest",
"UserGroupName": "superadmin"
}
},
"MaxSlots": 16,
"ServerName": "diengdoh"
}
}
Generate a real token, don't ship the placeholder:
openssl rand -hex 32
The REST API listens on 7878 by default. Smoke test it:
curl "http://127.0.0.1:7878/status"
curl "http://127.0.0.1:7878/v2/server/status?token=YOUR_TOKEN"
Keep 7878 bound to localhost or behind a reverse proxy — do not expose it raw. Only the game port 7777 goes public.
serverconfig.txt vs flags
You can pass everything as flags, or drop a serverconfig.txt and point at it with -config. For systemd I prefer flags — they're visible in the unit and there's no second source of truth. The flags I use:
| Flag | Purpose |
|---|---|
-port 7777 |
listen port |
-world <path> |
load this .wld |
-autocreate 3 |
generate large world if missing |
-maxplayers 16 |
slot cap |
-secure |
enable anti-cheat checks |
Daemonize with systemd
Quick-and-dirty option first: screen or tmux. Fine for a quick test, bad for anything you care about — no auto-restart, dies with a bad SSH session.
tmux new -s terraria
# run the server, then Ctrl-b d to detach
For real, use systemd. I've got a longer writeup on unit files, but here's the one for TShock:
# /etc/systemd/system/terraria.service
[Unit]
Description=Terraria TShock Server
After=network.target
[Service]
Type=simple
User=terraria
Group=terraria
WorkingDirectory=/opt/terraria/tshock-server
ExecStart=/opt/terraria/tshock-server/TShock.Server \
-port 7777 \
-world /opt/terraria/worlds/main.wld \
-autocreate 3 \
-secure
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
WorkingDirectory matters — TShock resolves tshock/, ServerPlugins/, and logs/ relative to cwd. Get it wrong and it silently writes a fresh config somewhere else.
sudo systemctl daemon-reload
sudo systemctl enable --now terraria
sudo systemctl status terraria
sudo journalctl -u terraria -f
Graceful stop matters for save integrity — systemctl stop sends SIGTERM, which TShock handles by saving and exiting. Don't kill -9 a running world.
Firewall
Only the game port. With ufw (full ruleset notes in my ufw post):
sudo ufw allow 7777/tcp
sudo ufw reload
Leave 7878 (REST) off the public allowlist. If you need remote API access, tunnel it over SSH or front it with an authenticated reverse proxy — never ufw allow 7878.
Plugins
TShock plugins are .dll files. Drop them into ServerPlugins/ and restart.
sudo -u terraria cp SomePlugin.dll /opt/terraria/tshock-server/ServerPlugins/
sudo systemctl restart terraria
Match the plugin's targeted TShock major version — a 4.x plugin won't load on 5.x. Watch the boot log; failed plugins log a load error and the server keeps running without them, so check journalctl after dropping a new one in.
Back up the world files
The world lives in one .wld file. That file is your entire server. Back it up on a schedule — TShock also keeps .bak rollbacks, but those sit on the same disk.
# /opt/terraria/backup.sh
#!/usr/bin/env bash
set -euo pipefail
DEST=/opt/terraria/backups
mkdir -p "$DEST"
cp /opt/terraria/worlds/main.wld "$DEST/main-$(date +%F-%H%M).wld"
# keep last 48
ls -1t "$DEST"/main-*.wld | tail -n +49 | xargs -r rm
Cron it under the terraria user:
sudo -u terraria crontab -e
# every 30 min
*/30 * * * * /opt/terraria/backup.sh
Copying a live .wld is fine — Terraria writes atomically — but if you're paranoid, run the backup right after an in-game /save. Better yet, rsync the backups off-box.
Verify
After enabling everything, confirm the four things that break setups:
# 1. service up and stable
systemctl is-active terraria
# 2. game port listening
sudo ss -tlnp | grep 7777
# 3. REST answers locally
curl -s "http://127.0.0.1:7878/status"
# 4. firewall lets 7777 in, NOT 7878
sudo ufw status | grep -E '7777|7878'
Then connect from the actual Terraria client: Multiplayer → Join via IP, enter the box IP and 7777, password if set.
Gotchas
- Terraria version mismatch. The single most common "can't connect." The TShock build targets one exact Terraria client version. If players updated and you didn't, they bounce. Pin both.
- REST token exposed. A superadmin REST token is full server control over HTTP. Long random value, localhost-only binding, never in a public repo or screenshot.
- Wrong WorkingDirectory. Without it in the unit, TShock generates a second config dir under
/and ignores your edits. Always set it. - Running framework-dependent without the runtime. It exits immediately with a .NET-not-found error. Use the self-contained build or
apt install dotnet-runtime-8.0. -autocreateon an existing world. It only creates if the file is missing, so it's safe — but if your-worldpath is wrong, it'll happily generate a new empty world and you'll wonder where everyone's base went.
Same pattern as my Satisfactory server writeup: dedicated user, systemd, tight firewall, backups. Boring and bulletproof beats a screen session you forget about.