Vibecode Tailscale
track this build5 phases, 10 steps, beginner friendly0%The data plane is WireGuard and the clients are open source. The coordination server is what Tailscale runs, and headscale reimplements it well enough for a homelab. The free tier already covers six users, so the honest DIY case is wanting the control plane on your own box.
You are building a lean indie version of Tailscale. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Tailscale · indie build Your own Tailscale control plane: headscale on a VPS behind Caddy, the official Tailscale clients pointed at it, MagicDNS and a deny-by-default ACL, an embedded relay and an exit node, and a restore you performed before switching off the free account. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Control plane | headscale | rule zero: do not write a VPN | | Data plane | WireGuard via the official Tailscale clients | you never touch the cryptography | | TLS | Caddy | clients need HTTPS to the control server | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Know that Tailscale is free for six users** · free - Why: The honest reason to do this is owning the control plane, not saving money. - Get it: Read tailscale.com/pricing once. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. The coordination server needs a public IP. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: hs.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **headscale** · free - Why: The control server. - Get it: Download the .deb from github.com/juanfont/headscale/releases and install it on the VPS. - Verify: headscale version prints - [ ] **The official Tailscale clients on every device** · free - Why: The endpoints. - Get it: tailscale.com/download for each platform. - [ ] **Off-box storage for backups** · cents - Why: The headscale database is the network's identity. - Get it: rclone to B2 or R2. ## Quick start ```sh sudo systemctl enable --now headscale headscale users create you ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. - SSO with every identity provider, done - the admin console and device posture - Funnel and the relay network at their scale - someone else on call for the control plane If one of those is essential to you, that is the reason to keep paying for Tailscale, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Tailscale The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Tailscale control plane. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. This is an operations task with a restore drill at the end. ### Rule zero Do not write a VPN and do not touch the cryptography. WireGuard is the data plane, the official Tailscale clients are the endpoints, and headscale is the coordination server. Your work is deploying and operating it. ### Stack (fixed, do not substitute) - headscale on a small VPS with a public IP, behind Caddy for TLS on a subdomain. The official Tailscale clients on every device. ### Phase 1 · The server Build: headscale installed with its config pointing at your public URL, Caddy in front, a systemd unit, and one user created. Done when: the health endpoint answers over HTTPS and headscale users list shows your user. Do not build yet: clients, ACLs. ### Phase 2 · First two devices Build: register a laptop and a phone against your server with pre-auth keys, and confirm they reach each other by Tailscale IP. Done when: a ping between the two devices succeeds over the mesh and traffic does not traverse the VPS (check with a direct connection in the client status). ### Phase 3 · DNS and ACLs Build: MagicDNS with a base domain, and an ACL policy file that denies by default and allows what you need. Test a denied path. Done when: devices resolve each other by name, an allowed port connects, and a denied port is refused. ### Phase 4 · Relays and exit node Build: enable an embedded DERP relay for devices behind hostile NATs and configure one machine as an exit node. Done when: a device on a restrictive network still connects (via relay) and routing through the exit node changes your public IP. ### Phase 5 · Backup and restore drill Build: nightly backup of the headscale database and config off the box, and restore.sh. Then restore into a fresh VPS and confirm existing devices reconnect without re-registering. Done when: you have performed one restore and the devices came back. Until then, keep the free Tailscale account. ### Out of scope (and why) - SSO with every provider, the admin console, device posture. That is the seat price, and for six people it is free anyway. ### README must contain - The date of the last restore drill. - How to add a device with a pre-auth key. ===== AGENTS.md ===== # Agent instructions · Tailscale indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: headscale, WireGuard via the official Tailscale clients, Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Until the restore drill is done, keep the free Tailscale account. ===== BUILD_PLAN.md ===== # Build plan · Tailscale Your own Tailscale control plane: headscale on a VPS behind Caddy, the official Tailscale clients pointed at it, MagicDNS and a deny-by-default ACL, an embedded relay and an exit node, and a restore you performed before switching off the free account. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The server headscale up behind Caddy with one user. ### Steps 1. Install headscale, set server_url and the base domain in config.yaml, Caddy in front Files: `/etc/headscale/config.yaml`, `Caddyfile` 2. Create your user ```sh sudo systemctl enable --now headscale headscale users create you ``` ### Done when - [ ] The health endpoint answers over HTTPS - [ ] headscale users list shows your user ## Phase 2 · First two devices A laptop and a phone reach each other directly. ### Steps 1. Generate a pre-auth key and register two devices ```sh headscale preauthkeys create --user you --reusable --expiration 24h tailscale up --login-server https://hs.yourdomain.com --authkey <key> ``` 2. Ping between them and confirm a direct connection in tailscale status ### Done when - [ ] A ping succeeds over the mesh - [ ] The connection is direct, not via the VPS ## Phase 3 · DNS and ACLs Names that resolve and a policy that denies by default. ### Steps 1. Enable MagicDNS with BASE_DOMAIN 2. Write an ACL policy denying by default and test a denied port ### Done when - [ ] Devices resolve each other by name - [ ] An allowed port connects; a denied port is refused ## Phase 4 · Relays and exit node Hostile NATs still connect; one machine routes traffic. ### Steps 1. Enable the embedded DERP relay 2. Advertise and approve an exit node ```sh tailscale up --advertise-exit-node headscale routes enable -r <id> ``` ### Done when - [ ] A device on a restrictive network still connects - [ ] Routing through the exit node changes your public IP ## Phase 5 · Backup and restore drill The database off the box and a restore that keeps devices connected. ### Steps 1. Nightly backup of the headscale database and config off the box ```sh sqlite3 /var/lib/headscale/db.sqlite ".backup '/tmp/hs-$(date +%F).db'" rclone copy /tmp/hs-$(date +%F).db remote:headscale/ ``` 2. Restore into a fresh VPS and confirm existing devices reconnect without re-registering ### Done when - [ ] A restore performed and dated - [ ] Devices reconnect without re-registering ### Watch out - Until the restore drill is done, keep the free Tailscale account. ## Not in this build - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. ## After v1, if you want it - OIDC login via headscale's OIDC support - Subnet routers for a home network ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Public server URL in config.yaml. HEADSCALE_URL=https://hs.yourdomain.com # Required. MagicDNS base domain; must differ from the server's domain. BASE_DOMAIN=ts.yourdomain.internal
You are building a lean indie version of Tailscale. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Tailscale · indie build Your own Tailscale control plane: headscale on a VPS behind Caddy, the official Tailscale clients pointed at it, MagicDNS and a deny-by-default ACL, an embedded relay and an exit node, and a restore you performed before switching off the free account. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Control plane | headscale | rule zero: do not write a VPN | | Data plane | WireGuard via the official Tailscale clients | you never touch the cryptography | | TLS | Caddy | clients need HTTPS to the control server | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Know that Tailscale is free for six users** · free - Why: The honest reason to do this is owning the control plane, not saving money. - Get it: Read tailscale.com/pricing once. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. The coordination server needs a public IP. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: hs.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **headscale** · free - Why: The control server. - Get it: Download the .deb from github.com/juanfont/headscale/releases and install it on the VPS. - Verify: headscale version prints - [ ] **The official Tailscale clients on every device** · free - Why: The endpoints. - Get it: tailscale.com/download for each platform. - [ ] **Off-box storage for backups** · cents - Why: The headscale database is the network's identity. - Get it: rclone to B2 or R2. ## Quick start ```sh sudo systemctl enable --now headscale headscale users create you ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. - SSO with every identity provider, done - the admin console and device posture - Funnel and the relay network at their scale - someone else on call for the control plane If one of those is essential to you, that is the reason to keep paying for Tailscale, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Tailscale The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Tailscale control plane. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. This is an operations task with a restore drill at the end. ### Rule zero Do not write a VPN and do not touch the cryptography. WireGuard is the data plane, the official Tailscale clients are the endpoints, and headscale is the coordination server. Your work is deploying and operating it. ### Stack (fixed, do not substitute) - headscale on a small VPS with a public IP, behind Caddy for TLS on a subdomain. The official Tailscale clients on every device. ### Phase 1 · The server Build: headscale installed with its config pointing at your public URL, Caddy in front, a systemd unit, and one user created. Done when: the health endpoint answers over HTTPS and headscale users list shows your user. Do not build yet: clients, ACLs. ### Phase 2 · First two devices Build: register a laptop and a phone against your server with pre-auth keys, and confirm they reach each other by Tailscale IP. Done when: a ping between the two devices succeeds over the mesh and traffic does not traverse the VPS (check with a direct connection in the client status). ### Phase 3 · DNS and ACLs Build: MagicDNS with a base domain, and an ACL policy file that denies by default and allows what you need. Test a denied path. Done when: devices resolve each other by name, an allowed port connects, and a denied port is refused. ### Phase 4 · Relays and exit node Build: enable an embedded DERP relay for devices behind hostile NATs and configure one machine as an exit node. Done when: a device on a restrictive network still connects (via relay) and routing through the exit node changes your public IP. ### Phase 5 · Backup and restore drill Build: nightly backup of the headscale database and config off the box, and restore.sh. Then restore into a fresh VPS and confirm existing devices reconnect without re-registering. Done when: you have performed one restore and the devices came back. Until then, keep the free Tailscale account. ### Out of scope (and why) - SSO with every provider, the admin console, device posture. That is the seat price, and for six people it is free anyway. ### README must contain - The date of the last restore drill. - How to add a device with a pre-auth key. ===== AGENTS.md ===== # Agent instructions · Tailscale indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: headscale, WireGuard via the official Tailscale clients, Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Until the restore drill is done, keep the free Tailscale account. ===== BUILD_PLAN.md ===== # Build plan · Tailscale Your own Tailscale control plane: headscale on a VPS behind Caddy, the official Tailscale clients pointed at it, MagicDNS and a deny-by-default ACL, an embedded relay and an exit node, and a restore you performed before switching off the free account. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The server headscale up behind Caddy with one user. ### Steps 1. Install headscale, set server_url and the base domain in config.yaml, Caddy in front Files: `/etc/headscale/config.yaml`, `Caddyfile` 2. Create your user ```sh sudo systemctl enable --now headscale headscale users create you ``` ### Done when - [ ] The health endpoint answers over HTTPS - [ ] headscale users list shows your user ## Phase 2 · First two devices A laptop and a phone reach each other directly. ### Steps 1. Generate a pre-auth key and register two devices ```sh headscale preauthkeys create --user you --reusable --expiration 24h tailscale up --login-server https://hs.yourdomain.com --authkey <key> ``` 2. Ping between them and confirm a direct connection in tailscale status ### Done when - [ ] A ping succeeds over the mesh - [ ] The connection is direct, not via the VPS ## Phase 3 · DNS and ACLs Names that resolve and a policy that denies by default. ### Steps 1. Enable MagicDNS with BASE_DOMAIN 2. Write an ACL policy denying by default and test a denied port ### Done when - [ ] Devices resolve each other by name - [ ] An allowed port connects; a denied port is refused ## Phase 4 · Relays and exit node Hostile NATs still connect; one machine routes traffic. ### Steps 1. Enable the embedded DERP relay 2. Advertise and approve an exit node ```sh tailscale up --advertise-exit-node headscale routes enable -r <id> ``` ### Done when - [ ] A device on a restrictive network still connects - [ ] Routing through the exit node changes your public IP ## Phase 5 · Backup and restore drill The database off the box and a restore that keeps devices connected. ### Steps 1. Nightly backup of the headscale database and config off the box ```sh sqlite3 /var/lib/headscale/db.sqlite ".backup '/tmp/hs-$(date +%F).db'" rclone copy /tmp/hs-$(date +%F).db remote:headscale/ ``` 2. Restore into a fresh VPS and confirm existing devices reconnect without re-registering ### Done when - [ ] A restore performed and dated - [ ] Devices reconnect without re-registering ### Watch out - Until the restore drill is done, keep the free Tailscale account. ## Not in this build - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. ## After v1, if you want it - OIDC login via headscale's OIDC support - Subnet routers for a home network ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Public server URL in config.yaml. HEADSCALE_URL=https://hs.yourdomain.com # Required. MagicDNS base domain; must differ from the server's domain. BASE_DOMAIN=ts.yourdomain.internal
You are building a production product version of Tailscale. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Tailscale · product brief ## Problem The data plane is WireGuard and the clients are open source. The coordination server is what Tailscale runs, and headscale reimplements it well enough for a homelab. The free tier already covers six users, so the honest DIY case is wanting the control plane on your own box. ## Product outcome A private network whose control plane you own, with a proven restore and a documented upgrade. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a VPS with a public IP - a domain - the official clients on every device ## Explicit non-goals for v1 - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. - SSO with every identity provider, done - the admin console and device posture - Funnel and the relay network at their scale - someone else on call for the control plane ## Success criteria - Restore drill dated - ACL deny-by-default verified - One upgrade performed ===== BRIEF.md ===== # Build brief · Tailscale The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Tailscale control plane. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. This is an operations task with a restore drill at the end. ### Rule zero Do not write a VPN and do not touch the cryptography. WireGuard is the data plane, the official Tailscale clients are the endpoints, and headscale is the coordination server. Your work is deploying and operating it. ### Stack (fixed, do not substitute) - headscale on a small VPS with a public IP, behind Caddy for TLS on a subdomain. The official Tailscale clients on every device. ### Phase 1 · The server Build: headscale installed with its config pointing at your public URL, Caddy in front, a systemd unit, and one user created. Done when: the health endpoint answers over HTTPS and headscale users list shows your user. Do not build yet: clients, ACLs. ### Phase 2 · First two devices Build: register a laptop and a phone against your server with pre-auth keys, and confirm they reach each other by Tailscale IP. Done when: a ping between the two devices succeeds over the mesh and traffic does not traverse the VPS (check with a direct connection in the client status). ### Phase 3 · DNS and ACLs Build: MagicDNS with a base domain, and an ACL policy file that denies by default and allows what you need. Test a denied path. Done when: devices resolve each other by name, an allowed port connects, and a denied port is refused. ### Phase 4 · Relays and exit node Build: enable an embedded DERP relay for devices behind hostile NATs and configure one machine as an exit node. Done when: a device on a restrictive network still connects (via relay) and routing through the exit node changes your public IP. ### Phase 5 · Backup and restore drill Build: nightly backup of the headscale database and config off the box, and restore.sh. Then restore into a fresh VPS and confirm existing devices reconnect without re-registering. Done when: you have performed one restore and the devices came back. Until then, keep the free Tailscale account. ### Out of scope (and why) - SSO with every provider, the admin console, device posture. That is the seat price, and for six people it is free anyway. ### README must contain - The date of the last restore drill. - How to add a device with a pre-auth key. ===== ARCHITECTURE.md ===== # Architecture · Tailscale ## Stack | Part | Choice | Why | | --- | --- | --- | | Control plane | headscale | rule zero: do not write a VPN | | Data plane | WireGuard via the official Tailscale clients | you never touch the cryptography | | TLS | Caddy | clients need HTTPS to the control server | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | headscale | coordination, users, ACLs | Tailscale's hosted plane, same clients | | Clients | WireGuard | Never | | Caddy | TLS | Any reverse proxy | | Backup | the database | Any storage | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `HEADSCALE_URL` · required · Public server URL in config.yaml. - `BASE_DOMAIN` · required · MagicDNS base domain; must differ from the server's domain. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Tailscale product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: headscale, WireGuard via the official Tailscale clients, Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Until the restore drill is done, keep the free Tailscale account. ===== MILESTONES.md ===== # Delivery milestones · Tailscale Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The server headscale up behind Caddy with one user. ### Steps 1. Install headscale, set server_url and the base domain in config.yaml, Caddy in front Files: `/etc/headscale/config.yaml`, `Caddyfile` 2. Create your user ```sh sudo systemctl enable --now headscale headscale users create you ``` ### Done when - [ ] The health endpoint answers over HTTPS - [ ] headscale users list shows your user ## M2 · First two devices A laptop and a phone reach each other directly. ### Steps 1. Generate a pre-auth key and register two devices ```sh headscale preauthkeys create --user you --reusable --expiration 24h tailscale up --login-server https://hs.yourdomain.com --authkey <key> ``` 2. Ping between them and confirm a direct connection in tailscale status ### Done when - [ ] A ping succeeds over the mesh - [ ] The connection is direct, not via the VPS ## M3 · DNS and ACLs Names that resolve and a policy that denies by default. ### Steps 1. Enable MagicDNS with BASE_DOMAIN 2. Write an ACL policy denying by default and test a denied port ### Done when - [ ] Devices resolve each other by name - [ ] An allowed port connects; a denied port is refused ## M4 · Relays and exit node Hostile NATs still connect; one machine routes traffic. ### Steps 1. Enable the embedded DERP relay 2. Advertise and approve an exit node ```sh tailscale up --advertise-exit-node headscale routes enable -r <id> ``` ### Done when - [ ] A device on a restrictive network still connects - [ ] Routing through the exit node changes your public IP ## M5 · Backup and restore drill The database off the box and a restore that keeps devices connected. ### Steps 1. Nightly backup of the headscale database and config off the box ```sh sqlite3 /var/lib/headscale/db.sqlite ".backup '/tmp/hs-$(date +%F).db'" rclone copy /tmp/hs-$(date +%F).db remote:headscale/ ``` 2. Restore into a fresh VPS and confirm existing devices reconnect without re-registering ### Done when - [ ] A restore performed and dated - [ ] Devices reconnect without re-registering ### Watch out - Until the restore drill is done, keep the free Tailscale account. ## M6 · Operate (production only) Updates, monitoring, key expiry handling. ### Steps 1. An update procedure for headscale with a backup first 2. Uptime on the control server and a reminder for node key expiry ### Done when - [ ] One upgrade performed - [ ] An alert fires when headscale stops ===== OPERATIONS.md ===== # Operations · Tailscale ## Backup Nightly database and config off the box. ## Restore Verified once; re-verify after upgrades. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on the control URL. ## Incident checklist If the server is lost without backup, every device must re-register; this is why the drill matters. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Restore drill dated - [ ] ACL deny-by-default verified - [ ] One upgrade performed ## Launch constraint Do not market omitted Tailscale capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Public server URL in config.yaml. HEADSCALE_URL=https://hs.yourdomain.com # Required. MagicDNS base domain; must differ from the server's domain. BASE_DOMAIN=ts.yourdomain.internal
# Tailscale · indie build Your own Tailscale control plane: headscale on a VPS behind Caddy, the official Tailscale clients pointed at it, MagicDNS and a deny-by-default ACL, an embedded relay and an exit node, and a restore you performed before switching off the free account. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Control plane | headscale | rule zero: do not write a VPN | | Data plane | WireGuard via the official Tailscale clients | you never touch the cryptography | | TLS | Caddy | clients need HTTPS to the control server | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Know that Tailscale is free for six users** · free - Why: The honest reason to do this is owning the control plane, not saving money. - Get it: Read tailscale.com/pricing once. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. The coordination server needs a public IP. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: hs.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **headscale** · free - Why: The control server. - Get it: Download the .deb from github.com/juanfont/headscale/releases and install it on the VPS. - Verify: headscale version prints - [ ] **The official Tailscale clients on every device** · free - Why: The endpoints. - Get it: tailscale.com/download for each platform. - [ ] **Off-box storage for backups** · cents - Why: The headscale database is the network's identity. - Get it: rclone to B2 or R2. ## Quick start ```sh sudo systemctl enable --now headscale headscale users create you ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. - SSO with every identity provider, done - the admin console and device posture - Funnel and the relay network at their scale - someone else on call for the control plane If one of those is essential to you, that is the reason to keep paying for Tailscale, and the README should say so rather than pretend.
# Build brief · Tailscale The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Tailscale control plane. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. This is an operations task with a restore drill at the end. ### Rule zero Do not write a VPN and do not touch the cryptography. WireGuard is the data plane, the official Tailscale clients are the endpoints, and headscale is the coordination server. Your work is deploying and operating it. ### Stack (fixed, do not substitute) - headscale on a small VPS with a public IP, behind Caddy for TLS on a subdomain. The official Tailscale clients on every device. ### Phase 1 · The server Build: headscale installed with its config pointing at your public URL, Caddy in front, a systemd unit, and one user created. Done when: the health endpoint answers over HTTPS and headscale users list shows your user. Do not build yet: clients, ACLs. ### Phase 2 · First two devices Build: register a laptop and a phone against your server with pre-auth keys, and confirm they reach each other by Tailscale IP. Done when: a ping between the two devices succeeds over the mesh and traffic does not traverse the VPS (check with a direct connection in the client status). ### Phase 3 · DNS and ACLs Build: MagicDNS with a base domain, and an ACL policy file that denies by default and allows what you need. Test a denied path. Done when: devices resolve each other by name, an allowed port connects, and a denied port is refused. ### Phase 4 · Relays and exit node Build: enable an embedded DERP relay for devices behind hostile NATs and configure one machine as an exit node. Done when: a device on a restrictive network still connects (via relay) and routing through the exit node changes your public IP. ### Phase 5 · Backup and restore drill Build: nightly backup of the headscale database and config off the box, and restore.sh. Then restore into a fresh VPS and confirm existing devices reconnect without re-registering. Done when: you have performed one restore and the devices came back. Until then, keep the free Tailscale account. ### Out of scope (and why) - SSO with every provider, the admin console, device posture. That is the seat price, and for six people it is free anyway. ### README must contain - The date of the last restore drill. - How to add a device with a pre-auth key.
# Agent instructions · Tailscale indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: headscale, WireGuard via the official Tailscale clients, Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Until the restore drill is done, keep the free Tailscale account.
# Build plan · Tailscale Your own Tailscale control plane: headscale on a VPS behind Caddy, the official Tailscale clients pointed at it, MagicDNS and a deny-by-default ACL, an embedded relay and an exit node, and a restore you performed before switching off the free account. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · The server headscale up behind Caddy with one user. ### Steps 1. Install headscale, set server_url and the base domain in config.yaml, Caddy in front Files: `/etc/headscale/config.yaml`, `Caddyfile` 2. Create your user ```sh sudo systemctl enable --now headscale headscale users create you ``` ### Done when - [ ] The health endpoint answers over HTTPS - [ ] headscale users list shows your user ## Phase 2 · First two devices A laptop and a phone reach each other directly. ### Steps 1. Generate a pre-auth key and register two devices ```sh headscale preauthkeys create --user you --reusable --expiration 24h tailscale up --login-server https://hs.yourdomain.com --authkey <key> ``` 2. Ping between them and confirm a direct connection in tailscale status ### Done when - [ ] A ping succeeds over the mesh - [ ] The connection is direct, not via the VPS ## Phase 3 · DNS and ACLs Names that resolve and a policy that denies by default. ### Steps 1. Enable MagicDNS with BASE_DOMAIN 2. Write an ACL policy denying by default and test a denied port ### Done when - [ ] Devices resolve each other by name - [ ] An allowed port connects; a denied port is refused ## Phase 4 · Relays and exit node Hostile NATs still connect; one machine routes traffic. ### Steps 1. Enable the embedded DERP relay 2. Advertise and approve an exit node ```sh tailscale up --advertise-exit-node headscale routes enable -r <id> ``` ### Done when - [ ] A device on a restrictive network still connects - [ ] Routing through the exit node changes your public IP ## Phase 5 · Backup and restore drill The database off the box and a restore that keeps devices connected. ### Steps 1. Nightly backup of the headscale database and config off the box ```sh sqlite3 /var/lib/headscale/db.sqlite ".backup '/tmp/hs-$(date +%F).db'" rclone copy /tmp/hs-$(date +%F).db remote:headscale/ ``` 2. Restore into a fresh VPS and confirm existing devices reconnect without re-registering ### Done when - [ ] A restore performed and dated - [ ] Devices reconnect without re-registering ### Watch out - Until the restore drill is done, keep the free Tailscale account. ## Not in this build - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. ## After v1, if you want it - OIDC login via headscale's OIDC support - Subnet routers for a home network
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Public server URL in config.yaml. HEADSCALE_URL=https://hs.yourdomain.com # Required. MagicDNS base domain; must differ from the server's domain. BASE_DOMAIN=ts.yourdomain.internal
# Tailscale · product brief ## Problem The data plane is WireGuard and the clients are open source. The coordination server is what Tailscale runs, and headscale reimplements it well enough for a homelab. The free tier already covers six users, so the honest DIY case is wanting the control plane on your own box. ## Product outcome A private network whose control plane you own, with a proven restore and a documented upgrade. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a VPS with a public IP - a domain - the official clients on every device ## Explicit non-goals for v1 - SSO with every provider, the admin console, device posture: the seat price, and free for six people anyway. - SSO with every identity provider, done - the admin console and device posture - Funnel and the relay network at their scale - someone else on call for the control plane ## Success criteria - Restore drill dated - ACL deny-by-default verified - One upgrade performed
# Build brief · Tailscale The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a self-hosted Tailscale control plane. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. This is an operations task with a restore drill at the end. ### Rule zero Do not write a VPN and do not touch the cryptography. WireGuard is the data plane, the official Tailscale clients are the endpoints, and headscale is the coordination server. Your work is deploying and operating it. ### Stack (fixed, do not substitute) - headscale on a small VPS with a public IP, behind Caddy for TLS on a subdomain. The official Tailscale clients on every device. ### Phase 1 · The server Build: headscale installed with its config pointing at your public URL, Caddy in front, a systemd unit, and one user created. Done when: the health endpoint answers over HTTPS and headscale users list shows your user. Do not build yet: clients, ACLs. ### Phase 2 · First two devices Build: register a laptop and a phone against your server with pre-auth keys, and confirm they reach each other by Tailscale IP. Done when: a ping between the two devices succeeds over the mesh and traffic does not traverse the VPS (check with a direct connection in the client status). ### Phase 3 · DNS and ACLs Build: MagicDNS with a base domain, and an ACL policy file that denies by default and allows what you need. Test a denied path. Done when: devices resolve each other by name, an allowed port connects, and a denied port is refused. ### Phase 4 · Relays and exit node Build: enable an embedded DERP relay for devices behind hostile NATs and configure one machine as an exit node. Done when: a device on a restrictive network still connects (via relay) and routing through the exit node changes your public IP. ### Phase 5 · Backup and restore drill Build: nightly backup of the headscale database and config off the box, and restore.sh. Then restore into a fresh VPS and confirm existing devices reconnect without re-registering. Done when: you have performed one restore and the devices came back. Until then, keep the free Tailscale account. ### Out of scope (and why) - SSO with every provider, the admin console, device posture. That is the seat price, and for six people it is free anyway. ### README must contain - The date of the last restore drill. - How to add a device with a pre-auth key.
# Architecture · Tailscale ## Stack | Part | Choice | Why | | --- | --- | --- | | Control plane | headscale | rule zero: do not write a VPN | | Data plane | WireGuard via the official Tailscale clients | you never touch the cryptography | | TLS | Caddy | clients need HTTPS to the control server | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | headscale | coordination, users, ACLs | Tailscale's hosted plane, same clients | | Clients | WireGuard | Never | | Caddy | TLS | Any reverse proxy | | Backup | the database | Any storage | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `HEADSCALE_URL` · required · Public server URL in config.yaml. - `BASE_DOMAIN` · required · MagicDNS base domain; must differ from the server's domain. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Tailscale product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: headscale, WireGuard via the official Tailscale clients, Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Until the restore drill is done, keep the free Tailscale account.
# Delivery milestones · Tailscale Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The server headscale up behind Caddy with one user. ### Steps 1. Install headscale, set server_url and the base domain in config.yaml, Caddy in front Files: `/etc/headscale/config.yaml`, `Caddyfile` 2. Create your user ```sh sudo systemctl enable --now headscale headscale users create you ``` ### Done when - [ ] The health endpoint answers over HTTPS - [ ] headscale users list shows your user ## M2 · First two devices A laptop and a phone reach each other directly. ### Steps 1. Generate a pre-auth key and register two devices ```sh headscale preauthkeys create --user you --reusable --expiration 24h tailscale up --login-server https://hs.yourdomain.com --authkey <key> ``` 2. Ping between them and confirm a direct connection in tailscale status ### Done when - [ ] A ping succeeds over the mesh - [ ] The connection is direct, not via the VPS ## M3 · DNS and ACLs Names that resolve and a policy that denies by default. ### Steps 1. Enable MagicDNS with BASE_DOMAIN 2. Write an ACL policy denying by default and test a denied port ### Done when - [ ] Devices resolve each other by name - [ ] An allowed port connects; a denied port is refused ## M4 · Relays and exit node Hostile NATs still connect; one machine routes traffic. ### Steps 1. Enable the embedded DERP relay 2. Advertise and approve an exit node ```sh tailscale up --advertise-exit-node headscale routes enable -r <id> ``` ### Done when - [ ] A device on a restrictive network still connects - [ ] Routing through the exit node changes your public IP ## M5 · Backup and restore drill The database off the box and a restore that keeps devices connected. ### Steps 1. Nightly backup of the headscale database and config off the box ```sh sqlite3 /var/lib/headscale/db.sqlite ".backup '/tmp/hs-$(date +%F).db'" rclone copy /tmp/hs-$(date +%F).db remote:headscale/ ``` 2. Restore into a fresh VPS and confirm existing devices reconnect without re-registering ### Done when - [ ] A restore performed and dated - [ ] Devices reconnect without re-registering ### Watch out - Until the restore drill is done, keep the free Tailscale account. ## M6 · Operate (production only) Updates, monitoring, key expiry handling. ### Steps 1. An update procedure for headscale with a backup first 2. Uptime on the control server and a reminder for node key expiry ### Done when - [ ] One upgrade performed - [ ] An alert fires when headscale stops
# Operations · Tailscale ## Backup Nightly database and config off the box. ## Restore Verified once; re-verify after upgrades. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on the control URL. ## Incident checklist If the server is lost without backup, every device must re-register; this is why the drill matters. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Restore drill dated - [ ] ACL deny-by-default verified - [ ] One upgrade performed ## Launch constraint Do not market omitted Tailscale capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Public server URL in config.yaml. HEADSCALE_URL=https://hs.yourdomain.com # Required. MagicDNS base domain; must differ from the server's domain. BASE_DOMAIN=ts.yourdomain.internal
$ choose a build depth, inspect the files, then open the complete pack in your agent
Six users are free. Above that, companies pay for SSO, ACLs in a UI and a control plane that is not their problem.
xSSO with every identity provider, done
xthe admin console and device posture
xFunnel and the relay network at their scale
xsomeone else on call for the control plane
Tailscale pricing
standard$8/mo · monthly per user · $96/yr
free tierThe free Personal plan covers up to 6 users, unlimited devices and nearly every feature.
verified 2026-09-04 · source ↗
Is Tailscale free?
The free Personal plan covers up to 6 users, unlimited devices and nearly every feature. Paid is Standard at $8/mo (checked 2026-09-04).
Vibecode Tailscale
Kinda. The core of Tailscale is buildable in a weekend with the prompt on this page, but there are real gaps: SSO with every identity provider, done, the admin console and device posture. Read the honest list above before committing.
How much does Tailscale cost?
Tailscale costs about $8/month (Standard, checked 2026-09-04), which is $96 per year.
What do I lose by replacing Tailscale?
Honestly: SSO with every identity provider, done; the admin console and device posture; Funnel and the relay network at their scale; someone else on call for the control plane. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Tailscale?
Yes: headscale (open-source implementation of the Tailscale control server). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.