Stop Exposing Your Hytale Server Infrastructure

HytaleONE Team
· · 8 min read
Hytale server infrastructure protected by firewall, VPN, and private network boundaries

A Hytale server needs to be reachable by players. Your database, dashboards, metrics endpoints, admin panels, exporters, and backend game servers do not.

That sounds obvious until you scan a real community server box and find PostgreSQL on 5432, Prometheus on 9090, Grafana on 3000, a web panel, Node Exporter, and every backend server listening on the public internet. The game port is supposed to be public. The rest is attack surface.

This guide is the practical lockdown version: what should be exposed, what should bind to localhost, what belongs behind a VPN, and how to verify it from the outside.


In this article: Public Surface · PostgreSQL · Prometheus · Grafana · Docker · Backend Servers · SSH Tunnels · WireGuard · Audit Checklist


The Only Public Service Should Be the Player Entrypoint

Hytale uses QUIC over UDP. The official server manual lists the default port as 5520/udp and explicitly notes that TCP forwarding is not required for player connections.

For a simple single-server setup, your public surface should usually be:

Public internet
└── 5520/udp -> Hytale server

For a network setup with a proxy, your public surface should usually be:

Public internet
└── 5520/udp -> Hytale proxy
    ├── private -> lobby backend
    ├── private -> survival backend
    └── private -> minigame backend

Everything else should be local-only, private-network-only, or VPN-only.

That includes:

  • PostgreSQL
  • Redis
  • Prometheus
  • Grafana
  • Alertmanager
  • Node Exporter
  • JMX Exporter
  • cAdvisor
  • admin panels
  • internal APIs
  • backend Hytale servers behind a proxy

The most common mistake is thinking “it has a password” is enough. A password-protected service is still a public service. It can be brute-forced, fingerprinted, exploited, DoSed, or misconfigured later.

Start With a Default-Deny Firewall

On a plain Debian or Ubuntu host, ufw is enough for most Hytale servers:

apt-get install ufw -y
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp
ufw allow 5520/udp
ufw enable

That example assumes SSH was moved to port 2222, like in our Debian 13 Hytale server guide. If you still use port 22, allow 22/tcp instead.

Check the active rules:

ufw status verbose

Expected public allow list for a basic host:

2222/tcp ALLOW IN Anywhere
5520/udp ALLOW IN Anywhere

If you see 3000/tcp, 5432/tcp, 9090/tcp, 9100/tcp, or random panel ports open to Anywhere, stop and fix that before you advertise the server.

Lock PostgreSQL to Local or Private Network Access

PostgreSQL should almost never listen on the public internet for a Hytale server stack. Your website, panel, or API can talk to it locally or over a private network. Players do not need database access.

PostgreSQL has two layers that matter:

  • listen_addresses controls which interfaces accept connection attempts.
  • pg_hba.conf controls which clients can authenticate after reaching PostgreSQL.

The PostgreSQL docs say the default listen_addresses value is localhost, which allows only local loopback TCP connections. Keep it that way unless you have a specific private-network reason to change it.

In postgresql.conf:

listen_addresses = 'localhost'

In pg_hba.conf, prefer local-only rules:

local   all             postgres                                peer
local   hytale          hytale_app                              scram-sha-256
host    hytale          hytale_app      127.0.0.1/32            scram-sha-256
host    hytale          hytale_app      ::1/128                 scram-sha-256

Reload PostgreSQL after changes:

systemctl reload postgresql

If your app runs on another machine, do not jump straight to listen_addresses = '*' and 0.0.0.0/0. Use a private address or WireGuard address instead:

listen_addresses = '127.0.0.1,10.7.0.1'

Then allow only the specific peer:

host    hytale          hytale_app      10.7.0.2/32             scram-sha-256

Bad pattern:

listen_addresses = '*'
host all all 0.0.0.0/0 md5

That is not “remote database access”. That is a public login prompt attached to your production data.

Do Not Expose Prometheus or Exporters

Prometheus is not just a pretty metrics UI. Its own security model warns that Prometheus HTTP endpoints, /metrics endpoints, and Go /pprof endpoints should not be exposed to the public internet unless you know exactly what you are doing and have added appropriate protections.

Why it matters:

  • Metrics can leak hostnames, ports, paths, usernames, world names, JVM details, plugin names, and internal topology.
  • Query endpoints can be expensive enough to overload the monitoring server.
  • Exporters are designed for scraping by Prometheus, not for internet traffic.
  • Debug endpoints can reveal operational details you do not want public.

Bind Prometheus to localhost if Grafana runs on the same host:

# /etc/prometheus/prometheus.yml stays normal
# systemd or command line controls the web listener
--web.listen-address=127.0.0.1:9090

Bind Node Exporter locally or to a private interface:

node_exporter --web.listen-address=127.0.0.1:9100

For Java/JMX metrics, bind the exporter to localhost too. If you followed our Prometheus and Grafana monitoring guide, make the dashboard path private before adding more exporters.

If Prometheus and exporters are on different hosts, use WireGuard or another private network. Do not scrape across the public internet just because it works.

Put Grafana Behind Auth or a Private Network

Grafana looks safer because it has users, roles, and dashboards. Do not confuse dashboard permissions with data-source isolation.

Grafana’s security docs warn that Viewer users can make arbitrary queries against data sources available to their organization. Anonymous access has similar implications: anyone with access can view dashboards, list resources, and query configured data sources according to the Viewer role.

Good patterns:

  • Bind Grafana to 127.0.0.1 and reach it through SSH tunnels.
  • Bind Grafana to a WireGuard address and require VPN access.
  • Put Grafana behind a reverse proxy with real authentication.
  • Split sensitive data sources into separate organizations or restricted data sources.

Local-only Grafana config:

[server]
http_addr = 127.0.0.1
http_port = 3000

If you must expose Grafana publicly, put it behind HTTPS, disable anonymous access, use strong authentication, keep it patched, and review every data source. Public Grafana is not the default choice for a game server.

Docker Can Bypass Your ufw Assumptions

Docker publishing is a common footgun. Docker’s own docs explain that when you publish container ports, traffic can be routed before it hits the ufw chains you expected. In practice, a container port can become reachable even when your ufw mental model says it should not be.

This is safe:

services:
  postgres:
    image: postgres:18
    ports:
      - "127.0.0.1:5432:5432"

This is not safe for a production database:

services:
  postgres:
    image: postgres:18
    ports:
      - "5432:5432"

The second form binds to all interfaces by default. On a public VPS, that usually means the internet can hit it.

For internal-only services, prefer no published port at all:

services:
  app:
    image: example/hytale-panel
    depends_on:
      - postgres

  postgres:
    image: postgres:18
    expose:
      - "5432"

Inside the Compose network, app can reach postgres:5432. The host and internet do not need a published port.

For your Hytale game server container, publishing the UDP game port is expected:

ports:
  - "5520:5520/udp"

For metrics, databases, and admin panels, bind to localhost or keep them internal.

Block Direct Access to Backend Hytale Servers

If you run a Hytale proxy, the proxy is the public entrypoint. Backend servers should be reachable only from the proxy.

This matters even more for proxy stacks that require backend servers to trust the proxy for authentication or handoff. In our Hytale proxy software comparison, Numdrassl is the clearest example: backend servers run behind the proxy and must not accept direct public connections.

Bind backends to private addresses where possible:

java -XX:AOTCache=HytaleServer.aot \
  -jar HytaleServer.jar \
  --assets Assets.zip \
  --bind 10.7.0.11:5521

Or, if all processes run on one host:

java -XX:AOTCache=HytaleServer.aot \
  -jar HytaleServer.jar \
  --assets Assets.zip \
  --bind 127.0.0.1:5521

Then publish only the proxy:

Public:  5520/udp -> proxy
Private: 5521/udp -> lobby backend
Private: 5522/udp -> survival backend
Private: 5523/udp -> minigame backend

If the backend must listen on a public interface because of hosting constraints, firewall it to the proxy IP only.

Use SSH Tunnels for Emergency Access

SSH tunnels are the fastest way to reach a private service without opening it publicly.

Grafana example:

ssh -L 3000:127.0.0.1:3000 -p 2222 root@your-server

Then open this on your laptop:

http://127.0.0.1:3000

PostgreSQL example:

ssh -L 5432:127.0.0.1:5432 -p 2222 root@your-server

Then connect locally:

psql "postgresql://[email protected]:5432/hytale"

SSH tunnels are great for occasional access. If you manage the stack daily or have multiple admins, use a VPN instead.

Use WireGuard for Regular Admin Access

WireGuard gives every admin and server a private address. You can bind sensitive services to the WireGuard interface and never expose them publicly.

Example layout:

10.7.0.1  main Hytale host
10.7.0.2  admin laptop
10.7.0.3  monitoring host
10.7.0.4  proxy host

Services then bind to 10.7.0.1 instead of 0.0.0.0:

# Grafana
[server]
http_addr = 10.7.0.1
http_port = 3000
# PostgreSQL
listen_addresses = '127.0.0.1,10.7.0.1'

Firewall rules become simple:

ufw allow 5520/udp
ufw allow 2222/tcp
ufw allow in on wg0

WireGuard’s quickstart shows the core model: create a wg0 interface, assign private tunnel addresses, add peers with public keys, and bring the interface up. For production, use wg-quick, keep private keys private, and remove peers when admins leave.

External Audit Checklist

Do not trust local checks alone. Audit from another machine on a different network.

From your laptop:

nmap -sS -sU -Pn -p T:22,2222,80,443,3000,5432,9090,9100,U:5520 your-server-ip

Expected for a basic Hytale server:

2222/tcp open
5520/udp open|filtered

UDP scans often show open|filtered; that is normal. What you do not want is a list of internal TCP services.

On the server, check listeners:

ss -tulpn

Look for bad binds:

0.0.0.0:5432
0.0.0.0:3000
0.0.0.0:9090
0.0.0.0:9100

Good binds look like:

127.0.0.1:5432
127.0.0.1:3000
127.0.0.1:9090
10.7.0.1:3000

Also check Docker publishes:

docker ps --format 'table {{.Names}}\t{{.Ports}}'

Bad:

0.0.0.0:5432->5432/tcp
0.0.0.0:9090->9090/tcp

Better:

127.0.0.1:5432->5432/tcp
5520/udp
0.0.0.0:5520->5520/udp

Minimum Secure Hytale Stack

For a small community server, this is a clean target:

ComponentPublic?Recommended access
Hytale server or proxyYes5520/udp
SSHRestrictedkey-only, non-default port, admin IPs if possible
PostgreSQLNolocalhost or WireGuard
RedisNolocalhost or private network
PrometheusNolocalhost or WireGuard
GrafanaUsually noSSH tunnel, WireGuard, or authenticated reverse proxy
Node ExporterNolocalhost/private scrape only
JMX ExporterNolocalhost/private scrape only
Backend Hytale serversNoproxy-only private access

Public exposure should be intentional, documented, and checked after every deployment change.

Final Rule

If players do not need to connect to it, do not put it on the public internet.

Hytale’s public port is UDP 5520 by default. Your observability stack, database, admin tools, and backend servers are not part of the player-facing protocol. Keep them behind localhost, a private network, SSH tunnels, or WireGuard.

The best security improvement for most game servers is not a fancier password. It is fewer public ports.