# How to Run a Minimal Tailcat Client in Go: Complete Setup Guide

> Learn how to run a minimal Tailcat client in Go. This guide covers setting up a client with a connection token, dialing TCP ports, and utilizing net.Conn for encrypted communication.

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

---

**You can run a minimal Tailcat client by passing a connection token to `tailcat.NewClient`, dialing a TCP port with `DialTCPPort`, and using the returned `net.Conn` for encrypted communication.**

Tailcat is an open-source networking library from the `tailscale/tailcat` repository that enables secure TCP connections over WireGuard without manual key management. According to the source code, the library handles NAT traversal, DERP relay selection, and encryption behind a minimal Go API. This guide demonstrates how to build a production-ready client in fewer than 20 lines of code.

## How the Tailcat Client Works Internally

### Token Parsing and Server Discovery

The client receives a **connection token**—a CBOR-encoded base64 string containing the server’s WireGuard public key and DERP relay coordinates. The `Server` type definition at line 277 of [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) handles token generation, while the client decodes this blob via `tailcat.ConnBlob` to auto-configure its network stack.

### Ephemeral WireGuard Tunnels

Upon initialization, the client generates an ephemeral WireGuard keypair and connects through the same DERP relay as the server using magicsock. The peers exchange keys, complete a WireGuard handshake, and establish a userspace encrypted tunnel. This low-level plumbing is implemented in [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) and [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go), which manage UDP hole-punching and automatic NAT traversal.

### Lazy Dialing Architecture

Tailcat conserves resources through **lazy dialing**. The tunnel remains dormant and consumes zero bandwidth until you explicitly call `DialTCPPort`, at which point the client negotiates the WireGuard session and establishes the TCP stream.

## Building a Minimal Tailcat Client in Go

Import the library and instantiate the client with `tailcat.NewClient`, passing the server token as a `ConnBlob`:

```go
package main

import (
	"context"
	"io"
	"log"
	"os"

	"github.com/tailscale/tailcat"
)

func main() {
	// The server token is passed as the first CLI argument.
	// Example token: tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu
	cl := tailcat.NewClient(tailcat.ConnBlob(os.Args[1]))
	defer cl.Close()

	// Connect to TCP port 80 on the server (you can change the port).
	c, err := cl.DialTCPPort(context.Background(), 80)
	if err != nil {
		log.Fatal(err)
	}
	// Copy whatever the server sends to stdout.
	io.Copy(os.Stdout, c)
}

```

Build and execute the client with a valid server token:

```bash
go build -o client .
./client tcomFwWCCcjS5nKNqAod034nWoJZW0LZqDhhC8U_dKdnDRYQ8uNGFpGQEu

```

The `DialTCPPort` method returns a standard `net.Conn`, allowing you to use familiar Go networking patterns while Tailcat transparently handles encryption, routing, and NAT traversal.

## Creating a Minimal Server for Testing

To generate a connection token for your client, run a minimal server using the `tailcat.Server` type. Define an `OnTCP` handler that responds to incoming connections:

```go
package main

import (
	"fmt"
	"log"
	"net"

	"github.com/tailscale/tailcat"
)

func main() {
	s := &tailcat.Server{
		OnTCP: func(port uint16) func(net.Conn) {
			return func(c net.Conn) {
				fmt.Fprintf(c, "hello from port %v\n", port)
				c.Close()
			}
		},
	}
	if err := s.Start(); err != nil {
		log.Fatal(err)
	}
	// Print the token that the client must use.
	fmt.Println(s.ConnBlob())
	select {} // keep the server running
}

```

Execute `go run server.go` to output a fresh token. Copy the printed blob and provide it as the command-line argument to your minimal client.

## Key Source Files and Architecture

Understanding the repository structure helps when debugging or extending functionality:

- **[`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go)** – Core API containing the `Server` and `Client` types, plus the `NewClient` constructor and token handling logic at line 277.
- **[`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)** – Low-level magicsock and WireGuard integration for managing encrypted tunnels.
- **[`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go)** – Discovery protocol implementation for UDP hole-punching and DERP fallback.
- **[`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go)** – Reference CLI implementation demonstrating production-grade flag parsing and library integration.

## Summary

- **Obtain a connection token** from a running `tailcat.Server` using `ConnBlob()`.
- **Initialize the client** with `tailcat.NewClient()`, passing the token wrapped in a `tailcat.ConnBlob`.
- **Establish connections lazily** via `DialTCPPort()` to create the WireGuard tunnel only when needed.
- **Use standard `net.Conn` interfaces** for I/O operations; Tailcat handles encryption and routing transparently.
- **Leverage automatic NAT traversal** through the magicsock implementation in [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) and [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go).

## Frequently Asked Questions

### What data format does the Tailcat connection token use?

The token is a base64-encoded CBOR blob containing the server’s WireGuard public key and DERP relay coordinates. You pass this string directly to `tailcat.NewClient()` as a `ConnBlob`; the library handles decoding and network configuration automatically without manual key distribution.

### Do I need to configure WireGuard keys manually?

No. The client generates ephemeral WireGuard keypairs automatically during initialization. The server’s public key embedded in the connection token enables the handshake, eliminating the need for static configuration files or manual key exchange as implemented in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go).

### How does Tailcat handle firewall or NAT blocking?

If direct UDP connectivity fails, the client automatically falls back to DERP (Designated Encrypted Relay for Packets) relays. This failover logic in [`disco.go`](https://github.com/tailscale/tailcat/blob/main/disco.go) operates transparently, ensuring connectivity even through strict NATs or firewalls without changing the application-level `DialTCPPort` API.

### Can I use Tailcat for UDP traffic?

While the underlying magicsock transport in [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) supports UDP, the high-level minimal client API currently exposes TCP streaming through `DialTCPPort`, which returns a `net.Conn` suitable for standard TCP read/write operations.