yatun is an open-source TCP reverse tunneling tool (think ngrok / cloudflared) written in Go. It exposes local services to the internet through a public relay server.
┌──────────┐ TCP ┌────────────────┐ yamux ┌──────────┐ TCP ┌─────────┐
│ Internet │───────────▶│ yatund │◀═════════▶│ yatun │────────▶│ local │
│ Client │ random port│ (relay server)│ session │ (agent) │ :port │ service │
└──────────┘ └────────────────┘ └──────────┘ └─────────┘
I wanted to understand how tools like ngrok and cloudflared work under the hood, so I built one.
| Component | Role |
|---|---|
| yatund | Public relay server. Agents connect on port 5678. For each agent, it opens an ephemeral TCP port and forwards all incoming traffic through a yamux multiplexed session back to the agent. |
| yatun | Agent that runs alongside your local service. Connects to yatund, authenticates, and starts forwarding. Comes with a real-time Bubble Tea TUI showing tunnel status, active connections, and ping latency. |
- Agent opens a TCP connection to
yatund:5678, upgrades to a yamux session. - Agent sends connection details (subdomain) over the first yamux stream.
- Server opens an ephemeral TCP port, sends the public address back to the agent, starts a heartbeat ping loop.
- When an internet client connects to the open port, the server opens a new yamux stream toward the agent.
- Agent dials
localhost:<port>and bidirectionally copies data between the yamux stream and the local service.
Each agent gets its own TCP port on the server. All connections to that port are multiplexed over a single yamux session to the agent, which forwards them to the local service.
# For the client
go install github.com/KatIsCoding/yatun/cmd/yatun@latest
# For the server
go install github.com/KatIsCoding/yatun/cmd/yatund@latestPre-built binaries are available on the Releases page.
Expose a local service through the public relay at yatun.snowdev.one — no server setup needed.
# Expose local port 8080
yatun --port 8080The agent opens a TUI showing connection status, the public address, active connections, and ping latency.
- Press
qorCtrl+Cto quit. - Press
cto copy the public address to your clipboard.
Use --server to point at a different relay:
yatun --port 8080 --server your-server.comRun your own relay on a machine with a public IP.
# Optional: set your public domain so addresses are reported as domain.com:port
export DOMAIN=https://tunnel.example.com
yatundThe server listens on port 5678 for agent connections.
| Variable | Required | Default | Description |
|---|---|---|---|
DOMAIN |
No | "" |
Public-facing domain. When set, the server reports addresses as yourdomain.com:<port> instead of raw IP:port. |
TLS |
No | 0 (off) |
Enable TLS for incoming external connections. Set to any non‑0 value. Requires certificate files. |
CERT_PATH |
No | certs/cert.cer |
Path to the TLS certificate (fullchain). Only used when TLS is enabled. |
KEY_PATH |
No | certs/cert_key.key |
Path to the TLS private key. Only used when TLS is enabled. |
| Flag | Required | Default | Description |
|---|---|---|---|
--port |
Yes | — | Local TCP port to expose through the tunnel. |
--server |
No | yatun.snowdev.one |
Address of the yatund server (hostname or IP). |
A Dockerfile and compose file are provided for a simple example of how the relay could be deployed with docker.
cd deploy
docker compose up --buildThe compose file requires an .env file in deploy/. Example:
# Domain for address reporting (optional)
DOMAIN=https://tunnel.example.com
# TLS (set to 1 to enable)
TLS=1
# Paths to your certificate files on the host (For the purposes of the example in the deploy/ folder)
HOST_CER_FILE=/root/.acme.sh/example.com_ecc/fullchain.cer
HOST_KEY_FILE=/root/.acme.sh/example.com_ecc/example.com.keyThe server looks for certificates at certs/cert.cer (fullchain) and certs/cert_key.key (private key) inside the container by default (can be changed over env vars). The compose file maps host paths to those locations.
Messages between agent and server use a simple binary format:
[1 byte: type][2 bytes: payload length (big-endian uint16)][N bytes: JSON payload]
| Byte | Message | Direction | Description |
|---|---|---|---|
A |
TCPConnection | Agent → Server | Signal: agent requests a TCP tunnel |
B |
ConnectionDetails | Agent → Server | Handshake: subdomain info (JSON) |
C |
ResponseMessage | Server → Agent | Server response with public address (JSON) |
D |
PingMessage | Server → Agent | Heartbeat from server (JSON) |
yatun/
├── cmd/
│ ├── yatun/ # Agent binary
│ │ └── main.go
│ └── yatund/ # Server binary
│ └── main.go
├── deploy/
│ ├── Dockerfile
│ └── docker-compose.yml
├── internal/
│ ├── config/ # Server configuration from env vars
│ ├── message/ # Wire protocol encoding / decoding
│ ├── server/ # Server-side tunneling logic
│ ├── tls/ # TLS certificate loading and connection wrapping
│ └── tui/ # Bubble Tea TUI for the agent
├── ssl/
└── install.sh # Helper to install acme.sh
MIT