# How to Use the Tailcat Go Library as a Server: Complete Implementation Guide

> Implement a Tailscale server using the Tailcat Go library. Create WireGuard tunnels without root privileges. Learn how to set up your server now.

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

---

**The Tailcat Go library provides a zero-value functional server type that creates a WireGuard-encrypted tunnel over Tailscale's DERP relay network without requiring root privileges or host network configuration.**

The `tailscale/tailcat` repository offers a lightweight, userspace networking solution for Go applications. When you use the Tailcat Go library as a server, you embed a complete TCP/IP stack that routes encrypted traffic through Tailscale's infrastructure. This approach eliminates the need for kernel modules or elevated permissions while providing secure connectivity between distributed components.

## Understanding the Server Architecture

### The tailcat.Server Type

At the core of the implementation is the `tailcat.Server` struct defined in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 315-388). This type encapsulates the entire server state, including WireGuard key management, DERP region selection, and TCP connection handlers. The design philosophy emphasizes simplicity: the zero value of `tailcat.Server` is immediately functional, requiring no mandatory configuration fields to begin operation.

### Zero-Value Ready Design

Unlike traditional network servers that require extensive setup, you can instantiate and start a Tailcat server with minimal boilerplate. Optional fields such as `Logf` for logging, `Region` for specific DERP regions, or `RegionID` for geographic selection allow customization, but only the `OnTCP` callback is typically necessary for handling inbound connections.

## Server Initialization and Startup Process

### Key Generation and DERP Selection

When you invoke the `Start()` method (implemented in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) lines 90-140), the server executes a precise initialization sequence. First, it checks the `Server.Key` field; if zero, it generates an ephemeral WireGuard key using `key.NewNode`. Next, it determines the optimal DERP region by evaluating the `Region`, `RegionID`, or automatically selecting the nearest region based on latency through `ConnInfo.Expand`.

### Transport Stack Initialization

The startup process initializes three critical components in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 90-140):

- **magicsock**: Handles UDP and DERP relay transport
- **netstack**: Provides the userspace TCP/IP implementation using gVisor (located in [`internal/netstack/netstack.go`](https://github.com/tailscale/tailcat/blob/main/internal/netstack/netstack.go))
- **locoBackend**: Orchestrates the coordination between WireGuard and the transport layer

After initialization, the server registers DERP handshake callbacks to respond to client probes.

## Handling TCP Connections

Once running, the server exposes TCP listeners and dispatches connections based on their origin. The logic in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 84-98) differentiates between direct connections and forwarded traffic.

### OnTCP vs OnTCPForward

Configure the `OnTCP` field to handle connections destined for the server's own address. Alternatively, use `OnTCPForward` for exit-node mode where the server forwards traffic to other destinations. Both handlers receive a port number and return a function that accepts a `net.Conn`, allowing you to implement protocol-specific logic for each port.

## Complete Server Implementation Example

The following example demonstrates a minimal server implementation that listens on all TCP ports:

```go
// server.go
package main

import (
	"fmt"
	"log"
	"net"

	"github.com/tailscale/tailcat"
)

func main() {
	// Create a Server; the zero value is ready to use.
	s := &tailcat.Server{
		// Handle any TCP port the server receives.
		OnTCP: func(port uint16) func(net.Conn) {
			return func(c net.Conn) {
				fmt.Fprintf(c, "hello from port %v\n", port)
				c.Close()
			}
		},
	}

	// Start the server – this blocks until the DERP connection is ready.
	if err := s.Start(); err != nil {
		log.Fatalf("tailcat server failed: %v", err)
	}

	// Print the connection token that clients will use.
	fmt.Println("Server token:", s.ConnBlob())

	// Keep the process alive (the server runs in background goroutines).
	select {}
}

```

After starting, the server prints a connection token generated by the `ConnBlob()` method (logic defined in [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)). This token encodes the server's WireGuard public key and DERP region information.

### Connecting a Client

Clients use this token to establish connections without needing the server's IP address:

```go
// client.go
package main

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

	"github.com/tailscale/tailcat"
)

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

	// Dial TCP port 80 on the server.
	c, err := client.DialTCPPort(context.Background(), 80)
	if err != nil {
		log.Fatalf("dial error: %v", err)
	}
	io.Copy(os.Stdout, c) // prints "hello from port 80"
}

```

## Summary

- The `tailcat.Server` type in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 315-388) provides a zero-value functional server requiring no root privileges
- The `Start()` method handles WireGuard key generation, DERP region selection, and initialization of the gVisor-based userspace TCP/IP stack
- Configure `OnTCP` callbacks in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 84-98) to handle inbound TCP connections by port number
- The server outputs a connection token via `ConnBlob()` (implemented in [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go)) that clients use to establish secure tunnels
- All networking occurs through Tailscale's DERP relay infrastructure without modifying host network configuration

## Frequently Asked Questions

### Does the Tailcat server require root privileges or kernel modules?

No. The Tailcat Go library operates entirely in userspace using the gVisor netstack implementation ([`internal/netstack/netstack.go`](https://github.com/tailscale/tailcat/blob/main/internal/netstack/netstack.go)) and Tailscale's magicsock for transport. According to the `tailscale/tailcat` source code, the server creates a WireGuard tunnel over the existing network interface without modifying routing tables or requiring CAP_NET_ADMIN capabilities.

### How does the server handle WireGuard key management?

The `Start()` method in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 90-140) automatically handles key generation. If the `Server.Key` field is zero upon startup, the library invokes `key.NewNode()` to create an ephemeral WireGuard key pair. This ephemeral key is then encoded into the connection token returned by `ConnBlob()`, allowing clients to derive the server's public key for tunnel establishment.

### What is the difference between OnTCP and OnTCPForward callbacks?

The `OnTCP` callback handles connections destined for the server itself, as implemented in the TCP dispatch logic of [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 84-98). Conversely, `OnTCPForward` manages exit-node scenarios where the server acts as a forwarder to other destinations. Both callbacks follow the signature `func(port uint16) func(net.Conn)`, enabling per-port handler registration.

### How does the DERP handshake establish connectivity?

During startup, the server registers callbacks to handle the Tailcat protocol handshake. When a client sends a "Meow" ping probe through the DERP relay, the server responds with a "Meowed" acknowledgment, completing the WireGuard tunnel establishment. This handshake mechanism, referenced in the `Start()` method implementation, allows NAT traversal without public IP addresses or port forwarding.