net_backend

Deploy · self-hosted on your own machine or VPS

Run it yourself.

The repository's deploy/ folder has everything to run a server on one Linux machine (written for Ubuntu 24.04): HTTPS and WSS through Caddy, migrations on every deploy, daily backups with a tested restore, and a hardened SSH login. This page is a summary; the deployment guide walks through every step.

Choose the install path once

Docker Composesystemd
The server runsin a container (non-root, read-only, health-checked)as a service of a dedicated user, with systemd's sandboxing
DatabaseMySQL 8.4 or PostgreSQL 16 in a containerMySQL, PostgreSQL or SQLite on the machine
Caddyin a containerCaddy's own package
Migrationsthe migrate service runs before the server startsExecStartPre=… migrate before every start
Backupsnet-backend-backup (systemd timer) dumps inside the database containerthe same timer, dumping directly
Updatesgit pull + docker compose up -d --buildbuild, then install.sh again (or copy the binary + restart)

Both paths install the reference server: the framework with the Auth, Storage and Chat modules, configured from config.toml. Your own game server has the same command line (serve, migrate, config check, user:create, …), so every file works for it unchanged.

Before you start

Both install paths were installed from scratch and tested on a real Ubuntu 24.04 machine.

HTTPS and WSS with Caddy

Migrations on every deploy

Migrations run automatically before the server starts: a one-shot migrate service in Docker Compose, ExecStartPre=… migrate under systemd.

Backups and restore

A daily systemd timer backs up the database and keeps each backup 14 days:

DatabaseHow
MySQL / MariaDBmysqldump --single-transaction (consistent, writers keep going), checked for its final "Dump completed" line
PostgreSQLpg_dump -Fc, checked with pg_restore --list
SQLitethe online backup API (consistent while the server writes), PRAGMA integrity_check, gzip

A restore that is safe to run. net-backend-restore first checks the whole backup, before anything is stopped or dropped. Then it takes a safety backup of the current state, stops the server, loads the backup (PostgreSQL in one transaction, so a failed load leaves the old data), runs migrate, starts everything and waits until /readyz answers. If a step after the stop fails, it prints the command that puts the safety backup back. The database password never appears on a command line.

restore
sudo net-backend-restore /var/backups/net-backend/net-backend-mysql-20261002T033012Z.sql.gz --yes

A backup on the same disk does not survive the loss of the machine: copy /var/backups/net-backend/ elsewhere every day (for example with restic or rclone).

Measured on a small VPS

Measured on a 2 vCPU / 7.8 GiB virtual machine (Ubuntu 24.04, MySQL 8 or PostgreSQL 16 on the same machine, Caddy in front). The HTTPS rows ran load_test on that same machine, so it shared the 2 vCPU with the server, Caddy and the database:

WhatMeasured
10 000 authenticated WebSockets over HTTPS through Caddyall connected in 56 s (TLS handshakes), none closed during the hold; server ~15 KiB per socket, Caddy ~121 KiB
A redeploy: 5000 sockets closed with 1001 by a restartall 5000 reconnected (p99 17 s, bound by TLS handshakes on the shared CPUs)
Chat over HTTPS: 200 members, 50 senders at 1 message/s, every message stored600 000 of 600 000 deliveries, p99 294 ms (MySQL), 241 ms (PostgreSQL)
Room fan-out on loopback (one sender, 64-byte pushes)105 000 deliveries/s into 200 members; 149 000/s into 1000 members
1000 players saving for the first time at the same momentall 1000 stored in 8.6 s (MySQL), 7.4 s (PostgreSQL)
4 MiB batch saves through Caddy10 of 10, median 0.97 s (MySQL), 0.47 s (PostgreSQL)
HTTP through Caddy, 64 connections/v1/info ~4 500 requests/s; GET /v1/account 770 (MySQL) / 890 (PostgreSQL); a storage read 940 / 1 050

Memory: ~15 KiB per socket in the server plus ~120 KiB in Caddy, so roughly 1.4 GB per 10 000 connected players, on top of the database's buffer pool and the system (the default ws.max_connections = 10000 wants a 4 GB machine). Raise ws.max_connections only after a load test on the machine itself.

Load-test your own machine with load_test, a tool in the repository: it creates accounts, opens sockets, runs chat fan-out, DM bursts, simultaneous first saves, 4 MiB batch puts and HTTP request rates through Caddy, and prints latency percentiles. The full capacity section →

Security checklist

Read the full deployment guide →