| LICENSE | ||
| README.md | ||
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:
- Leaving the default port makes it easier for people to get to your server - they can just type a domain without a port.
- Changing the Hytale port makes it harder for bots or hackers to find your Hytale server.
- 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. - You don't need
fail2banif you uselimitfor 22/tcp, or simply safelist your source IP. - 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