Vibecode ngrok
track this build5 phases, 10 steps, beginner friendly0%A tunnel is an SSH reverse forward or a WireGuard peer plus a reverse proxy on a $5 VPS; that is a sitting. What you do not get is the global edge, the request inspector, and OAuth in front of the tunnel in one flag. cloudflared does most of it free.
You are building a lean indie version of ngrok. 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 ===== # ngrok · indie build A tunnel to localhost on a VPS you already have: frp exposes a local port on a subdomain you own, Caddy terminates TLS, every tunnel is protected by default, and a local log shows and replays incoming requests. cloudflared does most of this for free; this is for owning the plumbing. Estimated effort: **one sitting**. 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 | | --- | --- | --- | | Tunnel | frp (server on the VPS, client locally) | a mature reverse proxy, not a hand-rolled TCP forwarder | | TLS | Caddy with a wildcard subdomain | one certificate for every tunnel name | ## 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 - [ ] **Consider cloudflared first** · free - Why: Cloudflare Tunnel is free and covers most hobby use with one command. This build is for wanting your own box. - Get it: developers.cloudflare.com/cloudflare-one/connections/connect-networks - [ ] **A VPS with a public IP** · about $5 a month - Why: The public end of the tunnel. - Get it: Smallest Ubuntu 24.04 instance. - [ ] **A domain with a wildcard record** · free on a domain you own - Why: *.tunnel.yourdomain.com so each tunnel gets a name. - Get it: Add an A record for *.tunnel pointing at the VPS. - [ ] **frp binaries** · free - Why: frps on the server, frpc on your machine. - Get it: Download the release for each platform from github.com/fatedier/frp/releases. - Verify: frps --version and frpc --version print - [ ] **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 ## Quick start ```sh caddy hash-password ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. - the request inspector and replay - OAuth and IP policies in front of the tunnel with one flag - the global edge and its latency - TCP and TLS tunnels without setup If one of those is essential to you, that is the reason to keep paying for ngrok, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ngrok 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 tunnel to localhost like ngrok, on a VPS I already have. 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. Two shortcuts first: cloudflared is free and covers most hobby use; Tailscale Funnel covers the rest. This is for when you want the plumbing on your own box. ### Stack (fixed, do not substitute) - A Linux VPS with a public IP, Caddy for TLS, and frp (server on the VPS, client locally). Not a hand-rolled TCP proxy. - A domain with a wildcard A record to the VPS. ### Phase 1 · The server Build: frps on the VPS bound to localhost ports only, with a strong token in its config, and Caddy in front terminating TLS for *.tunnel.yourdomain with automatic certificates. Done when: the VPS exposes only 80, 443 and SSH from outside, and a certificate issues for a test subdomain. Do not build yet: the client, auth. ### Phase 2 · The client Build: a tunnel <port> <name> wrapper around frpc that maps a local port to name.tunnel.yourdomain, prints the URL, and cleans up on exit. Done when: a local server on 3000 is reachable at its subdomain over HTTPS, and Ctrl-C removes the route. ### Phase 3 · Protection Build: basic auth per tunnel via Caddy, and an IP allowlist flag, so a tunnel is never public by accident. Default to protected. Done when: an unauthenticated request to a protected tunnel gets 401 and the allowlist blocks a foreign IP. ### Phase 4 · Inspection Build: a local request log · Caddy access logs shipped to the client and pretty-printed with method, path, status and latency, plus a replay command that re-sends the last request body to localhost. Done when: a webhook hitting the tunnel appears in the local log within a second and replays correctly. ### Phase 5 · Service and README Build: systemd units for frps and Caddy, a config template, and the README. Done when: the VPS reboots and tunnels resume without a manual step. ### Out of scope (and why) - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels. That is what ten dollars buys. ### README must contain - The default-protected rule and how to open a tunnel deliberately. - The cloudflared alternative, stated as the free path. ===== AGENTS.md ===== # Agent instructions · ngrok indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: frp (server on the VPS, client locally), Caddy with a wildcard subdomain. 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". ===== BUILD_PLAN.md ===== # Build plan · ngrok A tunnel to localhost on a VPS you already have: frp exposes a local port on a subdomain you own, Caddy terminates TLS, every tunnel is protected by default, and a local log shows and replays incoming requests. cloudflared does most of this for free; this is for owning the plumbing. 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 frps bound to localhost with a token; Caddy issuing wildcard certificates. ### Steps 1. Install frps on the VPS with frps.toml bound to 127.0.0.1 and FRP_TOKEN Files: `frps.toml` 2. Caddyfile for *.tunnel.yourdomain.com with the DNS challenge for wildcard certificates Wildcard certificates need the DNS challenge; Caddy needs a DNS provider module or use per-name certificates on demand. Files: `Caddyfile` ### Done when - [ ] Only 22, 80 and 443 are reachable from outside - [ ] A certificate issues for a test subdomain ## Phase 2 · The client tunnel <port> <name> prints a URL and cleans up on exit. ### Steps 1. Write a wrapper around frpc that generates a per-tunnel config Files: `tunnel.sh` 2. Print the URL and remove the route on Ctrl-C ### Done when - [ ] A local server on 3000 is reachable at its subdomain over HTTPS - [ ] Ctrl-C removes the route ## Phase 3 · Protection Never public by accident. ### Steps 1. Basic auth per tunnel via Caddy, on by default ```sh caddy hash-password ``` 2. An IP allowlist flag and an explicit --public flag ### Done when - [ ] An unauthenticated request gets 401 - [ ] The allowlist blocks a foreign IP - [ ] --public is required to open a tunnel ## Phase 4 · Inspection See and replay incoming requests locally. ### Steps 1. Ship Caddy access logs for the tunnel to the client and pretty-print them 2. A replay command that re-sends the last request body to localhost ### Done when - [ ] A webhook appears in the local log within a second - [ ] Replay reaches the local server ## Phase 5 · Service and README Survives a reboot; documented. ### Steps 1. systemd units for frps and Caddy 2. README with the default-protected rule and the cloudflared alternative Files: `README.md` ### Done when - [ ] The VPS reboots and tunnels resume - [ ] The README states the free alternative ## Not in this build - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. ## After v1, if you want it - OAuth in front via Caddy's forward_auth - TCP tunnels for databases ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. openssl rand -hex 32; the same value on server and client. FRP_TOKEN=long-random # Required. The wildcard base. TUNNEL_DOMAIN=tunnel.yourdomain.com # Required. Default protection for every tunnel. BASIC_AUTH_USER=me # Required · secret. Caddy hash-password output. BASIC_AUTH_HASH=$2a$14$...
You are building a lean indie version of ngrok. 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 ===== # ngrok · indie build A tunnel to localhost on a VPS you already have: frp exposes a local port on a subdomain you own, Caddy terminates TLS, every tunnel is protected by default, and a local log shows and replays incoming requests. cloudflared does most of this for free; this is for owning the plumbing. Estimated effort: **one sitting**. 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 | | --- | --- | --- | | Tunnel | frp (server on the VPS, client locally) | a mature reverse proxy, not a hand-rolled TCP forwarder | | TLS | Caddy with a wildcard subdomain | one certificate for every tunnel name | ## 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 - [ ] **Consider cloudflared first** · free - Why: Cloudflare Tunnel is free and covers most hobby use with one command. This build is for wanting your own box. - Get it: developers.cloudflare.com/cloudflare-one/connections/connect-networks - [ ] **A VPS with a public IP** · about $5 a month - Why: The public end of the tunnel. - Get it: Smallest Ubuntu 24.04 instance. - [ ] **A domain with a wildcard record** · free on a domain you own - Why: *.tunnel.yourdomain.com so each tunnel gets a name. - Get it: Add an A record for *.tunnel pointing at the VPS. - [ ] **frp binaries** · free - Why: frps on the server, frpc on your machine. - Get it: Download the release for each platform from github.com/fatedier/frp/releases. - Verify: frps --version and frpc --version print - [ ] **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 ## Quick start ```sh caddy hash-password ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. - the request inspector and replay - OAuth and IP policies in front of the tunnel with one flag - the global edge and its latency - TCP and TLS tunnels without setup If one of those is essential to you, that is the reason to keep paying for ngrok, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · ngrok 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 tunnel to localhost like ngrok, on a VPS I already have. 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. Two shortcuts first: cloudflared is free and covers most hobby use; Tailscale Funnel covers the rest. This is for when you want the plumbing on your own box. ### Stack (fixed, do not substitute) - A Linux VPS with a public IP, Caddy for TLS, and frp (server on the VPS, client locally). Not a hand-rolled TCP proxy. - A domain with a wildcard A record to the VPS. ### Phase 1 · The server Build: frps on the VPS bound to localhost ports only, with a strong token in its config, and Caddy in front terminating TLS for *.tunnel.yourdomain with automatic certificates. Done when: the VPS exposes only 80, 443 and SSH from outside, and a certificate issues for a test subdomain. Do not build yet: the client, auth. ### Phase 2 · The client Build: a tunnel <port> <name> wrapper around frpc that maps a local port to name.tunnel.yourdomain, prints the URL, and cleans up on exit. Done when: a local server on 3000 is reachable at its subdomain over HTTPS, and Ctrl-C removes the route. ### Phase 3 · Protection Build: basic auth per tunnel via Caddy, and an IP allowlist flag, so a tunnel is never public by accident. Default to protected. Done when: an unauthenticated request to a protected tunnel gets 401 and the allowlist blocks a foreign IP. ### Phase 4 · Inspection Build: a local request log · Caddy access logs shipped to the client and pretty-printed with method, path, status and latency, plus a replay command that re-sends the last request body to localhost. Done when: a webhook hitting the tunnel appears in the local log within a second and replays correctly. ### Phase 5 · Service and README Build: systemd units for frps and Caddy, a config template, and the README. Done when: the VPS reboots and tunnels resume without a manual step. ### Out of scope (and why) - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels. That is what ten dollars buys. ### README must contain - The default-protected rule and how to open a tunnel deliberately. - The cloudflared alternative, stated as the free path. ===== AGENTS.md ===== # Agent instructions · ngrok indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: frp (server on the VPS, client locally), Caddy with a wildcard subdomain. 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". ===== BUILD_PLAN.md ===== # Build plan · ngrok A tunnel to localhost on a VPS you already have: frp exposes a local port on a subdomain you own, Caddy terminates TLS, every tunnel is protected by default, and a local log shows and replays incoming requests. cloudflared does most of this for free; this is for owning the plumbing. 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 frps bound to localhost with a token; Caddy issuing wildcard certificates. ### Steps 1. Install frps on the VPS with frps.toml bound to 127.0.0.1 and FRP_TOKEN Files: `frps.toml` 2. Caddyfile for *.tunnel.yourdomain.com with the DNS challenge for wildcard certificates Wildcard certificates need the DNS challenge; Caddy needs a DNS provider module or use per-name certificates on demand. Files: `Caddyfile` ### Done when - [ ] Only 22, 80 and 443 are reachable from outside - [ ] A certificate issues for a test subdomain ## Phase 2 · The client tunnel <port> <name> prints a URL and cleans up on exit. ### Steps 1. Write a wrapper around frpc that generates a per-tunnel config Files: `tunnel.sh` 2. Print the URL and remove the route on Ctrl-C ### Done when - [ ] A local server on 3000 is reachable at its subdomain over HTTPS - [ ] Ctrl-C removes the route ## Phase 3 · Protection Never public by accident. ### Steps 1. Basic auth per tunnel via Caddy, on by default ```sh caddy hash-password ``` 2. An IP allowlist flag and an explicit --public flag ### Done when - [ ] An unauthenticated request gets 401 - [ ] The allowlist blocks a foreign IP - [ ] --public is required to open a tunnel ## Phase 4 · Inspection See and replay incoming requests locally. ### Steps 1. Ship Caddy access logs for the tunnel to the client and pretty-print them 2. A replay command that re-sends the last request body to localhost ### Done when - [ ] A webhook appears in the local log within a second - [ ] Replay reaches the local server ## Phase 5 · Service and README Survives a reboot; documented. ### Steps 1. systemd units for frps and Caddy 2. README with the default-protected rule and the cloudflared alternative Files: `README.md` ### Done when - [ ] The VPS reboots and tunnels resume - [ ] The README states the free alternative ## Not in this build - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. ## After v1, if you want it - OAuth in front via Caddy's forward_auth - TCP tunnels for databases ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. openssl rand -hex 32; the same value on server and client. FRP_TOKEN=long-random # Required. The wildcard base. TUNNEL_DOMAIN=tunnel.yourdomain.com # Required. Default protection for every tunnel. BASIC_AUTH_USER=me # Required · secret. Caddy hash-password output. BASIC_AUTH_HASH=$2a$14$...
You are building a production product version of ngrok. 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 ===== # ngrok · product brief ## Problem A tunnel is an SSH reverse forward or a WireGuard peer plus a reverse proxy on a $5 VPS; that is a sitting. What you do not get is the global edge, the request inspector, and OAuth in front of the tunnel in one flag. cloudflared does most of it free. ## Product outcome Tunnels you control, protected by default, with the one free alternative stated honestly. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a $5 VPS with a public IP - a domain with a wildcard record ## Explicit non-goals for v1 - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. - the request inspector and replay - OAuth and IP policies in front of the tunnel with one flag - the global edge and its latency - TCP and TLS tunnels without setup ## Success criteria - Default-protected verified - Reboot test passes ===== BRIEF.md ===== # Build brief · ngrok 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 tunnel to localhost like ngrok, on a VPS I already have. 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. Two shortcuts first: cloudflared is free and covers most hobby use; Tailscale Funnel covers the rest. This is for when you want the plumbing on your own box. ### Stack (fixed, do not substitute) - A Linux VPS with a public IP, Caddy for TLS, and frp (server on the VPS, client locally). Not a hand-rolled TCP proxy. - A domain with a wildcard A record to the VPS. ### Phase 1 · The server Build: frps on the VPS bound to localhost ports only, with a strong token in its config, and Caddy in front terminating TLS for *.tunnel.yourdomain with automatic certificates. Done when: the VPS exposes only 80, 443 and SSH from outside, and a certificate issues for a test subdomain. Do not build yet: the client, auth. ### Phase 2 · The client Build: a tunnel <port> <name> wrapper around frpc that maps a local port to name.tunnel.yourdomain, prints the URL, and cleans up on exit. Done when: a local server on 3000 is reachable at its subdomain over HTTPS, and Ctrl-C removes the route. ### Phase 3 · Protection Build: basic auth per tunnel via Caddy, and an IP allowlist flag, so a tunnel is never public by accident. Default to protected. Done when: an unauthenticated request to a protected tunnel gets 401 and the allowlist blocks a foreign IP. ### Phase 4 · Inspection Build: a local request log · Caddy access logs shipped to the client and pretty-printed with method, path, status and latency, plus a replay command that re-sends the last request body to localhost. Done when: a webhook hitting the tunnel appears in the local log within a second and replays correctly. ### Phase 5 · Service and README Build: systemd units for frps and Caddy, a config template, and the README. Done when: the VPS reboots and tunnels resume without a manual step. ### Out of scope (and why) - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels. That is what ten dollars buys. ### README must contain - The default-protected rule and how to open a tunnel deliberately. - The cloudflared alternative, stated as the free path. ===== ARCHITECTURE.md ===== # Architecture · ngrok ## Stack | Part | Choice | Why | | --- | --- | --- | | Tunnel | frp (server on the VPS, client locally) | a mature reverse proxy, not a hand-rolled TCP forwarder | | TLS | Caddy with a wildcard subdomain | one certificate for every tunnel name | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Server | frps and Caddy | Any reverse proxy with wildcard TLS | | Client | the wrapper | Any frpc config generator | | Inspector | logs and replay | Optional | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `FRP_TOKEN` · required, secret · openssl rand -hex 32; the same value on server and client. - `TUNNEL_DOMAIN` · required · The wildcard base. - `BASIC_AUTH_USER` · required · Default protection for every tunnel. - `BASIC_AUTH_HASH` · required, secret · Caddy hash-password output. ## 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 · ngrok product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: frp (server on the VPS, client locally), Caddy with a wildcard subdomain. - 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. ===== MILESTONES.md ===== # Delivery milestones · ngrok Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The server frps bound to localhost with a token; Caddy issuing wildcard certificates. ### Steps 1. Install frps on the VPS with frps.toml bound to 127.0.0.1 and FRP_TOKEN Files: `frps.toml` 2. Caddyfile for *.tunnel.yourdomain.com with the DNS challenge for wildcard certificates Wildcard certificates need the DNS challenge; Caddy needs a DNS provider module or use per-name certificates on demand. Files: `Caddyfile` ### Done when - [ ] Only 22, 80 and 443 are reachable from outside - [ ] A certificate issues for a test subdomain ## M2 · The client tunnel <port> <name> prints a URL and cleans up on exit. ### Steps 1. Write a wrapper around frpc that generates a per-tunnel config Files: `tunnel.sh` 2. Print the URL and remove the route on Ctrl-C ### Done when - [ ] A local server on 3000 is reachable at its subdomain over HTTPS - [ ] Ctrl-C removes the route ## M3 · Protection Never public by accident. ### Steps 1. Basic auth per tunnel via Caddy, on by default ```sh caddy hash-password ``` 2. An IP allowlist flag and an explicit --public flag ### Done when - [ ] An unauthenticated request gets 401 - [ ] The allowlist blocks a foreign IP - [ ] --public is required to open a tunnel ## M4 · Inspection See and replay incoming requests locally. ### Steps 1. Ship Caddy access logs for the tunnel to the client and pretty-print them 2. A replay command that re-sends the last request body to localhost ### Done when - [ ] A webhook appears in the local log within a second - [ ] Replay reaches the local server ## M5 · Service and README Survives a reboot; documented. ### Steps 1. systemd units for frps and Caddy 2. README with the default-protected rule and the cloudflared alternative Files: `README.md` ### Done when - [ ] The VPS reboots and tunnels resume - [ ] The README states the free alternative ## M6 · Operate (production only) Know when the tunnel host is down; keep it patched. ### Steps 1. Uptime check on the VPS and frps health 2. Firewall, unattended upgrades, and a rotation procedure for FRP_TOKEN ### Done when - [ ] Stopping frps triggers an alert - [ ] Rotating the token is documented and tested ===== OPERATIONS.md ===== # Operations · ngrok ## Backup Configs in git; no state. ## Restore Redeploy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on the VPS. ## Incident checklist Rotate FRP_TOKEN; check Caddy logs for unexpected tunnel names. 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 - [ ] Default-protected verified - [ ] Reboot test passes ## Launch constraint Do not market omitted ngrok 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 · secret. openssl rand -hex 32; the same value on server and client. FRP_TOKEN=long-random # Required. The wildcard base. TUNNEL_DOMAIN=tunnel.yourdomain.com # Required. Default protection for every tunnel. BASIC_AUTH_USER=me # Required · secret. Caddy hash-password output. BASIC_AUTH_HASH=$2a$14$...
# ngrok · indie build A tunnel to localhost on a VPS you already have: frp exposes a local port on a subdomain you own, Caddy terminates TLS, every tunnel is protected by default, and a local log shows and replays incoming requests. cloudflared does most of this for free; this is for owning the plumbing. Estimated effort: **one sitting**. 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 | | --- | --- | --- | | Tunnel | frp (server on the VPS, client locally) | a mature reverse proxy, not a hand-rolled TCP forwarder | | TLS | Caddy with a wildcard subdomain | one certificate for every tunnel name | ## 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 - [ ] **Consider cloudflared first** · free - Why: Cloudflare Tunnel is free and covers most hobby use with one command. This build is for wanting your own box. - Get it: developers.cloudflare.com/cloudflare-one/connections/connect-networks - [ ] **A VPS with a public IP** · about $5 a month - Why: The public end of the tunnel. - Get it: Smallest Ubuntu 24.04 instance. - [ ] **A domain with a wildcard record** · free on a domain you own - Why: *.tunnel.yourdomain.com so each tunnel gets a name. - Get it: Add an A record for *.tunnel pointing at the VPS. - [ ] **frp binaries** · free - Why: frps on the server, frpc on your machine. - Get it: Download the release for each platform from github.com/fatedier/frp/releases. - Verify: frps --version and frpc --version print - [ ] **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 ## Quick start ```sh caddy hash-password ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. - the request inspector and replay - OAuth and IP policies in front of the tunnel with one flag - the global edge and its latency - TCP and TLS tunnels without setup If one of those is essential to you, that is the reason to keep paying for ngrok, and the README should say so rather than pretend.
# Build brief · ngrok 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 tunnel to localhost like ngrok, on a VPS I already have. 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. Two shortcuts first: cloudflared is free and covers most hobby use; Tailscale Funnel covers the rest. This is for when you want the plumbing on your own box. ### Stack (fixed, do not substitute) - A Linux VPS with a public IP, Caddy for TLS, and frp (server on the VPS, client locally). Not a hand-rolled TCP proxy. - A domain with a wildcard A record to the VPS. ### Phase 1 · The server Build: frps on the VPS bound to localhost ports only, with a strong token in its config, and Caddy in front terminating TLS for *.tunnel.yourdomain with automatic certificates. Done when: the VPS exposes only 80, 443 and SSH from outside, and a certificate issues for a test subdomain. Do not build yet: the client, auth. ### Phase 2 · The client Build: a tunnel <port> <name> wrapper around frpc that maps a local port to name.tunnel.yourdomain, prints the URL, and cleans up on exit. Done when: a local server on 3000 is reachable at its subdomain over HTTPS, and Ctrl-C removes the route. ### Phase 3 · Protection Build: basic auth per tunnel via Caddy, and an IP allowlist flag, so a tunnel is never public by accident. Default to protected. Done when: an unauthenticated request to a protected tunnel gets 401 and the allowlist blocks a foreign IP. ### Phase 4 · Inspection Build: a local request log · Caddy access logs shipped to the client and pretty-printed with method, path, status and latency, plus a replay command that re-sends the last request body to localhost. Done when: a webhook hitting the tunnel appears in the local log within a second and replays correctly. ### Phase 5 · Service and README Build: systemd units for frps and Caddy, a config template, and the README. Done when: the VPS reboots and tunnels resume without a manual step. ### Out of scope (and why) - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels. That is what ten dollars buys. ### README must contain - The default-protected rule and how to open a tunnel deliberately. - The cloudflared alternative, stated as the free path.
# Agent instructions · ngrok indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: frp (server on the VPS, client locally), Caddy with a wildcard subdomain. 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".
# Build plan · ngrok A tunnel to localhost on a VPS you already have: frp exposes a local port on a subdomain you own, Caddy terminates TLS, every tunnel is protected by default, and a local log shows and replays incoming requests. cloudflared does most of this for free; this is for owning the plumbing. 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 frps bound to localhost with a token; Caddy issuing wildcard certificates. ### Steps 1. Install frps on the VPS with frps.toml bound to 127.0.0.1 and FRP_TOKEN Files: `frps.toml` 2. Caddyfile for *.tunnel.yourdomain.com with the DNS challenge for wildcard certificates Wildcard certificates need the DNS challenge; Caddy needs a DNS provider module or use per-name certificates on demand. Files: `Caddyfile` ### Done when - [ ] Only 22, 80 and 443 are reachable from outside - [ ] A certificate issues for a test subdomain ## Phase 2 · The client tunnel <port> <name> prints a URL and cleans up on exit. ### Steps 1. Write a wrapper around frpc that generates a per-tunnel config Files: `tunnel.sh` 2. Print the URL and remove the route on Ctrl-C ### Done when - [ ] A local server on 3000 is reachable at its subdomain over HTTPS - [ ] Ctrl-C removes the route ## Phase 3 · Protection Never public by accident. ### Steps 1. Basic auth per tunnel via Caddy, on by default ```sh caddy hash-password ``` 2. An IP allowlist flag and an explicit --public flag ### Done when - [ ] An unauthenticated request gets 401 - [ ] The allowlist blocks a foreign IP - [ ] --public is required to open a tunnel ## Phase 4 · Inspection See and replay incoming requests locally. ### Steps 1. Ship Caddy access logs for the tunnel to the client and pretty-print them 2. A replay command that re-sends the last request body to localhost ### Done when - [ ] A webhook appears in the local log within a second - [ ] Replay reaches the local server ## Phase 5 · Service and README Survives a reboot; documented. ### Steps 1. systemd units for frps and Caddy 2. README with the default-protected rule and the cloudflared alternative Files: `README.md` ### Done when - [ ] The VPS reboots and tunnels resume - [ ] The README states the free alternative ## Not in this build - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. ## After v1, if you want it - OAuth in front via Caddy's forward_auth - TCP tunnels for databases
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. openssl rand -hex 32; the same value on server and client. FRP_TOKEN=long-random # Required. The wildcard base. TUNNEL_DOMAIN=tunnel.yourdomain.com # Required. Default protection for every tunnel. BASIC_AUTH_USER=me # Required · secret. Caddy hash-password output. BASIC_AUTH_HASH=$2a$14$...
# ngrok · product brief ## Problem A tunnel is an SSH reverse forward or a WireGuard peer plus a reverse proxy on a $5 VPS; that is a sitting. What you do not get is the global edge, the request inspector, and OAuth in front of the tunnel in one flag. cloudflared does most of it free. ## Product outcome Tunnels you control, protected by default, with the one free alternative stated honestly. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a $5 VPS with a public IP - a domain with a wildcard record ## Explicit non-goals for v1 - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels: what ten dollars buys. - the request inspector and replay - OAuth and IP policies in front of the tunnel with one flag - the global edge and its latency - TCP and TLS tunnels without setup ## Success criteria - Default-protected verified - Reboot test passes
# Build brief · ngrok 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 tunnel to localhost like ngrok, on a VPS I already have. 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. Two shortcuts first: cloudflared is free and covers most hobby use; Tailscale Funnel covers the rest. This is for when you want the plumbing on your own box. ### Stack (fixed, do not substitute) - A Linux VPS with a public IP, Caddy for TLS, and frp (server on the VPS, client locally). Not a hand-rolled TCP proxy. - A domain with a wildcard A record to the VPS. ### Phase 1 · The server Build: frps on the VPS bound to localhost ports only, with a strong token in its config, and Caddy in front terminating TLS for *.tunnel.yourdomain with automatic certificates. Done when: the VPS exposes only 80, 443 and SSH from outside, and a certificate issues for a test subdomain. Do not build yet: the client, auth. ### Phase 2 · The client Build: a tunnel <port> <name> wrapper around frpc that maps a local port to name.tunnel.yourdomain, prints the URL, and cleans up on exit. Done when: a local server on 3000 is reachable at its subdomain over HTTPS, and Ctrl-C removes the route. ### Phase 3 · Protection Build: basic auth per tunnel via Caddy, and an IP allowlist flag, so a tunnel is never public by accident. Default to protected. Done when: an unauthenticated request to a protected tunnel gets 401 and the allowlist blocks a foreign IP. ### Phase 4 · Inspection Build: a local request log · Caddy access logs shipped to the client and pretty-printed with method, path, status and latency, plus a replay command that re-sends the last request body to localhost. Done when: a webhook hitting the tunnel appears in the local log within a second and replays correctly. ### Phase 5 · Service and README Build: systemd units for frps and Caddy, a config template, and the README. Done when: the VPS reboots and tunnels resume without a manual step. ### Out of scope (and why) - The global edge, OAuth-in-front with one flag, TCP and TLS tunnels. That is what ten dollars buys. ### README must contain - The default-protected rule and how to open a tunnel deliberately. - The cloudflared alternative, stated as the free path.
# Architecture · ngrok ## Stack | Part | Choice | Why | | --- | --- | --- | | Tunnel | frp (server on the VPS, client locally) | a mature reverse proxy, not a hand-rolled TCP forwarder | | TLS | Caddy with a wildcard subdomain | one certificate for every tunnel name | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Server | frps and Caddy | Any reverse proxy with wildcard TLS | | Client | the wrapper | Any frpc config generator | | Inspector | logs and replay | Optional | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `FRP_TOKEN` · required, secret · openssl rand -hex 32; the same value on server and client. - `TUNNEL_DOMAIN` · required · The wildcard base. - `BASIC_AUTH_USER` · required · Default protection for every tunnel. - `BASIC_AUTH_HASH` · required, secret · Caddy hash-password output. ## 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 · ngrok product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: frp (server on the VPS, client locally), Caddy with a wildcard subdomain. - 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.
# Delivery milestones · ngrok Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · The server frps bound to localhost with a token; Caddy issuing wildcard certificates. ### Steps 1. Install frps on the VPS with frps.toml bound to 127.0.0.1 and FRP_TOKEN Files: `frps.toml` 2. Caddyfile for *.tunnel.yourdomain.com with the DNS challenge for wildcard certificates Wildcard certificates need the DNS challenge; Caddy needs a DNS provider module or use per-name certificates on demand. Files: `Caddyfile` ### Done when - [ ] Only 22, 80 and 443 are reachable from outside - [ ] A certificate issues for a test subdomain ## M2 · The client tunnel <port> <name> prints a URL and cleans up on exit. ### Steps 1. Write a wrapper around frpc that generates a per-tunnel config Files: `tunnel.sh` 2. Print the URL and remove the route on Ctrl-C ### Done when - [ ] A local server on 3000 is reachable at its subdomain over HTTPS - [ ] Ctrl-C removes the route ## M3 · Protection Never public by accident. ### Steps 1. Basic auth per tunnel via Caddy, on by default ```sh caddy hash-password ``` 2. An IP allowlist flag and an explicit --public flag ### Done when - [ ] An unauthenticated request gets 401 - [ ] The allowlist blocks a foreign IP - [ ] --public is required to open a tunnel ## M4 · Inspection See and replay incoming requests locally. ### Steps 1. Ship Caddy access logs for the tunnel to the client and pretty-print them 2. A replay command that re-sends the last request body to localhost ### Done when - [ ] A webhook appears in the local log within a second - [ ] Replay reaches the local server ## M5 · Service and README Survives a reboot; documented. ### Steps 1. systemd units for frps and Caddy 2. README with the default-protected rule and the cloudflared alternative Files: `README.md` ### Done when - [ ] The VPS reboots and tunnels resume - [ ] The README states the free alternative ## M6 · Operate (production only) Know when the tunnel host is down; keep it patched. ### Steps 1. Uptime check on the VPS and frps health 2. Firewall, unattended upgrades, and a rotation procedure for FRP_TOKEN ### Done when - [ ] Stopping frps triggers an alert - [ ] Rotating the token is documented and tested
# Operations · ngrok ## Backup Configs in git; no state. ## Restore Redeploy. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on the VPS. ## Incident checklist Rotate FRP_TOKEN; check Caddy logs for unexpected tunnel names. 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 - [ ] Default-protected verified - [ ] Reboot test passes ## Launch constraint Do not market omitted ngrok 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 · secret. openssl rand -hex 32; the same value on server and client. FRP_TOKEN=long-random # Required. The wildcard base. TUNNEL_DOMAIN=tunnel.yourdomain.com # Required. Default protection for every tunnel. BASIC_AUTH_USER=me # Required · secret. Caddy hash-password output. BASIC_AUTH_HASH=$2a$14$...
$ choose a build depth, inspect the files, then open the complete pack in your agent
Because ngrok http 3000 is one command and the inspector shows every webhook body. Ten dollars for zero setup is a fair trade when a demo is in five minutes.
xthe request inspector and replay
xOAuth and IP policies in front of the tunnel with one flag
xthe global edge and its latency
xTCP and TLS tunnels without setup
ngrok pricing
hobbyist$10/mo · monthly flat with included credit · $120/yr
free tierThe free plan gives 3 online endpoints, 1 GB of transfer and 20k HTTP requests a month on ngrok-branded domains.
verified 2026-09-04 · source ↗
Is ngrok free?
The free plan gives 3 online endpoints, 1 GB of transfer and 20k HTTP requests a month on ngrok-branded domains. Paid is Hobbyist at $10/mo (checked 2026-09-04).
Vibecode ngrok
Kinda. The core of ngrok is buildable in a weekend with the prompt on this page, but there are real gaps: the request inspector and replay, OAuth and IP policies in front of the tunnel with one flag. Read the honest list above before committing.
How much does ngrok cost?
ngrok costs about $10/month (Hobbyist, checked 2026-09-04), which is $120 per year.
What do I lose by replacing ngrok?
Honestly: the request inspector and replay; OAuth and IP policies in front of the tunnel with one flag; the global edge and its latency; TCP and TLS tunnels without setup. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to ngrok?
Yes: frp (fast reverse proxy for exposing local servers), cloudflared (Cloudflare Tunnel client, free tier covers most hobby use). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.