Cloudsteading
Product image still needed. This listing has source documentation, but no reviewed screenshot yet.

hostc

Expose a local development server through a self-hosted Cloudflare tunnel endpoint.

hostc is a self-hosted ngrok alternative built on Cloudflare (Durable Objects, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.

Source & license

Upstream license: Apache-2.0

License TL;DR

You can use, change and sell it, including in closed-source products. When sharing copies, include the license, keep required notices and mark changed files. It includes a contributor patent grant with conditions, but no trademark permission or warranty.

Explain Apache 2.0 in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@akazwz

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Free tier eligible within limits

The documented hostc deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs.

Hosting requirements
  • Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits.
  • Use the configured SQLite Durable Object classes within 100,000 requests/day, 13,000 GB-s duration/day, 5 million SQL rows read/day, 100,000 written/day and 5 GB storage; active sockets consume duration.
  • Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: 8a840da. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • ngrok ↗

    - **Nothing to set up.** No sign-up, no auth token, no binary to download. If you have Node.js, you have hostc. - **Visitors see your app.** No warning page to click through before they reach it. - **Hot reload works.** WebSockets pass straight through, so Vite, Next.js and friends update every open device the moment you save. Server-Sent Events stream as they are produced. - **Dev servers accept it as is.** Requests arrive addressed to localhost and redirects point back to the public URL, so there is no allowed-hosts setting to change. - **The link survives.** Network drops and server restarts don't change the URL; hostc reconnects on its own. - **Free and open source.** The server is a Cloudflare Worker you can also run on your own domain.

  • workers ↗

    { "$schema": "node_modules/wrangler/config-schema.json", "name": "hostc-tunnel", "main": "src/index.ts", "compatibility_date": "2026-09-20", // Served only on the routes passed at deploy time. "workers_dev": false, // Local development defaults. Production values are passed by scripts/deploy.ts. "vars": { "TUNNEL_DOMAIN": "localhost:8787", }, "durable_objects": { "bindings": [{ "name": "TUNNEL", "class_name": "Tunnel" }],

  • durable-objects ↗

    routes passed at deploy time. "workers_dev": false, // Local development defaults. Production values are passed by scripts/deploy.ts. "vars": { "TUNNEL_DOMAIN": "localhost:8787", }, "durable_objects": { "bindings": [{ "name": "TUNNEL", "class_name": "Tunnel" }], }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Tunnel"] }], "ratelimits": [ { "name": "CREATE_LIMIT", "namespace_id": "1001", "simple": { "limit": 20, "period": 60 }, }, ], "observability": { "enabled": tru

  • free-tier-eligible ↗

    { "$schema": "node_modules/wrangler/config-schema.json", "name": "hostc-tunnel", "main": "src/index.ts", "compatibility_date": "2026-09-20", // Served only on the routes passed at deploy time. "workers_dev": false, // Local development defaults. Production values are passed by scripts/deploy.ts. "vars": { "TUNNEL_DOMAIN": "localhost:8787", }, "durable_objects": { "bindings": [{ "name": "TUNNEL", "class_name": "Tunnel" }],

  • free-tier-eligible ↗

    routes passed at deploy time. "workers_dev": false, // Local development defaults. Production values are passed by scripts/deploy.ts. "vars": { "TUNNEL_DOMAIN": "localhost:8787", }, "durable_objects": { "bindings": [{ "name": "TUNNEL", "class_name": "Tunnel" }], }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Tunnel"] }], "ratelimits": [ { "name": "CREATE_LIMIT", "namespace_id": "1001", "simple": { "limit": 20, "period": 60 }, }, ], "observability": { "enabled": tru

  • free-tier-eligible ↗

    up>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 seconds)<br> Max of 15 minutes of CPU time per [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) or [Queue Consumer](https://developers.cloudflare.co

  • free-tier-eligible ↗

    jects are available both on Workers Free and Workers Paid plans. - **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available. - **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only available to accounts that already have a key-value-backed namespace. If you wish to downgrade from a Workers Paid plan to a Workers Free plan, you must first ensure that you have deleted all Durable Object namespaces with the key-value storage backend. On Workers Free plan: - If you exceed any one of the free tier limits, further operations of that type will fail with an error. - Daily free limits reset at 00:00 UTC. ## Compute billing Durable Objects are billed for compute duration (wall-clock time) while the Durable Object is actively running or is idle in memory but unable to [hibernate](https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/). Durable Objects that are idle and eligible for hibernation are not billed for duration, even before the runtime has hibernated them. Requests to a D

  • free-tier-eligible ↗

    billed accordingly. | | Free plan | Paid plan | | --- | --- | --- | | Requests | 100,000 / day | 1 million / month, + $0.15/million<br> Includes HTTP requests, RPC sessions<sup>1</sup>, WebSocket messages<sup>2</sup>, and alarm invocations | | Duration<sup>3</sup> | 13,000 GB-s / day | 400,000 GB-s / month, + $12.50/million GB-s<sup>4,5</sup> | <details> <summary> Footnotes </summary> <sup>1</sup> Each <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/">RPC session</a> is billed as one request to your Durable Object. Every <a href="https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/">RPC method call</a> on a <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects stub</a> is its own RPC session and therefore a single billed request. RPC method calls can return objects (stubs) extending <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/lifecycle/#lifetimes-memory-and-resource-management"><code>RpcTarget</code></a> and invo

  • free-tier-eligible ↗

    /). | | Workers Free plan | Workers Paid plan | | --- | --- | --- | | Rows reads <sup>1,2</sup> | 5 million / day | First 25 billion / month included + $0.001 / million rows | | Rows written <sup>1,2,3,4</sup> | 100,000 / day | First 50 million / month included + $1.00 / million rows | | SQL Stored data <sup>5</sup> | 5 GB (total) | 5 GB-month, + $0.20/ GB-month | <details> <summary> Footnotes </summary> <sup>1</sup> Rows read and rows written included limits and rates match <a href="https://developers.cloudflare.com/d1/platform/pricing/">D1 pricing</a>, Cloudflare's serverless SQL database. <sup>2</sup> Key-value methods like <code>get()</code>, <code>put()</code>, <code>delete()</code>, or <code>list(

  • Apache-2.0 ↗

    Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to ca

  • architecture ↗

    { "$schema": "node_modules/wrangler/config-schema.json", "name": "hostc-tunnel", "main": "src/index.ts", "compatibility_date": "2026-09-20", // Served only on the routes passed at deploy time. "workers_dev": false, // Local development defaults. Production values are passed by scripts/deploy.ts. "vars": { "TUNNEL_DOMAIN": "localhost:8787", }, "durable_objects": { "bindings": [{ "name": "TUNNEL", "class_name": "Tunnel" }],

  • architecture ↗

    routes passed at deploy time. "workers_dev": false, // Local development defaults. Production values are passed by scripts/deploy.ts. "vars": { "TUNNEL_DOMAIN": "localhost:8787", }, "durable_objects": { "bindings": [{ "name": "TUNNEL", "class_name": "Tunnel" }], }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["Tunnel"] }], "ratelimits": [ { "name": "CREATE_LIMIT", "namespace_id": "1001", "simple": { "limit": 20, "period": 60 }, }, ], "observability": { "enabled": tru

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

ngrok logongrok ↗

Public HTTP/WebSocket access to a local development server through a self-hosted tunnel endpoint; complete managed identity, logging and enterprise tunnel policies are excluded.

See supporting source ↗
external SaaS target
varies

How it works

The shape of hostc on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points2
hostc-tunnel
apps/server/wrangler.jsonc
hostc-web
apps/web/wrangler.jsonc
↓
App
hostc-tunnel
entry
Cloudflare Workers
Entrypoint: src/index.ts
hostc-web
entry
Cloudflare Workers
↓

Configuration and workflow sources

Reviewed commit 8a840dac9220. Files were read as data; upstream applications and CI jobs were not executed.

Deployment configuration · 2 files
apps/server/wrangler.jsonc ↗

Cloudflare Workers · compatibility 2026-09-20

hostc-tunnel · default

Entrypoint: src/index.ts

  • TUNNEL → Durable Objects · class Tunnel
apps/web/wrangler.jsonc ↗

Cloudflare Workers · compatibility 2026-09-20

hostc-web · default

Static assets: public · 404-page

  • Static assets → Static assets

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

apps/server/src/index.ts ↗
  • L18 · fetch handler exported · references TUNNEL_DOMAIN · calls tunnelIdFromHost, handleTunnelRequest, Response.json, url.pathname.startsWith, checkProtocol, createTunnel, jsonError, url.pathname.match, connectTunnel
  • L51 · tunnelIdFromHost calls (conditional paths may differ): tunnelDomain.toLowerCase, host.toLowerCase, normalized.endsWith, normalized.slice, isTunnelId
  • L61 · handleTunnelRequest calls (conditional paths may differ): errorResponse, fetch, env.TUNNEL.getByName
  • L68 · createTunnel calls (conditional paths may differ): request.headers.get, env.CREATE_LIMIT.limit, jsonError, randomTunnelId, init, env.TUNNEL.getByName, connectUrl.toString, signConnectToken, Response.json
  • L89 · connectTunnel calls (conditional paths may differ): isTunnelId, jsonError, request.headers.get, verifyConnectToken, fetch, env.TUNNEL.getByName
  • L103 · checkProtocol calls (conditional paths may differ): request.headers.get, String, jsonError
  • L110 · randomTunnelId calls (conditional paths may differ): crypto.getRandomValues, join, Array.from
  • L116 · jsonError calls (conditional paths may differ): Response.json

Environment references: env.TUNNEL_DOMAIN · env.TUNNEL · env.CREATE_LIMIT · env.TOKEN_SECRET

apps/server/src/pages.ts ↗
  • L10 · errorResponse calls (conditional paths may differ): wantsHtml, Response.json, html, escape
  • L54 · wantsHtml calls (conditional paths may differ): includes, request.headers.get
  • L58 · html calls (conditional paths may differ): escape
  • L83 · escape calls (conditional paths may differ): value.replace, char.charCodeAt
apps/server/src/token.ts ↗
  • L11 · signConnectToken calls (conditional paths may differ): crypto.subtle.sign, importKey, message, toBase64Url
  • L16 · verifyConnectToken calls (conditional paths may differ): fromBase64Url, crypto.subtle.verify, importKey, message
  • L24 · message calls (conditional paths may differ): encoder.encode
  • L28 · importKey calls (conditional paths may differ): encoder.encode, keys.get, crypto.subtle.importKey, keys.set
  • L43 · toBase64Url calls (conditional paths may differ): replace, replaceAll, btoa, String.fromCharCode
  • L50 · fromBase64Url calls (conditional paths may differ): test, atob, replaceAll, value.replaceAll, Uint8Array.from, char.charCodeAt
apps/server/src/tunnel.ts ↗
  • L667 · forwardedRequestHeaders calls (conditional paths may differ): name.startsWith, list.push, stripHopByHop
  • L681 · publicResponseHeaders calls (conditional paths may differ): stripHopByHop, name.toLowerCase, headers.append, withoutCookieDomain
  • L694 · withoutCookieDomain calls (conditional paths may differ): cookie.split, attributes.filter, attribute.split, join
  • L700 · attachmentOf calls (conditional paths may differ): ws.deserializeAttachment
  • L704 · isStale calls (conditional paths may differ): attachmentOf, ctx.getWebSocketAutoResponseTimestamp, Date.now
  • L711 · safeClose calls (conditional paths may differ): ws.close, sendableCloseCode, sendableCloseReason
  • L719 · withTimeout calls (conditional paths may differ): setTimeout, resolve, promise.then, clearTimeout, reject
Build and deployment pipeline · 2 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

CI · .github/workflows/ci.yml ↗

Triggers: push, pull_request

check · no job dependencies declared

  1. actions/checkout@v7actions/checkout@v7
  2. pnpm/action-setup@v6pnpm/action-setup@v6
  3. actions/setup-node@v7actions/setup-node@v7
  4. Shell commandpnpm install --frozen-lockfile
  5. Shell commandpnpm check
  6. Shell commandcp apps/server/.dev.vars.example apps/server/.dev.vars
  7. Shell commandpnpm test:e2e
Release CLI · .github/workflows/release.yml ↗

Triggers: workflow_dispatch

publish · no job dependencies declared

Condition: github.ref == 'refs/heads/main'

  1. actions/checkout@v7actions/checkout@v7
  2. pnpm/action-setup@v6pnpm/action-setup@v6
  3. actions/setup-node@v7actions/setup-node@v7
  4. Shell commandpnpm install --frozen-lockfile
  5. Shell commandpnpm check
  6. Shell commandpnpm -F hostc pack --pack-destination "$RUNNER_TEMP"
  7. Shell commandnpm publish "$RUNNER_TEMP"/hostc-*.tgz --tag "${{ inputs.dist-tag }}"
package.json ↗
  • build: pnpm -F hostc build
  • deploy:server: pnpm -F @hostc/server run deploy
  • deploy:web: pnpm -F web run deploy
apps/server/package.json ↗
  • deploy: node scripts/deploy.ts
apps/web/package.json ↗
  • deploy: node scripts/deploy.ts

Full upstream document by @akazwz · README.md · snapshot 8a840da

hostc logo

hostc

Localhost, anywhere.

One command gives your dev server a public HTTPS URL. WebSockets and hot reload included.
Free, open source, no account.

npm version Apache-2.0 license GitHub stars

hostc.dev · 简体中文

Quick start

Start your app, then point hostc at its port:

npx hostc@latest 3000
  https://k7m2xq9pa4dn.hostc.app  → http://localhost:3000

  Anyone with this URL can reach your local server. Press Ctrl+C to stop.

Open the URL on any device. Every request shows up in your terminal as it arrives.

hostc is free and open source. If it helps you, a star on GitHub helps other developers find it, and it's the best encouragement for the project.

Why hostc

  • Nothing to set up. No sign-up, no auth token, no binary to download. If you have Node.js, you have hostc.
  • Visitors see your app. No warning page to click through before they reach it.
  • Hot reload works. WebSockets pass straight through, so Vite, Next.js and friends update every open device the moment you save. Server-Sent Events stream as they are produced.
  • Dev servers accept it as is. Requests arrive addressed to localhost and redirects point back to the public URL, so there is no allowed-hosts setting to change.
  • The link survives. Network drops and server restarts don't change the URL; hostc reconnects on its own.
  • Free and open source. The server is a Cloudflare Worker you can also run on your own domain.

Use it to

  • share something you built with an AI coding tool before you deploy it anywhere,
  • show work in progress to a teammate or a client,
  • test webhooks from Stripe, GitHub or Slack against the code on your machine,
  • try your site on a real phone, with hot reload,
  • let a coding agent share what it built. Point the agent at hostc.dev/llms.txt and it knows how to run hostc and read the URL.

Usage

hostc <target> [options]

  3000                     http://localhost:3000
  127.0.0.1:8080           http://127.0.0.1:8080
  https://localhost:5173   any http(s) origin

  --server <url>   tunnel server (env HOSTC_SERVER)
  --qr             print a QR code of the public URL
  -h, --help       show this help
  -v, --version    show the version

Press Ctrl+C to stop; the URL is released immediately. Restarting hostc gives a new URL.

The URL is public: anyone who has it reaches your server. Only expose what you mean to share.

Always run @latest

hostc is free and moves fast, so server updates can be incompatible with older CLIs. Run it with npx hostc@latest and you always get the matching version.

Don't install it globally (npm i -g hostc) or add it to a project: an installed copy stays on its version and stops working when the server moves on. An outdated CLI stops at startup and tells you to upgrade.

How it works

browser ──▶ Worker ──▶ Durable Object (one per tunnel) ◀── WebSocket ── hostc ──▶ localhost

hostc opens a single outgoing WebSocket, so nothing on your machine has to be reachable from the internet. Each public request or WebSocket becomes a stream on that connection, with flow control for bodies. Idle tunnels sleep on the server, which is what keeps hostc free. The wire format is in docs/protocol.md.

Self-hosting

  1. Add your domains to Cloudflare: one for the API and one for tunnels, for example example.com and example.app. They can be the same domain, but a separate tunnel domain keeps tunnel cookies and abuse reports away from your main site. Add a proxied wildcard DNS record on the tunnel domain (* → 192.0.2.1); Cloudflare's Universal SSL covers *.example.app.

  2. Set the token secret once: pnpm -F @hostc/server exec wrangler secret put TOKEN_SECRET (at least 32 random bytes, e.g. openssl rand -base64 48).

  3. Deploy:

    API_DOMAIN=example.com TUNNEL_DOMAIN=example.app pnpm deploy:server
    
  4. Use it: npx hostc@latest 3000 --server https://example.com, or build the CLI with HOSTC_DEFAULT_SERVER=https://example.com pnpm build.

Contributing

Requires Node.js 22.22+ and pnpm.

pnpm install
cp apps/server/.dev.vars.example apps/server/.dev.vars
pnpm dev                       # tunnel server on http://localhost:8787
pnpm build                     # build the CLI
node apps/cli/dist/hostc.mjs 3000 --server http://localhost:8787

Tunnels are served on http://<id>.localhost:8787; Chrome and Firefox resolve *.localhost to your machine.

pnpm check      # format check, lint, typecheck, unit and integration tests
pnpm test:e2e   # wrangler dev + CLI + a local origin, end to end
Path What
packages/protocol frame format, messages and constants shared by both sides
packages/client Node.js client: connection, streams, reconnects
apps/server Cloudflare Worker + Durable Object tunnel server
apps/cli the hostc command
apps/web hostc.dev website (one static page) and llms.txt

Issues and pull requests are welcome.

If hostc is useful to you, star it on GitHub.

License

Apache-2.0

Frequently asked about hostc

What is hostc?+

hostc is a self-hosted ngrok alternative built on the Cloudflare developer platform. Expose a local development server through a self-hosted Cloudflare tunnel endpoint.

What does hostc replace?+

hostc is listed as an alternative to ngrok. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does hostc use?+

hostc is built on Durable Objects, Workers.

How much does hostc cost to run?+

The documented hostc deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs. Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits. Use the configured SQLite Durable Object classes within 100,000 requests/day, 13,000 GB-s duration/day, 5 million SQL rows read/day, 100,000 written/day and 5 GB storage; active sockets consume duration. Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations. Check current Cloudflare pricing before deploying.

Is hostc open source?+

The upstream repository declares the Apache-2.0 license. Read its terms at https://raw.githubusercontent.com/akazwz/hostc/8a840dac9220662a51c57eda1d21a9f221caf6fa/LICENSE. Source code and contributor credit are available at https://github.com/akazwz/hostc.

Discussion · 0

sign in to comment →
No comments yet — be the first.