No description
Find a file
2026-01-22 18:58:27 +01:00
LICENSE Initial commit 2026-01-22 13:25:02 +01:00
README.md Update README.md 2026-01-22 18:58:27 +01:00

hytale-server

My hytale.cyber.cafe server uses Debian 13.3 and running in a VM in Proxmox using the following configuration, with a security hardened systemd service, custom scripts, and cron. The VM has 16GB of RAM and 16 CPU threads. In later editions, when I have more time, I will create and document a script for installing and updating mods from Curseforge.

Note about how I document commands in code blocks: if I state multiple commands in one code block, it means they can be run together without issue. I separate individual commands with a line break for clarity about what is a different command. I prefer this over having individual lines for every single command.

Another note, steps 1 - 5 (and 8) are the important ones. 6 and later are just extra things to help make it nicer to manage the server.

Please check back for updates (pending time and experience), and please feel free to send me feedback via email with yawnbox at disobey.net.

1 - Deploy Debian 13.3

Once deployed, log in as root:

su -

Run updates and install dependencies:

apt update

apt dist-upgrade -y

apt install -y curl wget python3 unzip cron util-linux ca-certificates sed ufw gpg

Start cron:

systemctl enable --now cron

Enable the system firewall ufw that we just installed. Port 5520/udp is the default Hytale port and protocol.

Firewall notes:

  1. Leaving the default port makes it easier for people to get to your server - they can just type a domain without a port.
  2. Changing the Hytale port makes it harder for bots or hackers to find your Hytale server.
  3. Don't enable 22/tcp unless you need remote SSH. If it's a remote VM, I suggest safelisting your IP (ufw allow from 1.2.3.4) instead of enabling 22/tcp.
  4. You don't need fail2ban if you use limit for 22/tcp, or simply safelist your source IP.
  5. If you want a very secure, remotely accessible server, you do IP safelisting for for all of your users; or you run Wireguard on the Hytale server directly, like with pi-vpn, give everyone Wireguard profiles, and make the Hytale service accessible on the Wireguard LAN IP.
ufw allow 5520/udp

ufw limit 22/tcp

ufw enable

Continue these directions as root, or give your user sudo (this is not the user that you will run Hytale server with, we create a dedicated Hytale user later in this article):

apt install -y sudo

usermod -aG sudo <username>

2 - Install Temurin JDK v25 LTS (a better OpenJDK):

With apt!

curl -fsSL https://packages.adoptium.net/artifactory/api/gpg/key/public \
  | gpg --dearmor \
  | tee /usr/share/keyrings/adoptium.gpg > /dev/null

cat <<EOF | tee /etc/apt/sources.list.d/adoptium.sources
Types: deb
URIs: https://packages.adoptium.net/artifactory/deb
Suites: $(. /etc/os-release && echo $VERSION_CODENAME)
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /usr/share/keyrings/adoptium.gpg
EOF

apt update && apt install -y temurin-25-jdk

java -version

3 - Make a limited Hytale user

useradd --system --home /opt/hytale --shell /usr/sbin/nologin --user-group hytale

mkdir -p /opt/hytale

chown -R hytale:hytale /opt/hytale

chmod 0750 /opt/hytale

4 - Install the Hytale server

From: https://support.hytale.com/hc/en-us/articles/45326769420827-Hytale-Server-Manual

Download, unzip, and set permissions:

cd /opt/hytale

wget https://downloader.hytale.com/hytale-downloader.zip

unzip -o hytale-downloader.zip

chown -R hytale:hytale /opt/hytale

chmod 0750 /opt/hytale

chmod 0750 /opt/hytale/hytale-downloader

Run the Hytale server for the first time:

runuser -u hytale -- /opt/hytale/hytale-downloader

runuser -u hytale -- java -jar Server/HytaleServer.jar --assets Assets.zip

Once the server is running, authenticate for the first time in the server terminal interface:

/auth login device

Make your authentication permanent on your server so you don't have to reauthenticate every time the server restarts:

/auth persistence Encrypted

Stop the server:

/stop

5 - Create a Hytale systemd service

Finally to the good part.

Please note the -Xms10G -Xmx10G which sets a minimum 10GB and maximum 10GB of system RAM, you probably want to change this! Based on my experiences with modded Minecraft server performance, I like to statically set this, and make them the same if you are not constrained by limited RAM. You can remove these flags and let Hytale manage Java/RAM usage. I highly recommend keeping -XX:+UseZGC for Java garbage collection management.

sudo tee /etc/systemd/system/hytale.service > /dev/null <<'EOF'
[Unit]
Description=Hytale Server
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=hytale
Group=hytale
WorkingDirectory=/opt/hytale
RuntimeDirectory=hytale
RuntimeDirectoryMode=0750

ExecStartPre=/bin/bash -lc 'rm -f /run/hytale/console; mkfifo -m 0620 /run/hytale/console'
ExecStart=/bin/bash -lc 'exec 3<>/run/hytale/console; exec /usr/bin/java -Xms10G -Xmx10G -XX:+UseZGC -XX:+AlwaysPreTouch -jar Server/HytaleServer.jar --assets Assets.zip <&3'
ExecStop=/bin/bash -lc 'printf "stop\n" > /run/hytale/console'

KillSignal=SIGINT
SuccessExitStatus=143
LimitNOFILE=1048576

TimeoutStopSec=120
Restart=on-failure
RestartSec=5

StandardOutput=journal
StandardError=journal

# Hardening
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectControlGroups=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectClock=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/opt/hytale /run/hytale
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
CapabilityBoundingSet=
AmbientCapabilities=
RemoveIPC=yes
ProtectKernelLogs=yes
ProtectHostname=yes
LockPersonality=yes
SystemCallArchitectures=native
RestrictSUIDSGID=yes
RestrictNamespaces=yes
RestrictRealtime=yes
ProcSubset=pid
ProtectProc=invisible
UMask=0077

[Install]
WantedBy=multi-user.target
EOF

Reload systemd and start the new Hytale service:

systemctl daemon-reload

systemctl enable --now hytale.service

This systemd service is hardened fairly well. If you want to attempt to further harden the service, run an audit first:

systemd-analyze security hytale.service

6 - Create a script for checking for Hytale updates:

I sure wish Hytale published this in an apt repo, for Debian, so that we didn't need to do all this.

sudo tee /usr/local/sbin/hytale-update > /dev/null <<'EOF'
#!/bin/bash
set -euo pipefail

HT_DIR="/opt/hytale"
STATE_FILE="${HT_DIR}/.last_applied_zip"

cd "${HT_DIR}"

# Run downloader as hytale and capture output
OUT="$(/usr/sbin/runuser -u hytale -- ./hytale-downloader)"

# Extract the filename inside quotes after: to "....zip"
ZIP="$(printf '%s\n' "$OUT" | sed -n 's/.*to "\([^"]\+\.zip\)".*/\1/p' | tail -n 1)"

if [[ -z "${ZIP}" ]]; then
  echo "ERROR: Could not determine downloaded zip filename from downloader output."
  echo "Downloader output was:"
  echo "$OUT"
  exit 1
fi

if [[ ! -f "${HT_DIR}/${ZIP}" ]]; then
  echo "ERROR: Downloader claimed ${ZIP} but file is missing at ${HT_DIR}/${ZIP}"
  exit 1
fi

LAST=""
if [[ -f "${STATE_FILE}" ]]; then
  LAST="$(cat "${STATE_FILE}" || true)"
fi

if [[ "${ZIP}" == "${LAST}" ]]; then
  echo "No update to apply (latest already applied): ${ZIP}"
  exit 0
fi

echo "Applying update: ${ZIP}"
/usr/sbin/runuser -u hytale -- /usr/bin/unzip -o "${ZIP}"

echo "${ZIP}" > "${STATE_FILE}"
echo "Update applied successfully: ${ZIP}"
EOF

sudo chmod 0755 /usr/local/sbin/hytale-update
sudo chown root:root /usr/local/sbin/hytale-update

How to run (check) updates manually

It has to download the file in order to check if there is really an update. I wish there was a better way to do this.

sudo /usr/local/sbin/hytale-update

7 - Create an update script for cron using the Hytale server update script

sudo tee /usr/local/sbin/hytale-cron-update > /dev/null <<'EOF'
#!/bin/bash
set -euo pipefail

LOCK="/run/lock/hytale-update.lock"
STATE="/opt/hytale/.last_applied_zip"
LOGTAG="hytale-update-cron"

exec 9>"$LOCK"
flock -n 9 || exit 0

before=""
if [[ -f "$STATE" ]]; then
  before="$(cat "$STATE" || true)"
fi

if ! out="$(/usr/local/sbin/hytale-update 2>&1)"; then
  logger -t "$LOGTAG" "Updater failed: $out"
  exit 1
fi

after=""
if [[ -f "$STATE" ]]; then
  after="$(cat "$STATE" || true)"
fi

if [[ -n "$after" && "$after" != "$before" ]]; then
  logger -t "$LOGTAG" "New update applied ($before -> $after). Restarting hytale.service."
  if systemctl is-active --quiet hytale.service; then
    systemctl restart hytale.service
  else
    systemctl start hytale.service
  fi
else
  logger -t "$LOGTAG" "No new update (still: ${after:-<none>})."
fi
EOF

sudo chmod 0755 /usr/local/sbin/hytale-cron-update

sudo chown root:root /usr/local/sbin/hytale-cron-update

Validate the cron is running:

systemctl status cron --no-pager

Make it check daily at 4 AM:

sudo tee /etc/cron.d/hytale-update > /dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

0 4 * * * root /usr/local/sbin/hytale-cron-update
EOF

sudo chmod 0644 /etc/cron.d/hytale-update
sudo chown root:root /etc/cron.d/hytale-update

8 - Server management

Start the Hytale service: systemctl start hytale.service

Stop the Hytale service: systemctl stop hytale.service

Restart the Hytale service: systemctl restart hytale.service

Show the Hytale service status: systemctl status hytale.service --no-pager

Show the last 200 lines from Hytale: journalctl -u hytale.service -n 200 --no-pager

Show real-time logs for the Hytale service: journalctl -u hytale.service -f

Show the last 200 lines from Hytale cron update service: journalctl -t hytale-update-cron -n 200 --no-pager

9 - Hytale console

This is a script for an interactive console to run server commands without being logged in:

sudo tee /usr/local/sbin/hytale-console > /dev/null <<'EOF'
#!/bin/bash
set -euo pipefail

FIFO="/run/hytale/console"

if ! systemctl is-active --quiet hytale.service; then
  echo "ERROR: hytale.service is not running." >&2
  exit 1
fi

if [[ ! -p "$FIFO" ]]; then
  echo "ERROR: FIFO not found at $FIFO" >&2
  exit 1
fi

# Follow logs in the background (same terminal)
sudo journalctl -u hytale.service -n 0 -f &
JPID=$!

cleanup() { sudo kill "$JPID" 2>/dev/null || true; }
trap cleanup EXIT INT TERM

echo "Connected. Type commands. Type 'quit' to exit."
while true; do
  printf "hytale> "
  IFS= read -r line || break
  [[ -z "$line" ]] && continue
  [[ "$line" == "quit" || "$line" == "exit" ]] && break
  printf '%s\n' "$line" | sudo tee "$FIFO" >/dev/null
done
EOF

sudo chmod 0755 /usr/local/sbin/hytale-console

sudo chown root:root /usr/local/sbin/hytale-console

Run the interactive console:

sudo hytale-console

To exit the interactive console:

exit