Expose a local static directory to the internet through a Cloudflare Quick Tunnel.
Freeoxide Tunnel is a small Rust CLI, shipped as the ft binary, that runs a localhost static
file server and fronts it with an ephemeral cloudflared Quick Tunnel — giving you a public
*.trycloudflare.com URL in seconds, with no Cloudflare account or DNS setup required.
Linux / macOS:
curl -fsSL https://tunnel.freeoxide.com/install.sh | shGitHub-hosted fallback:
curl -fsSL https://github.com/freeoxide/tunnel/releases/latest/download/install.sh | shWindows (PowerShell):
irm https://tunnel.freeoxide.com/install.ps1 | iexGitHub-hosted fallback:
irm https://github.com/freeoxide/tunnel/releases/latest/download/install.ps1 | iexThe
tunnel.freeoxide.comURLs are the intended primary install host but are served by external website infrastructure that is not part of this repo. Until that is live, use the GitHub-hosted URLs above — they are produced directly by the release workflow and work today.
cargo binstall freeoxide-tunnel # prebuilt binary, no compile
cargo install freeoxide-tunnel --locked # build from crates.io
cargo install --git https://github.com/freeoxide/tunnel --locked # build from source (main)ft --versioncloudflared
must be installed and on your PATH. It is the external binary that creates the Quick Tunnel —
Freeoxide Tunnel never vendors it. If ft cannot find it, it prints a friendly install message
and exits.
- macOS:
brew install cloudflared - Windows:
winget install Cloudflare.cloudflared - Other platforms: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads
- Linux, macOS, and Windows all support both background (detached worker) and foreground modes.
Serve a directory and get a public URL:
ft ./distft prints the Quick Tunnel URL and keeps the tunnel running as a detached background worker.
The default action is START: ft <dir> with no subcommand spawns a tunnel for that directory.
Everything else is an explicit subcommand. <id|name> accepts either the numeric registry ID
or the service name.
| Command | Description |
|---|---|
ft <dir> |
Start a tunnel for a directory (the implicit START). |
ft <dir> --name <name> |
Start a tunnel with an explicit service name. |
ft <dir> --port <port> |
Bind the local server to a specific port. |
ft <dir> --foreground |
Run the worker in the foreground instead of detaching. |
ft ls |
List all known services. (alias: ps) |
ft ps |
Alias for ls. |
ft detail <id|name> |
Show detailed information about a single service. (alias: inspect) |
ft kill <id|name> |
Stop a running service and remove it from the registry. (alias: stop) |
ft logs <id|name> |
Print the logs for a service. |
ft logs <id|name> --follow |
Tail the log output (tail -f style). |
ft open <id|name> |
Open the public URL of a service in your default browser. |
--name <name>— explicit service name; defaults to a generated, unique name.--port <port>— local port to bind on; defaults to a free, allocated port.--foreground/-f— run in the foreground instead of spawning a detached worker.
ft ./dist # start with an auto-generated name and port
ft ./dist --name blog --port 3009 # start with a fixed name and port
ft ./dist --foreground # run attached to the current shell
ft ls # list services
ft ps # same as `ft ls`
ft detail blog # inspect by name
ft detail 2 # inspect by numeric ID
ft kill blog # stop and remove
ft logs blog # print logs
ft logs blog --follow # stream logs
ft open blog # open the public URL in a browserft <dir> spawns a detached worker process (a new session via setsid on Unix, or a process group
plus a KILL_ON_JOB_CLOSE Job Object on Windows) that:
- Starts a localhost static file server for
<dir>(axum + tower-http) on the chosen port. - Launches
cloudflaredas a Quick Tunnel pointing athttp://127.0.0.1:<port>. - Captures the generated
https://<random>.trycloudflare.comURL and records it in the registry.
The worker survives shell exit and runs until you stop it with ft kill, which signals the
worker's process group so the local server and cloudflared are torn down together. Use
--foreground to run the worker attached to your terminal instead.
All state lives under:
~/.local/state/freeoxide/tunnel/
├── registry.json # service registry (IDs, names, ports, URLs, status)
├── registry.lock # advisory lock serializing registry mutations
└── services/<name>/ # per-service state and logs
├── worker.log
├── server.log
└── tunnel.log
The root honors $XDG_STATE_HOME (defaulting to ~/.local/state). Service names are reduced to
a single safe path segment, so a registry-controlled name can never escape services/ via ..
or separators.
- Linux — primary target. PID-reuse-safe identity via
/proc/<pid>/cmdline; orphanedcloudflaredis reaped automatically viaPR_SET_PDEATHSIG. Supports background and foreground. - macOS — full support. PID-reuse-safe identity via
sysctl(KERN_PROCARGS2). There is noPR_SET_PDEATHSIGequivalent, so a worker killed abnormally (OOM,SIGKILL) leaves itscloudflaredorphaned untilft prunereaps it. Supports background and foreground. - Windows — full support. The detached worker owns a Job Object (
KILL_ON_JOB_CLOSE) that gives both whole-tree teardown and thePR_SET_PDEATHSIGequivalent (the worker's hard death still reaps itscloudflared). PID identity usesQueryFullProcessImageNameW. Supports background and foreground.
macOS binaries are not code-signed (signing is intentionally out of scope). The first run of a downloaded binary is quarantined by Gatekeeper; clear it with
xattr -dr com.apple.quarantine /path/to/ft(or right-click → Open → Open anyway).
MVP — complete and usable. Functional start/stop/list/logs flow for static directories over Cloudflare Quick Tunnels.
Branded as Freeoxide Tunnel · binary
ft· repo freeoxide/tunnel · tunnel.freeoxide.com
MIT