# How to Use Tailcat for Basic STDIN/STDOUT Piping Over Tailscale

> Learn to use tailcat for basic STDIN/STDOUT piping over Tailscale. Securely stream encrypted data between machines with simple commands. Get started now.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: how-to-guide
- Published: 2026-09-06

---

**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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go), the `--key=new` flag generates a temporary private key and bootstrap address:

```bash
$ 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:

```bash
$ 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`](https://github.com/tailscale/tailcat/blob/main/pipe_test.go) file demonstrates this exact pattern in the test suite:

```bash

# 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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) | Main entry point; handles `--key`, `--derpmap-url` flags and mode selection |
| [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) | Low-level wire protocol; manages Tailscale connections and DERP fallback |
| [`pipe_test.go`](https://github.com/tailscale/tailcat/blob/main/pipe_test.go) | Integration tests for stdin/stdout piping behavior |
| [`readme.go`](https://github.com/tailscale/tailcat/blob/main/readme.go) | Auto-generated usage documentation from `tailcat -h` |

## Complete Working Example

Two-terminal workflow for transferring a directory:

```bash

# 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`](https://github.com/tailscale/tailcat/blob/main/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.