Terraria Dedicated Server on Linux with TShock

Self-host a Terraria dedicated server on Ubuntu with TShock 5.x — dotnet runtime, config.json, REST API, systemd, ufw, and world backups.

Share
Terraria Dedicated Server on Linux with TShock

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.
  • -autocreate on an existing world. It only creates if the file is missing, so it's safe — but if your -world path 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.