CLI tool
tailscale/tailcat avatar
tailscale/tailcat

tailcat: netcat over Tailscale's data plane, without the control plane

like netcat, but over Tailscale's data plane, without Tailscale's control plane

7,809 stars334 forksGoBSD-3-Clause

At a glance

What is it?
tailcat is a Go library and CLI from Tailscale that pipes stdin/stdout or forwards TCP ports over WireGuard without a Tailscale account. It is a userspace tool for one-off transfers and port forwarding between two machines, and it costs you the coordination layer that normally makes that easy.
Who is it for?
Adopt tailcat if you need a userspace, account-free pipe or port forward between two machines you already control, and if you can accept that the connection address is exchanged out of band and that NAT traversal can fall back to a DERP relay. Do not adopt it as a replacement for a managed mesh: it has no coordination server, no ACLs, and no identity layer.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem tailcat solves: a pipe between two machines without an account

The README describes tailcat as a remix of Tailscale open source pieces that acts like netcat, but over Tailscale's data plane without Tailscale's control plane. That sentence is the whole product. If you have two machines and want to move bytes between them, netcat is the obvious tool, but it assumes you already have a route and a port that is reachable. Tailscale solves reachability, but it also asks you to sign in, run a daemon, and let a coordination server decide who can talk to whom.

tailcat keeps the second half and drops the third. The README states that you do not need a Tailscale account and do not need root or admin access, because it does not alter your machine's routing tables or DNS. It is a userspace library and CLI. The audience is narrow and specific: engineers who want a WireGuard tunnel between two boxes they already control, without enrolling either box into a tailnet, and who are willing to move the connection metadata themselves. That last clause is the trade you are making, and it is worth reading twice.

How tailcat bootstraps through DERP and upgrades to a direct UDP path

The mechanism is visible in the README and in the repository layout. One side runs a server and prints a short tailcat address. The other side takes that address and connects. All traffic between them is encrypted end-to-end with WireGuard. The initial connection bootstraps through a DERP server, and then magicsock performs NAT traversal to upgrade to a direct peer-to-peer UDP connection when possible. The README adds the parenthetical "usually!", which is honest and worth keeping in mind.

DERP is doing two jobs here. It is the NAT-hole-punching side channel, and it is the relay of last resort when traversal fails. The default DERP map is https://tailcat.dev/derpmap.json, described as free and rate-limited. The README also links to the upstream derper command if you want to run your own relay, under a section titled bring-your-own-derp-relay. The file names in the repository match the description: disco.go for the discovery protocol, pickregion.go for relay selection (with a pickregion_js.go variant for the WebAssembly build), wire.go, listen.go, and connblob_deprecated.go, which suggests the address format has already changed once.

The address itself is the coordination layer, compressed into a string. It is printed by the server and passed to the client by whatever channel you choose. Nothing in the README describes a rendezvous service that stores it, which is exactly the point: connection metadata is exchanged out of band, however you want.

Installing tailcat and piping your first bytes between two machines

The README does not carry install commands. It points at INSTALL.md for details on each method, and the table there lists static binaries, .deb and .rpm packages, Homebrew for macOS, Scoop for Windows, Snap, a container image, Nix, AUR, conda-forge, and building from source with the Go toolchain. FreeBSD and OpenBSD are listed as build-from-source only. The go.mod file declares module github.com/tailscale/tailcat and a go directive of 1.27.1, so the source path expects a recent Go toolchain.

The simplest first use is the stdin/stdout pipe. Start the server with no arguments. The README shows it printing the selected bootstrap relay region and a new address, then hanging while it waits.

Forwarding ports, serving local TCP, and the exit-node mode

The pipe is the demo; port forwarding is the practical use. The README shows a server started with a comma-separated list of ports, or the word all, and a client that names one of those ports. The client's output is a raw TCP conversation, which the README illustrates with an HTTP request and response. Start the server on the machine that owns the service:

bash
$ tailcat serve 8080,8443 # or: tailcat serve all
# 🐈 Server listening with new address: tcXXXXXXXXX

Then, on the other machine, pass the address and the port you want to reach. The README's example speaks HTTP directly over the tunnel:

bash
$ tailcat tcXXXXXXXXX 8080
GET / HTTP/1.1
Host: foo

HTTP/1.1 200 OK

A mapping can also redirect a port somewhere other than the same port on localhost. The README's example serves port 5555 by proxying it to an Android device's adb port on the LAN, with the note that this avoids exposing the rest of the network the way an exit node would. IPv6 targets go in brackets:

bash
$ tailcat serve 5555:10.2.200.213:5555
# Proxying port 5555 to 10.2.200.213:5555
# 🐈 Server listening with new address: tcXXXXXXXXX

The forward subcommand inverts the direction: instead of giving you a raw socket, it makes the server's ports available as ordinary local TCP ports, for browsers, database clients, or other tools that do not support SOCKS or stdio. A local port of 0 asks the operating system for a free port, and each listener prints its address once it is listening. The README shows a mapping that renumbers 8080 to 18080 while leaving 3306 alone:

bash
$ tailcat forward tcXXXXXXXXX 18080:8080 3306

The browse subcommand is documented as an alias for forward --open-browser with a 0:80 mapping, opening http://127.0.0.1:<port>/ once the listener is ready. There is also an exit-node mode, where the server forwards to assets on its own network. The README's example forwards 127.0.0.1:3001 to 172.23.52.30:3001 and 127.0.0.1:17170 to 172.23.52.31:17170 through the exit-node server:

bash
$ tailcat serve exit-node
# 🐈 Server listening with new address: tcXXXXXXXXX

$ tailcat forward tcXXXXXXXXX \
    3001:172.23.52.30:3001 \
    17170:172.23.52.31:17170

Listeners bind to 127.0.0.1 by default, and the README is explicit that --bind=0.0.0.0 should be used only when clients on other machines should be able to connect:

bash
$ tailcat forward --bind=0.0.0.0 tcXXXXXXXXX 18080:8080

Verbose networking logs are behind --verbose, placed before the subcommand.

Where tailcat stops being the right tool

The out-of-band address exchange is the limitation, not a footnote. Tailscale's control plane exists to distribute keys and addresses, enforce ACLs, and give machines stable names. tailcat removes all of that. You are the distribution channel. If the address leaks, whoever holds it can attempt the connection; the README does not describe an authorization step beyond possession of the address, and it does not document rollback or revocation. That is a real constraint for anything long-lived.

The relay path is the second constraint. The README says direct connections happen "when possible (usually!)", and the in-browser WebAssembly demo is documented as relayed over DERP only, with no direct connections until WebRTC support lands in issue #4. So the browser build is a demonstration, not a peer-to-peer path. If your traffic is large or latency-sensitive, a DERP fallback changes the picture, and the default relays are described as free and rate-limited.

The third constraint is scope. tailcat is a point-to-point tool. It has no subnet router, no MagicDNS equivalent, no device inventory. If you need many machines to reach each other under a policy you can audit, tailcat is the wrong shape, and the repository's own framing (netcat, not a mesh) tells you so.

tailcat, Headscale, and plain WireGuard: three different answers

The comparison that matters is with Headscale, which people search for alongside tailcat. Headscale is a self-hosted implementation of the Tailscale control plane. It keeps the coordination server, the node registry, and the ACL model, and you run it yourself. tailcat deletes the control plane entirely and asks you to move one address string. If your objection to Tailscale is where the coordination lives, Headscale answers it. If your objection is that coordination exists at all, tailcat answers it.

Plain WireGuard is the other reference point, and the difference is operational. WireGuard gives you the encrypted tunnel but expects you to know the peer's endpoint, keep the keys in sync, and handle the case where one side is behind NAT. tailcat's magicsock layer does the NAT traversal and DERP fallback for you, and the address carries the metadata. You are trading a config file you maintain for a string you paste. For a one-off transfer between two laptops, that is a good trade. For a fleet, a config file you can review is probably better.

The repository also carries SSH and SFTP support: tailcat_ssh.go, tailcat_ssh_keys.go, tailcat_sftp.go, and an export_sftp_test.go. The README section on the public-key-authenticated SSH server describes accepting keys from local authorized_keys files or literal OpenSSH public key lines, though the excerpt cuts off there. If you need a shell rather than a pipe, that path exists, but the README's SSH section is the thinnest part of the document and you should read the source before relying on it.

Maintenance, licensing, and what an upgrade costs you

The repository is not archived, and the last push was on 2026-09-21. Releases v0.5.0, v0.6.0, and v0.7.0 landed in September 2026, roughly two weeks apart, so the project is moving and the version numbers are still in the 0.x range. That cadence is the upgrade cost in one line: expect the CLI surface to shift. The presence of connblob_deprecated.go in the top-level tree is evidence that at least one format has already been retired, and the README's own usage examples print placeholder addresses like tcXXXXXXXXX, which suggests the exact address shape is not treated as a stable contract.

The licence is BSD-3-Clause, per the LICENSE file. That is permissive and imposes the usual condition of retaining the copyright notice and disclaimer. Nothing here is legal advice, and the practical point is narrower: tailcat depends on tailscale.com and github.com/tailscale/wireguard-go, so your obligations are not only tailcat's. If you vendor or redistribute a binary, read the notices for the dependency tree, not just the top-level licence.

The Makefile has a single tidy target that runs go mod tidy and then go run ./tool/updateflakes, which updates the Nix flake hashes. If you build from source in a Nix environment, that target is the one to run after dependency changes, and it is the only build automation the Makefile exposes.

Editorial conclusion

Adopt tailcat if you need a userspace, account-free pipe or port forward between two machines you already control, and if you can accept that the connection address is exchanged out of band and that NAT traversal can fall back to a DERP relay. Do not adopt it as a replacement for a managed mesh: it has no coordination server, no ACLs, and no identity layer. Before you commit, verify that the default DERP map at https://tailcat.dev/derpmap.json is acceptable for your traffic, and check INSTALL.md for the method that matches your platform, since the README only points at it.

Frequently asked questions

How do you use tailcat?

Run tailcat on one machine to start a server, which prints a short tailcat address, then pass that address to tailcat on the other machine. From there you can pipe stdin/stdout, serve local TCP ports with tailcat serve, forward them locally with tailcat forward, or open a browser with tailcat browse.

What is tailcat?

It is a Go library and CLI from Tailscale that behaves like netcat over Tailscale's data plane without Tailscale's control plane. Connections are encrypted end-to-end with WireGuard, bootstrap through a DERP relay, and upgrade to a direct peer-to-peer UDP connection when NAT traversal succeeds.

Do I need a Tailscale account or root access to run tailcat?

No. The README states that you do not need a Tailscale account and do not need root or admin access, because tailcat does not alter your machine's routing tables or DNS. It is a userspace library and CLI tool.

Where does tailcat get its relay, and can I run my own?

The default DERP map is https://tailcat.dev/derpmap.json, which the README describes as free and rate-limited. The README links to the upstream derper command for running your own relay, under a section titled bring-your-own-derp-relay.

Does the in-browser tailcat demo make direct connections?

No. The README states that browser traffic is relayed over DERP only, with no direct connections until WebRTC support lands in issue #4. The WebAssembly build interoperates with the CLI but does not get the peer-to-peer upgrade.

Official sources

  1. License: BSD-3-Clause
  2. Project website
  3. README
  4. Releases
  5. tailscale/tailcat on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tailscale-tailcat.svg)](https://hysenlabs.com/projects/tailscale-tailcat)