tunnelctl

Quickstart

Install tunnelctl, log in, and put your first local service on a public URL.

This guide takes you from zero to a live public URL in a few minutes.

Prerequisites

  • A tunnelctl account on your organization's identity provider (SSO).
  • A local service listening on some port (e.g. a dev server on 8080).

1. Install

tunnelctl is a single self-contained binary. Install it from your platform's package manager, or run it straight from the container image.

# Register the dnf repository (Rocky / Fedora / RHEL):
dnf config-manager addrepo --from-repofile="https://git.piblade.net/api/packages/tunnelctl/rpm.repo"
# Install (signed by the repo key, no manual import):
dnf install tunnelctl
# Trust the repo signing key:
sudo curl https://git.piblade.net/api/packages/tunnelctl/debian/repository.key -o /etc/apt/keyrings/forgejo-tunnelctl.asc
# Add the apt source:
echo "deb [signed-by=/etc/apt/keyrings/forgejo-tunnelctl.asc] https://git.piblade.net/api/packages/tunnelctl/debian stable main" | sudo tee /etc/apt/sources.list.d/forgejo.list
# Refresh + install the pinned version:
sudo apt update
sudo apt install tunnelctl=0.10.4-1
# Register the zypper repository (openSUSE / SLES):
zypper addrepo https://git.piblade.net/api/packages/tunnelctl/rpm.repo
zypper install tunnelctl
# Trust the repo signing key:
curl -JO https://git.piblade.net/api/packages/tunnelctl/alpine/key
# Add the apk repository for your Alpine release (v3.20 shown):
echo "https://git.piblade.net/api/packages/tunnelctl/alpine/main/v3.20" | sudo tee -a /etc/apk/repositories
# Install the pinned version:
sudo apk add tunnelctl=0.10.4-r1
# Add the tunnelctl Homebrew tap (one-time):
brew tap tunnelctl/tunnelctl https://git.piblade.net/tunnelctl/homebrew-tunnelctl.git
# Install the CLI:
brew install tunnelctl

Windows packaging is on the roadmap. For now, run the container image or build from source.

Distroless multi-arch image (amd64 + arm64); the entrypoint is tunnelctl, so CLI args go straight through:

podman run --rm oci.piblade.net/tunnelctl/tunnelctl-cli:latest --help

Running an actual tunnel from a container needs a little network setup — see Running in a container.

2. Log in

tunnelctl login

By default the CLI detects the right sign-in flow (--auth-flow=auto): on a desktop it opens your browser to the identity provider; on a headless box (SSH, container, CI) it switches to the device flow — a short code + URL you open on any device. You can also force a flow explicitly:

tunnelctl login --auth-flow=device   # headless: sign in from another device

See login & auth for all --auth-flow values.

Check who you are at any time:

tunnelctl whoami            # show identity + token expiry
tunnelctl whoami --verify   # also verify the tokens against the IdP and API

3. Expose a local service

Pick a protocol. For a web app, that's an HTTP tunnel — you give it a slug, which becomes the public subdomain:

tunnelctl up http myapp 8080

myapp is now served at https://myapp.intunnel.eu, forwarding to 127.0.0.1:8080. The command runs in the foreground and streams logs — press Ctrl+C to shut the tunnel down cleanly.

Target formats

The target can be a bare port (8080127.0.0.1:8080), a host:port (localhost:5173, [::1]:5173), or — for HTTP — a URL (http://localhost:3000). Omit it to reuse the target you used last time.

TCP / UDP tunnels

Non-HTTP services (databases, game servers, DNS, …) use a TCP or UDP tunnel, addressed by a public port instead of a slug:

tunnelctl up tcp 27017 5432    # tcp.intunnel.eu:27017 → 127.0.0.1:5432 (e.g. Postgres)
tunnelctl up udp 5353 53       # udp.intunnel.eu:5353 → 127.0.0.1:53

Ports come from a shared pool — tunnelctl ports pick tcp suggests a free one. See ports and Tunnels & protocols.

Run it in the background

To keep the tunnel up after you close the terminal, detach it as a daemon:

tunnelctl up http myapp 8080 -d

The command returns immediately and the tunnel keeps running.

4. Inspect and stop

tunnelctl status            # tunnels running on this host
tunnelctl status --all -v   # all your tunnels (every host) + verbose columns
tunnelctl logs http myapp   # view the daemon log for a tunnel
tunnelctl down http myapp   # stop one tunnel (or: down tcp 27017)
tunnelctl down --all        # stop every tunnel on this host

Next steps

On this page