An experimental, reverse-engineered Go client for UniFi Teleport. It implements the Teleport cloud exchange, authenticated STUN nomination, in-process WireGuard, and a local SOCKS5 proxy.
This project is intended for interoperability research, experimentation, and authorized use with systems you own or administer. It is unofficial, unsupported, and may stop working if the underlying service or protocol changes.
Users are responsible for ensuring that their use complies with applicable laws, software licenses, service terms, and Ubiquiti terms.
It is primarily intended for headless systems and selective access through a local SOCKS5 proxy without routing the entire host through the tunnel.
Notice of non-affiliation: This project is not affiliated with, endorsed by, or officially connected to Ubiquiti Inc. UniFi and Teleport are trademarks of their respective owners.
main.go: CLI lifecycle and in-tunnel UDP echo loop.api.goandsession.go: Teleport API exchange and local session state.stun.goandnomination.go: STUN mechanics and Teleport endpoint selection.wireguard.goandsocks5.go: tunnel transport and local proxy integration.*_test.go: unit tests for the corresponding protocol components.wireguard_integration_test.go: offline integration tests against a real wireguard-go device andwgCLI (build tagintegration).
- Go 1.25 or later (see
go.mod) - A reachable UniFi console with Teleport enabled
- A fresh
https://teleport.ui.link/<UUID>invite for the first pairing
Download a release binary, or build your own.
Release: grab teleport-client_<os>_<arch> (or .exe on Windows) for
your platform from the GitHub Releases
page, then chmod +x it (not needed on Windows).
Build from source:
git clone https://github.com/sinnet3000/teleport-client.git
cd teleport-client
make build # cross-compiles all platforms into bin/That fills bin/ with a teleport-client_<os>_<arch> for each platform
listed in PLATFORMS in the Makefile. A few other targets:
make test # go test ./...
make vet # go vet ./...
make clean # remove bin/The commands below assume the binary is installed or renamed to
teleport-client and available on your PATH. You can also invoke a downloaded
or locally built binary by its path; the flags are the same.
Use the UUID portion of a fresh invite. Store the resulting session JSON in a private location; it contains credentials and must not be committed or shared.
teleport-client \
--session-file ~/.config/teleport-client/session.json \
--invite <uuid-or-url>Leave that process running. You should see all of this in the output:
level=INFO msg="endpoint selected" ... mode=per_tuple_nominationlevel=INFO msg="WireGuard tunnel ready" ...- periodic
level=INFO msg="UDP echo statistics" ...summaries level=INFO msg="SOCKS5 proxy listening" address=127.0.0.1:1080 ...
Stop it with Ctrl-C. The saved session remains available for reconnecting.
Do not reuse the invite UUID. Reuse the saved session instead:
teleport-client \
--session-file ~/.config/teleport-client/session.jsonIf a prior session no longer receives a console candidate, create a new invite and run the first-connection command again with a new session-file path.
During startup, the client retries compatible observed and console-advertised endpoints if the first WireGuard handshake does not complete. If an established tunnel later stops answering its in-tunnel health probe for about one minute, the client compares the probe failure with WireGuard handshake and receive progress. It keeps a demonstrably active tunnel up if only the echo service is unavailable; otherwise it automatically cycles the known compatible endpoints with bounded backoff. If those endpoints remain unreachable, it closes the old tunnel and performs a fresh ICE/CONNECT negotiation with the saved session so the console can advertise a new UDP tuple. The SOCKS listener is recreated as part of that recovery; existing connections cannot survive a broken tunnel.
The proxy defaults to loopback and supports TCP CONNECT. socks5h resolves
hostnames inside the Teleport tunnel.
curl --proxy socks5h://127.0.0.1:1080 https://ipinfo.io/json
curl --proxy socks5h://127.0.0.1:1080 https://example.comThe proxy has no authentication. Binding it to a non-loopback address exposes it to other hosts on that network. SOCKS UDP ASSOCIATE is not implemented.
IPv4 and IPv6 are enabled by default. Restrict the outer connection with:
teleport-client -4 --session-file ~/.config/teleport-client/session.json
teleport-client -6 --session-file ~/.config/teleport-client/session.json--name <name>: set the Teleport client name.--debug: enable debug logging.--endpoint <host:port>: force the WireGuard endpoint.--turn: require and use a console TURN relay.--print-config: print the WireGuard configuration and exit.--socks5 <host:port>: set the SOCKS5 listen address (default127.0.0.1:1080).
Use the OS service manager to keep the client running. Pair once before you install a service; the service reads the saved session on every start. Keep invite URLs and session files out of service definitions.
Build the binary, copy the template, and replace __USER__ with your account
name.
mkdir -p ~/.local/bin ~/.config/teleport-client ~/Library/LaunchAgents
go build -o ~/.local/bin/teleport-client .
cp deploy/launchd/com.example.teleport-client.plist \
~/Library/LaunchAgents/com.example.teleport-client.plist
sed -i '' "s/__USER__/$(id -un)/g" \
~/Library/LaunchAgents/com.example.teleport-client.plist
launchctl bootstrap "gui/$(id -u)" \
~/Library/LaunchAgents/com.example.teleport-client.plistThe agent starts at login and writes to ~/Library/Logs/teleport-client.log.
To stop and remove it:
launchctl bootout "gui/$(id -u)" \
~/Library/LaunchAgents/com.example.teleport-client.plistInstall the binary at the path used by the unit, then enable the per-user service:
go build -o teleport-client .
sudo install -m 0755 teleport-client /usr/local/bin/teleport-client
mkdir -p ~/.config/systemd/user ~/.config/teleport-client
cp deploy/systemd/teleport-client.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now teleport-client.serviceOn a server, enable lingering once so this user service starts after boot:
sudo loginctl enable-linger "$USER"Check the service and follow its logs with:
systemctl --user status teleport-client.service
journalctl --user -u teleport-client.service -fThe unit reads ~/.config/teleport-client/session.json. Pair with that path,
or change --session-file in your copied unit.
go test ./...
go vet ./...