A self-hosted web platform for managing reverse SSH tunnels. Register a device once, then reach it from anywhere: monitor tunnel health on a live dashboard, open a terminal in your browser, manage files over SFTP, share access with teammates, and even browse the web through the remote device.
- Tunnel dashboard — live online/offline status and per-hop latency (you → Telepy → device), in list or card view.
- Guided tunnel creation — a five-step wizard issues keys and generates ready-to-run connection scripts: plain SSH, AutoSSH, systemd service, PowerShell, Docker Run, and Docker Compose, plus a single-use
curlURL for fetching the script on the target machine. - One-command device join — on Linux or macOS, paste one
curl … | sudo shcommand on the device: it creates the tunnel key, registers the device, authorizes Telepy, pins Telepy's host key, installs the tunnel as a systemd/launchd service and waits until Telepy has logged in through it. The command works once and expires after 30 minutes; the create page follows each step live. - Web terminal — multi-tab xterm.js terminal over WebSocket, with per-user sessions and live latency readout; pick a Nerd Font (real bold and italic faces, optional ligatures) and a colour scheme — built-in or your own, imported from a Windows Terminal or base16 scheme — as your default or per device. A dropped connection (network switch, sleeping laptop, reload) can keep the shell running for a time you choose and the tab picks it up again — or end it at once, which stays the default and can be enforced per device.
- Watch a terminal — tunnel owners and admins can open a read-only live view of anyone's web terminal from Live sessions; the person is told in their terminal and by a notification, can let the watcher type along (command rules then count those lines as the watcher's) and take control back at any time, and every step lands in the tunnel's Logs.
- File manager — SFTP file manager alongside the terminal: drag-and-drop uploads with progress, multi-select, move / copy / rename / delete / permissions, image and text preview, and an in-browser editor that never leaves half a file and warns when the file changed on the device.
- Remote desktop — RDP and VNC in the browser through the tunnel (guacd), or RustDesk decoded right in the tab; the RDP desktop's sound and RustDesk's sound play in the browser when you turn them on, and RustDesk sessions get a Files panel to upload and download files and whole folders (folders arrive as one .zip), rename, move and delete (up to 2 GB per file; deletes and renames are logged). An info diagram shows the path and which streams — picture, input, clipboard, sound, files — travel on the connection, and why one is off.
- Remote proxy browser — launch a real Chromium desktop (KasmVNC) whose traffic is tunneled through the target device, so external requests appear to come from that machine.
- Sharing & permissions — share tunnels with other users under hierarchical VIEW / EDIT / ADMIN permissions; taking access away (unsharing, a smaller account list, a lower permission) ends that person's open terminals, file managers, remote browsers and desktops, laptop SSH and terminal watching right away, and disabling an account also signs it out everywhere while its API tokens and connected apps are only suspended.
- Feature permissions & user groups — administrators decide which Telepy features each person may use (creating or sharing tunnels, terminal, toolbox, files, remote browser and desktop, LAN discovery, personal keys, recordings, command rules, webhooks, MCP apps, API docs) for everyone, per user group or per person, where a person's own setting is final; separate from sharing, and enforced by the API, the sockets, MCP tools and the SSH gateway alike. A diagram shows which layer decided and how it differs from tunnel sharing.
- Command sheet — pick a tunnel and get copy-ready
ssh -L/-R/-D/-J/scpcommands for forwarding ports or copying files from your own computer, each with a live diagram of where the traffic flows; works with your~/.ssh/configor as standalone one-liners. - Device facts — each device's OS, kernel, uptime, CPU, memory, root disk, load and SSH version, read once a day by the same read-only login Telepy already makes (nothing installed, nothing sent elsewhere), with warnings for a full disk, low memory, heavy load or a pending reboot on the tunnel list, in the Logs and to webhooks.
- Network topology (jump hosts) — see the machines behind each device at a glance: scan a device's LAN without installing anything (the device's own sshd makes the connections), label hosts and set their login, then open a web terminal on one through the device, or copy an
ssh -Jone-liner or aProxyJumpssh config stanza. - Webhooks — everyone can send the notifications they get (a tunnel going offline or changing IP, a share, an access request, a command waiting for approval, a sign-in…) to Slack, Discord, Google Chat, Microsoft Teams, Telegram, ntfy or their own service, per tunnel and in their own language; admins add site-wide webhooks for outages and security events. Requests are signed (Standard Webhooks) and personal URLs may only reach public addresses.
- Command rules — block commands, or hold them until someone approves, site-wide or per tunnel, for the web terminal and MCP agents alike; "allow" exceptions carve out the safe cases within the same scope, an approval rule can name its own approvers (a security team for
sudoeverywhere), and an info diagram shows the path every tested command takes. - Toolbox — saved command snippets beside the web terminal (and in the LAN terminal dock): your own, per device or site-wide, with
$variablesfilled in before each run (secret ones masked and never stored), favourites, "ask first" for risky ones and a dozen built-in examples; a snippet is typed into the shell like your own keystrokes, so command rules still apply, and an info diagram shows each step of a run. - Keys & logs — manage authorized keys and inspect the SSH gateway log from the UI, globally or per tunnel (users only see their own and shared tunnels). Per-device key lists put your keys into exactly the accounts you may use — or a chosen subset — including the administrators file on Windows devices, and a key removed from a device by hand can be put back for 7 days.
- Authentication — Google OAuth2 or username/password, JWT-backed API. The first user to log in becomes the superuser. Optional two-factor sign-in with passkeys (also passwordless) or an authenticator app (TOTP), plus one-time recovery codes; administrators can reset a user's second factors. "Your devices" lists every browser signed in to your account and signs any of them out at once (open pages close too); a sign-in from a new country or browser is announced, and admins can sign users out.
telepycommand line & API tokens —telepy ls,telepy ssh box,telepy exec --label env=prod -- uptime,telepy cp, live sessions, recordings and your tokens' call log (telepy api activity) from your terminal;telepy lan,telepy browse(a scripted browser on a device's network: read pages, screenshots, clicks); sign in through the browser or with a personal API token (scopes, tunnel limit, required expiry, revoked at once) that scripts, CI and headless MCP agents can use too.- API page — your tokens, every endpoint a token can call (grouped by scope, with copy-ready
curland the matching CLI command, built from the server's own allow-list), and an activity log of every call your tokens made; admins get everyone's. A diagram compares the three ways in (web sign-in, API token, MCP app) and what each revoke cuts. - Internationalization & theming — English, 繁體中文, and 日本語; light and dark themes.
Go to Tunnels → Create Tunnel, paste the device's SSH public key, and follow the wizard. Run the generated script on the device and the tunnel comes online.
Click Terminal on any online tunnel to get a shell on the device, straight from the browser.
Click Browser on an online tunnel to start a Chromium session that browses the web through the device (SSH SOCKS proxy + KasmVNC streaming).
The device keeps a reverse SSH connection (ssh -NR <port>:localhost:22 telepy@server) to Telepy's backend, which terminates it with its built-in SSH gateway and assigns each tunnel a dedicated port. The web terminal, file manager, and proxy browser all reach the device through that port — no inbound firewall rules or public IP needed on the device side.
Telepy runs as four containers orchestrated by Docker Compose:
| Service | Role |
|---|---|
traefik |
Reverse proxy; routes /api/* & /ws/* to the backend, everything else to the frontend |
frontend |
Next.js web UI |
backend |
Rust (axum) service: REST API, WebSocket terminal/file manager/relay, and the SSH gateway that terminates the reverse tunnels |
kasm-browser |
KasmVNC + Chromium for remote browser sessions |
Requires Docker with the Compose plugin.
-
Create your environment file and adjust as needed:
cp .env.example .env
-
Generate the SSH server keys:
./telepy.sh keygen
-
Build and start the stack:
docker compose up -d --build
-
Open
http://localhost:<WEB_SERVER_PORT>/login(default:http://localhost:8787/login) and sign in.
Note
The first user created becomes the superuser — whether via Google login or ./telepy.sh create-superuser (check dev-scripts/dev-create-superuser.sh to change the default username/password before running it).
- Tunnels → Create Tunnel, paste the device's SSH public key, and complete the wizard (add at least one OS username that exists on the device).
- Append the shown service key to
~/.ssh/authorized_keyson the device. - Run the generated connection script on the device (SSH for quick tests, AutoSSH/systemd/Docker for something persistent). Scripts can be retrieved any time from the tunnel's Scripts action.
- When the tunnel shows Online, the terminal, file manager, and remote browser are ready to use.
Environment variables in .env:
| Variable | Default | Description |
|---|---|---|
PROJECT_NAME |
main |
Suffix for container names |
DEBUG |
false |
Debug logging |
SECRET_KEY |
— | JWT signing key (set in production) |
WEB_SERVER_PORT |
8787 |
Port Traefik exposes the web UI on |
REVERSE_SERVER_SSH_PORT |
24242 |
Port devices connect their reverse SSH to |
SOCIAL_GOOGLE_CLIENT_ID |
— | Google OAuth client ID |
SERVER_DOMAIN |
localhost |
Public hostname of your Telepy server |
PUBLIC_URL |
— | Public base URL, e.g. https://telepy.example.com (OAuth issuer for MCP clients and the passkey origin; derived from the request when empty). Passkeys need a domain name with https (or localhost) |
INTERNAL_API_TOKEN |
— | Secret for internal service-to-service calls |
OPENAPI_ENABLED |
true |
Serve Swagger UI + OpenAPI spec (see below) |
Telepy is its own OAuth 2.1 authorization server, so any MCP-capable AI tool (Claude, Cursor, …) can
be pointed at https://<your-host>/mcp. The first use opens an approval page in your browser where you
check the app's redirect address, choose the permissions (see tunnels / run commands / read and write
files) and the tunnels it may use. Approved apps appear under Preferences → Connected apps, where
revoking stops their access and closes their sessions immediately. Sessions opened by AI tools close on
their own when idle; the limits are site settings. AI activity in the sidebar shows the open sessions (with
a close button) and every tool call the agents made; owners of shared tunnels are notified when an agent
connects. Details: docs/mcp.md.
For Claude Code: claude mcp add --transport http telepy https://<your-host>/mcp. Tools: list_tunnels,
tunnel_status, ssh_session_open / ssh_exec / ssh_session_list / ssh_session_close, sftp_list /
sftp_read / sftp_write, interactive shells for TUIs and prompts (shell_open / shell_send / shell_read — the
agent reads the screen as text; command rules gate every Enter; recorded on recorded tunnels) and, with the
browser:use permission, a remote browser that goes online through the device (browser_open / browser_navigate /
browser_read / browser_click / browser_type / browser_screenshot). The MCP page's "What can an app reach?"
diagram shows what each connected app's permissions open up. Every tool call is audited (/api/ai/activity), and a tunnel's owner is notified when
someone it is shared with opens an AI session on it. Details: docs/mcp.md.
The backend publishes its OpenAPI 3 document at /api/openapi.json and a Swagger UI at
/api/__hidden_swagger. Both require a login: the page reuses your web session (open it in the
browser you are logged in with; otherwise it sends you to /login), and the spec needs a Bearer
token (Authorization: Bearer <access_token> from POST /api/auth/token). "Try it out" requests
carry your login automatically; Authorize lets you test with another token. The spec is
generated from the actual routes (utoipa), so it is always in sync with the server;
telepy-server openapi prints the same document from the CLI. Set OPENAPI_ENABLED=false to
remove both. The API page in the web app links to it ("Full reference") and lists, from the same document, which
endpoints a personal API token may call.
./telepy.sh wraps the day-to-day maintenance scripts:
Usage: ./telepy.sh sub-command [args]
Sub-commands:
keygen: Generate SSH host keys (gateway) and the backend service key.
create-superuser: Create an admin account for Telepy management.
shell: Open a shell in the backend container.
migration: Reconcile the database schema (also runs automatically on start).
backend-debug: Recreate the backend container and follow its logs.
oui: Download the IEEE OUI registry so LAN hosts show their NIC vendor.
doctor: Check .env, containers, ports and the backend's configuration; exits 1 when something is broken.
Install a release build (macOS, Linux, Windows; see docs/cli.md) or cargo install --git https://github.com/NatLee/telepy telepy-cli, then:
telepy login https://telepy.example.com # browser sign-in, or --token tpt_… (the API page → Your tokens)
telepy ls # tunnels with health, latency, accounts and labels
telepy ssh box@pi # system ssh through the tunnel, Telepy's host key pinned
telepy exec --label env=prod -- uptime # many devices at once; command rules and audit apply
telepy cp ./app.conf box@pi:/etc/app/
telepy api activity --follow # every call your API tokens make, as it happensIn CI set TELEPY_URL and TELEPY_TOKEN instead of signing in.
- Remote browser architecture
- Remote desktop (RDP / VNC) architecture
- Webhooks: events, formats and signatures
- The
telepycommand line and API tokens - Latency measurement deep dive
- Rust backend migration plan
- Backend benchmark: Django vs Rust
- Articles
Nat Lee |
H. Alice |
Boris Lok |
Telepy is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). If you run a modified Telepy as a service for other people, you must offer them its source code. If that does not fit your use, a commercial license is available — contact @NatLee.
Versions released before October 2026 were MIT-licensed; see NOTICE. Contributions are made under the Contributor License Agreement.



