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:

  1. Prints the connection address to stdout
  2. Blocks waiting for a client connection
  3. 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=new generates 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →