HTML5 multiplayer game server hosting
Your HTML, JavaScript and textures can live on a static host. Your real-time multiplayer server also needs a runtime that holds connections open and keeps the game's state in memory. This guide puts both behind one domain on a Linux VPS.
Test scope: with Node.js 26 and Caddy 2.10.2 on Linux, we fetched the page through Caddy and connected two WebSocket clients. Both received player IDs, a shared two-player state and ping replies. The proxy check used local HTTP; public DNS, certificate issuance, systemd service startup and a paid VPS were not tested.
What you are deploying
Browser: https://game.example.com + wss://game.example.com
→ Caddy on ports 80/443
→ Node.js game server on port 3000
The browser-game tutorial supplies the Node.js server and HTML page. Caddy serves as the reverse proxy for both normal page requests and WebSocket upgrades. The page chooses ws:// on local HTTP and wss:// on HTTPS using location.protocol.
Keeping the page and socket on one domain avoids an extra endpoint setting in this example. If you later move the page to a CDN, change the socket URL to your game-server domain and validate allowed origins on the server.
1. Prove it works locally
Download the example ZIP and unzip it. Install Node.js 22 or later and Caddy from its official instructions. Our run used the versions in the test note above.
cd browser-multiplayer
npm install --omit=dev
node server.js
# Open http://localhost:3000 in two tabs.
Stop that process, then run the downloadable proxy smoke test:
python3 deploy/smoke-test.py /absolute/path/to/caddy
# PASS HTTP page through Caddy
# PASS two WebSocket players, shared state and ping replies through Caddy
The test starts Node.js on port 13000 and Caddy on port 18080, swaps only the domain and upstream in the supplied Caddyfile, then stops both processes. It checks application messages, not just an HTTP 101 handshake. Use unused local ports when running it.
2. Choose an always-running runtime
For this unmodified Node.js process, choose a VPS or a managed service that supports persistent WebSocket servers. Hosting that only publishes files cannot run it. Request-oriented serverless runtimes may require a different architecture; they are not a drop-in home for an in-memory game loop. For example, Cloudflare Durable Objects support WebSockets, but require adapting the server to that runtime.
A DigitalOcean basic VPS is listed at $4/month with 512 MiB RAM and 500 GiB transfer, or $6/month with 1 GiB and 1,000 GiB transfer, checked October 14, 2026. Domain registration, backups, tax and transfer overages can add costs. These figures do not establish how many players your game can support.
Colyseus Cloud starts at $15/month if you build with its framework. Playroom is a different SDK-based approach. See the comparison and linked price sources before committing to a provider.
3. Copy the example to the VPS
Use a current Linux distribution with systemd, install Node.js and Caddy, and copy the example's files to /opt/browser-game. The sample service expects Node at /usr/bin/node; check command -v node and adjust the absolute path if needed.
cd /opt/browser-game
npm install --omit=dev
sudo useradd --system --user-group --no-create-home browsergame
# The account needs read access to server.js, public/ and node_modules/.
sudo install -m 644 deploy/game-server.service /etc/systemd/system/browser-game.service
sudo systemctl daemon-reload
sudo systemctl enable --now browser-game
curl --fail http://127.0.0.1:3000/ -o /dev/null
sudo journalctl -u browser-game -n 50 --no-pager
The systemd unit runs the game without root and restarts after a crash. A restart loses this example's in-memory state and disconnects players. It does not restore matches or reconnect clients automatically.
4. Add the domain and HTTPS proxy
Create an A record for your game hostname pointing to the VPS public IPv4 address. Add an AAAA record only if IPv6 reaches the same server. Replace the sample hostname in the Caddyfile:
game.example.com {
reverse_proxy 127.0.0.1:3000
}
Install it as /etc/caddy/Caddyfile on a VPS dedicated to this example; if Caddy already serves other sites, add the site block to the existing file instead. Validate and reload:
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy
sudo journalctl -u caddy -n 50 --no-pager
Allow inbound TCP 80 and 443 in the host and provider firewalls, preserve SSH access, and keep direct inbound port 3000 blocked. With DNS and those ports reachable, Caddy's automatic HTTPS obtains and renews a certificate. Its reverse proxy supports WebSocket upgrades without a separate upgrade-header configuration.
5. Verify from outside the VPS
Open https://YOUR_GAME_DOMAIN in two browser tabs or on two devices. Both should show both players moving. Check the browser's network panel for a connection to wss://YOUR_GAME_DOMAIN with status 101 and incoming state messages.
Run the included bots against that endpoint to inspect round-trip time and payload size:
node test-clients.js wss://YOUR_GAME_DOMAIN 2
This test creates two synthetic players for about ten seconds. Use it on your own endpoint. Remote latency includes the network; local results cannot predict it. The code is a learning example: add input validation, connection/message limits, rooms, authentication where needed, and monitoring before a public release.
Common hosting failures
- Page loads, socket fails: check that an HTTPS page uses
wss://, that Node is alive, and that the proxy points to the correct port. - Caddy returns 502: first check
curl http://127.0.0.1:3000/on the VPS and the game-service logs. - No certificate: check DNS, stale AAAA records and inbound ports 80/443. Read Caddy's logs.
- Players lose the match on deploy: this process stores state in memory. Plan draining, reconnects and persistence explicitly.
- Costs grow with players: broadcasting every player's state to every other player grows traffic roughly with the square of room size. Use small rooms; see the first guide's measurements.
Download the setup
Download the complete browser example, or inspect the Caddyfile, service unit and proxy smoke test.