CLI tool
unjs/untun avatar
unjs/untun

untun: Local HTTP tunneling with Cloudflare Quick Tunnels

🚇 Tunnel your local HTTP(s) server to the world! powered by Cloudflare Quick Tunnels.

1,379 stars33 forksTypeScriptNOASSERTION

At a glance

What is it?
untun exposes a local HTTP or HTTPS server through a Cloudflare Quick Tunnel from either the command line or an ESM package. It is suited to temporary sharing and development workflows, not durable production ingress.
Who is it for?
Developers who need a temporary public URL for an HTTP service can adopt untun when accepting Cloudflare's terms and sharing a generated trycloudflare.com address is appropriate. Operators needing authentication, named domains, durable routing policies, or service-level guarantees should choose a production tunnel product instead.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why untun wraps Cloudflare Quick Tunnels

untun solves a narrow but common development problem: a web application is bound to a machine that other people cannot reach directly. It gives a local HTTP or HTTPS server a temporary public URL through Cloudflare Quick Tunnels. The intended users are frontend and full-stack developers, demo authors, reviewers, and automation code that needs to expose a local service without configuring inbound router rules.

The package is deliberately small. It does not present itself as a general reverse proxy or a managed production ingress system. The repository identifies Cloudflare Quick Tunnels as its transport and Cloudflare's `cloudflared` software as the integration layer. This distinction matters because traffic still crosses Cloudflare infrastructure, and installing that software is presented as acceptance of Cloudflare's License, Terms, and Privacy Policy.

untun adds value through a smaller interface around that service. The CLI accepts a local URL and waits for the tunnel URL. The API exposes `startTunnel`, which accepts a port or a complete URL plus tunnel-specific options. For applications that already depend on unjs tooling, the README also states that Cloudflare Quick Tunnels are used by `unjs/listhen`.

That scope makes untun useful when a temporary, externally reachable address is enough. It does not document authentication, named custom domains, access policies, persistent hostnames, or production traffic guarantees. A reviewer should not infer those capabilities merely because the tunnel ends in an HTTPS address.

The request path from localhost to trycloudflare.com

The documented flow starts with a local origin such as `http://localhost:3000`. untun starts a `cloudflared` tunnel for that origin and waits until a temporary HTTPS URL is assigned. The CLI output shows the assignment as a URL under `trycloudflare.com`. Requests sent to that public URL are therefore handled through Cloudflare's tunnel service and forwarded to the local server.

The CLI makes the origin explicit:

sh
npx untun@latest tunnel http://localhost:3000

The README reports three stages: starting `cloudflared`, waiting for the tunnel URL, and marking the tunnel ready. It does not document internal functions beyond the public `startTunnel` entry point, so finer implementation details are not confirmed by the available documentation.

For API users, `startTunnel` returns a tunnel object after startup:

ts
const tunnel = await startTunnel({ port: 3000 });

The package metadata shows an ESM entry point at `./dist/index.mjs`, while the README also shows a CommonJS `require` example. It declares `"type": "module"`, and the published `exports` map exposes the root module. This means consumers should verify their loading setup against the actual installed package rather than assuming compatibility with every module pattern.

Installing untun and obtaining the first public URL

The shortest documented first use is through `npx`, which avoids a permanent local installation. Run the command while the local server is already listening on the selected address:

sh
npx untun@latest tunnel http://localhost:3000

According to the README, the expected output begins with a startup message, then waits for the tunnel URL, and finishes with the generated `https://*.trycloudflare.com` address. If the command remains at the waiting stage, the documented example gives no separate diagnostic procedure, so users would need to inspect the terminal output or Cloudflare's `cloudflared` behavior rather than expect an untun-specific error guide.

Applications that call the API can install the package with one of the documented package managers:

sh
npm install untun

The same dependency can be added with Yarn or pnpm:

sh
yarn add untun

For pnpm, the README uses the install command even though `pnpm install untun` is unusual compared with adding a dependency by package name. The commands are reproduced here exactly as documented. The repository development workflow uses `pnpm install`, but that instruction is for contributors rather than package consumers.

Once the generated URL appears, open it in a browser or give it to the intended recipient. Because this creates public network access to the local service, the operator must decide whether the service contains data or endpoints that should not be reachable by anyone who obtains the URL.

Defining the origin and passing cloudflared arguments

The simplest API configuration uses `port`. Its default is `3000`, and it only affects the origin when `url` is absent:

ts
const tunnel = await startTunnel({ port: 3000 });

A complete `url` overrides the origin-building options. When no `url` is supplied, untun derives the origin from `protocol`, `hostname`, and `port`, whose defaults are `http`, `localhost`, and `3000` respectively. This is useful for a remote development host or an HTTPS development server, although the documentation does not show a full nondefault configuration.

TLS verification is disabled by default through `verifyTLS: false`. Enabling verification would require a certificate that cloudflared can validate against the configured local hostname. The README does not describe certificate installation, browser trust handling, or custom certificate authority configuration, so teams with a nontrivial local certificate setup may need to resolve those details independently.

untun can also pass Cloudflare command-line arguments through `extraArgs`:

ts
const tunnel = await startTunnel({
  port: 3000,
  extraArgs: ["--no-autoupdate"]
});

This is a direct bridge to `cloudflared`, not an untun-maintained abstraction of every available flag. Unsupported or incorrect arguments would therefore produce a Cloudflare integration problem, and the README does not explain validation or which arguments remain available across Cloudflare versions.

Cloudflare notice acceptance and usage boundaries

Two option groups have legal and operational significance. `acceptCloudflareNotice` defaults to `false`, and the corresponding environment variable is `UNTUN_ACCEPT_CLOUDFLARE_NOTICE`. Setting the option accepts Cloudflare's terms by default for the tunnel process. The option does not replace the separate notice in the license documentation, which says that installing `cloudflared` signifies acceptance of Cloudflare's License, Terms, and Privacy Policy.

This is not legal advice. It does mean that an adopting organization should determine whether it can accept those terms before using the package in an employee or commercial workflow. The generated hostname is also publicly routable by design. untun documents no allowlist, authentication layer, per-tunnel password, or application-level authorization.

Quick Tunnels should therefore be treated as temporary exposure rather than a complete production publishing system. The documentation gives no rollback command, custom hostname configuration, uptime target, service-level agreement, or persistence model. It also does not say what happens if Cloudflare cannot allocate a URL. Those omissions are material for an infrastructure decision.

A common failure mode is assuming that HTTPS makes the service private. It does not. Encryption in transit and access control solve different problems. If the local application exposes administrative actions, development credentials, customer records, or writable APIs, an unauthenticated public tunnel may be the wrong mechanism even when the tunnel starts successfully.

Package licensing, releases, and maintenance signals

The repository metadata and `package.json` both identify MIT licensing, and the README says the project is published under the MIT License. The repository catalog nevertheless labels the GitHub license as a custom classification and points to the `LICENSE` file. Users distributing the package should inspect the `LICENSE` file rather than rely only on the metadata label.

The `cloudflared` integration is described as based on a fork of `JacobLinCool/node-cloudflared`. Separate Cloudflare terms apply to the `cloudflared` software. The practical upgrade cost therefore includes reviewing changes in untun itself and changes in the Cloudflare tunnel client behavior. `extraArgs` also creates a compatibility surface because Cloudflare may change or remove command-line options.

The repository is not archived. Its last push was on 2026-10-15. The available release list shows `v0.2.0` dated 2026-07-20, followed by older `v0.1.3` and `v0.1.2` releases from 2023, while the current `package.json` declares version `0.2.2`. That discrepancy makes the package manifest and changelog worth checking during an upgrade rather than selecting a version solely from the release summary.

For repository development, the README requires the latest LTS version of Node.js, Corepack, and `pnpm install`. The project uses TypeScript, `obuild`, `vitest`, `oxlint`, and `oxfmt`. Contributor maintenance costs are tied to those tools. The README does not explain the distribution model for `cloudflared`, so deployment planning should confirm how the binary is obtained on each target machine.

untun versus a production tunnel or reverse proxy

The practical alternative is a production-oriented tunnel service or a conventional reverse proxy with explicit public ingress. The difference is operational control. untun asks Cloudflare Quick Tunnels for a temporary public URL and then exposes the local origin. A production tunnel is normally selected when persistent routing, named domains, access policies, identity controls, operational logs, or predictable update channels are required.

A conventional reverse proxy has a different architecture. It is deployed at a network boundary, receives public traffic on a controlled host, terminates TLS according to the operator's configuration, and forwards requests to one or more internal services. That setup requires more infrastructure, but it also gives the operator an explicit place to configure authentication, request limits, hostnames, TLS, and service availability.

untun remains the better fit when the goal is a short demonstration, a local callback target, or a public preview with no need to manage ingress. Its narrow command-line interface is an advantage in scripts that only require an origin URL and the generated tunnel address.

The choice changes when the public endpoint must remain reachable across restarts. Nothing in the README documents stable hostnames or automatic rebinding after process termination. Nor does it document recovery when the local service starts after the tunnel. If those properties matter, adopting a production ingress design is preferable to treating untun as a larger hosting platform than its documentation supports.

Editorial conclusion

Developers who need a temporary public URL for an HTTP service can adopt untun when accepting Cloudflare's terms and sharing a generated trycloudflare.com address is appropriate. Operators needing authentication, named domains, durable routing policies, or service-level guarantees should choose a production tunnel product instead. Before adoption, verify the current `LICENSE`, confirm that Cloudflare's terms fit the intended use, and inspect how the generated public URL is distributed.

Frequently asked questions

How do I install untun and expose a local server?

Install it with a documented npm, Yarn, or pnpm command, or use `npx` without a permanent installation. Run `npx untun@latest tunnel http://localhost:3000` and wait for the generated `trycloudflare.com` URL.

Which origin does untun use by default?

When `url` is not provided, untun derives the origin from `protocol`, `hostname`, and `port`. Their defaults are `http`, `localhost`, and `3000`.

Does untun provide authentication for the public tunnel URL?

The README does not document authentication, access policies, or a tunnel password. It only documents options for the local origin, TLS verification, Cloudflare notice acceptance, and extra `cloudflared` arguments.

Can I control cloudflared through untun?

Yes. The `extraArgs` option accepts Cloudflare command-line arguments, with `--no-autoupdate` shown as the example. untun does not document validation or compatibility rules for those arguments.

What license applies to untun and cloudflared?

The `package.json` and README identify MIT for untun. The README separately states that installing `cloudflared` signifies acceptance of Cloudflare's License, Terms, and Privacy Policy.

Official sources

  1. Issues
  2. README
  3. Releases
  4. unjs/untun on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/unjs-untun.svg)](https://hysenlabs.com/projects/unjs-untun)

Was this useful?