How to Use Tailcat for Basic STDIN/STDOUT Piping Over Tailscale
Run tailcat --key=new on the server to generate an address, then pipe data with command | tailcat <address> on the client to stream encrypted traffic between machines.
Tailcat is a lightweight utility from the Tailscale organization that creates bidirectional, encrypted pipes between machines using your existing Tailscale network. It behaves like cat but works across hosts, making it ideal for ad-hoc file transfers, backups, and streaming without configuring firewalls or port forwarding.
Core Concepts: Server Mode vs. Client Mode
Tailcat operates in two complementary modes that work together to establish a pipe:
- Server mode (
tailcat --key=new): Listens for incoming connections, prints a connectable address, and forwards data between its stdin/stdout and the remote peer - Client mode (
tailcat <address>): Connects to a server address and forwards its stdin to the remote side while writing remote output to stdout
The connection is secured by Tailscale's wire protocol (implemented in wire.go), with automatic DERP fallback when direct paths aren't available.
Starting the Tailcat Server
Begin by launching the server on the receiving machine. According to cmd/tailcat/tailcat.go, the --key=new flag generates a temporary private key and bootstrap address:
$ tailcat --key=new
tcp://100.64.41.23:42000
The server immediately:
- Prints the connection address to stdout
- Blocks waiting for a client connection
- Relays data between its stdin and the remote peer
Capture this address for use on the client side:
$ tailcat --key=new > /tmp/tc-addr
Connecting the Client and Piping Data
On the sending machine, pipe any command's output through tailcat using the captured address. The pipe_test.go file demonstrates this exact pattern in the test suite:
# Stream a file to the remote server
$ cat largefile.bin | tailcat $(cat /tmp/tc-addr)
# Compress and transfer a directory
$ tar -cz /var/logs | tailcat tcp://100.64.41.23:42000 > remote-backup.tar.gz
# Database dump over encrypted pipe
$ pg_dump mydb | tailcat $(cat /tmp/tc-addr) | gzip > backup.sql.gz
The client forwards its stdin to the Tailscale-secured connection and writes the remote side's responses to stdout.
How the Pipe Terminates
When the client's stdin reaches EOF, the connection half-closes. The server detects this condition and exits cleanly. This behavior is verified in pipe_test.go, which asserts that both processes terminate properly after EOF without hanging or requiring explicit shutdown signals.
Key Implementation Files
Understanding the source structure helps troubleshoot issues:
| File | Purpose |
|---|---|
cmd/tailcat/tailcat.go |
Main entry point; handles --key, --derpmap-url flags and mode selection |
wire.go |
Low-level wire protocol; manages Tailscale connections and DERP fallback |
pipe_test.go |
Integration tests for stdin/stdout piping behavior |
readme.go |
Auto-generated usage documentation from tailcat -h |
Complete Working Example
Two-terminal workflow for transferring a directory:
# Terminal 1 (destination machine)
$ tailcat --key=new
tcp://100.89.12.45:38192
# Server now waiting; type or pipe input to send to client
# Terminal 2 (source machine)
$ tar -czf - ./project | tailcat tcp://100.89.12.45:38192
# Archive streams through Tailscale encryption to Terminal 1
For full bidirectional transfer, run complementary commands on both ends—each side's stdin travels to the other's stdout.
Summary
- Server initiation:
tailcat --key=newgenerates a temporary address and listens - Client connection:
tailcat <address>joins the pipe with stdin/stdout forwarding - Encryption: All traffic uses Tailscale's wire protocol with automatic DERP fallback
- Cleanup: EOF on client stdin triggers graceful termination of both sides
- Flexibility: Works with any stdin-producing and stdout-consuming Unix tools
Frequently Asked Questions
Does tailcat require persistent keys or accounts?
No. The --key=new flag generates a temporary, disposable private key valid only for that session. This design eliminates key management overhead for one-off transfers.
What happens if direct Tailscale connectivity fails?
The connection automatically falls back to DERP relay servers. This fallback is transparent and handled in wire.go without requiring manual configuration.
Can multiple clients connect to one server?
No—tailcat creates 1:1 pipes. Each server address accepts exactly one client connection. For many-to-one scenarios, restart the server with a fresh --key=new for each peer.
How does tailcat compare to ssh for piping?
Tailcat requires no SSH daemon, host keys, or authentication setup on either endpoint. As long as both machines are on the same Tailscale network, the pipe works immediately with machine-level authorization already handled by Tailscale.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →