Railway

Deploy Headscale - Self-Hosted Tailscale Control Server, Actually Persistent

Self-hosted Tailscale control server - fixed: domain + persistent state

Deploy Headscale - Self-Hosted Tailscale Control Server, Actually Persistent

Just deployed

/var/lib/headscale

Deploy and Host Headscale on Railway

Headscale is the open-source, self-hosted implementation of the Tailscale control server - it lets you run your own coordination server for a WireGuard mesh VPN instead of depending on Tailscale's SaaS. This template runs the official headscale/headscale:stable binary behind a small Alpine wrapper (from Sokanon/railway-headscale, deployed here from a fork so the source stays stable) that substitutes your Railway domain into the config at startup.

About Hosting Headscale

The catalog's existing Headscale template has two defects that together explain its 25% health score, both confirmed by deploying the exact published configuration:

No public domain, so every deploy crash-loops on first boot. The entrypoint script builds server_url from RAILWAY_PUBLIC_DOMAIN and exits immediately if neither that nor HEADSCALE_SERVER_URL is set: ERROR: No server URL configured. The template's own config never generates a domain, so RAILWAY_PUBLIC_DOMAIN is never populated - deploying it as published fails 100% of the time until you notice this in the Railway dashboard and add a domain yourself. This template generates the domain automatically.

No volume, so the server's identity and node registry reset on every restart. Headscale stores its SQLite node database and its own Noise protocol private key under /var/lib/headscale. Without a persistent volume there, both get recreated from scratch on every redeploy - every device you'd registered has to be re-authenticated, and the server itself gets a brand new cryptographic identity, which for a VPN control plane means a working mesh gets reset by routine maintenance. Verified on this template by redeploying twice: the first boot logs no private key file at path, creating...; the second boot (with the volume mounted) does not - the key and database persist as they should.

A minor third fix: the shipped config.yaml used ephemeral_node_inactivity_timeout, a config key headscale deprecated and removed in favor of node.ephemeral.inactivity_timeout. Harmless but noisy

  • it printed a deprecation warning on every boot. Updated to the current key.

Why Deploy Headscale on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Headscale on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.

Specific to this template:

  • Public domain generated automatically - the entrypoint's hard requirement for a server URL is satisfied on first deploy, no manual dashboard step needed.
  • Persistent volume at /var/lib/headscale - node registrations and the server's own key survive redeploys and restarts.
  • Current config schema - no deprecation warnings in the logs.

Common Use Cases

  • A private WireGuard mesh VPN for a team or personal devices, without routing traffic through Tailscale's hosted control plane.
  • Site-to-site connectivity between servers across different clouds/regions, coordinated through your own control server.
  • A self-hosted alternative for organizations that need their VPN coordination plane under their own infrastructure for compliance reasons.

Dependencies for Headscale Hosting

  • None - Headscale is a single Go binary with an embedded SQLite database.

Deployment Dependencies

Implementation Details

Nothing required to fill in - PORT defaults to 8080 and the server URL is derived automatically from the Railway-assigned public domain.

To register a device, use the headscale CLI against this server (or the gRPC/CLI over docker exec if you prefer a local install) to create a user and a pre-auth key, then run tailscale up --login-server=https:// --authkey= on the client. ACL policy mode is file with no policy configured, which headscale treats as unrestricted - add a policy file if you need to restrict which nodes can talk to each other.

Verified on this template: clean boot with no crash loop, /health returns 200, and the Noise private key + SQLite database persist correctly across a redeploy (confirmed by absence of the key-regeneration log line on the second boot).


Template Content

More templates in this category

View Template
open-excalidraw
Self-hostable collaborative drawing built on Excalidraw

Prateek Mohanty
2
View Template
caring-vibrancy
Deploy and Host caring-vibrancy with Railway

5
View Template
Appsmith
Low-code platform for internal tools, dashboards, and admin panels.

Agaz Self-Host
0