How to Run a Minimal Tailcat Server in Go

Create a tailcat.Server value with optional TCP handling, call Start() to initialize the WireGuard tunnel and DERP connection, then share the base64 token from ConnBlob() with clients to establish encrypted connections.

Tailcat is a lightweight, userspace WireGuard tunnel implementation that routes traffic through Tailscale's DERP relays. The tailscale/tailcat repository provides a Go library that allows you to run a minimal server with just a few lines of code, requiring no kernel modules or root privileges. This guide explains how to leverage the zero-value Server struct to spin up an encrypted tunnel server based on the actual source implementation.

Understanding the Tailcat Server Architecture

Tailcat operates entirely in userspace, bypassing the need for kernel WireGuard modules while maintaining encrypted connectivity through Tailscale's global DERP infrastructure. The core implementation resides in tailcat.go, where the Server struct (defined at lines 77-85) orchestrates ephemeral key generation, nearest DERP region discovery, and WireGuard setup.

Unlike traditional VPN servers requiring extensive configuration, Tailcat's Server type uses sensible defaults when instantiated as a zero value. This design automatically provisions an ephemeral WireGuard key pair, selects the geographically closest DERP relay, and configures a basic logger without explicit initialization.

Creating a Minimal Tailcat Server

To run a minimal Tailcat server, instantiate the Server struct, optionally configure connection handling, invoke the startup sequence, and retrieve the connection token for clients.

Step 1: Instantiate the Server

Create a tailcat.Server value. The zero-value provides production-ready defaults:

s := &tailcat.Server{}

Step 2: Configure TCP Handling (Optional)

Set the OnTCP field to define how the server responds to incoming TCP connections. This field accepts a function that receives a port number and returns a net.Conn handler:

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()
        }
    },
}

Step 3: Start the Server

Call the Start() method (implemented at lines 52-60 in tailcat.go) to initialize the WireGuard interface, connect to the DERP relay, and begin listening for incoming connections:

if err := s.Start(); err != nil {
    log.Fatal(err)
}

Step 4: Retrieve the Connection Token

The ConnBlob() method returns a short base64-encoded string containing the server's WireGuard public key and DGRP routing information. Clients require this token to establish the tunnel:

fmt.Println(s.ConnBlob())

Complete Minimal Server Example

package main

import (
	"fmt"
	"log"
	"net"

	"github.com/tailscale/tailcat"
)

func main() {
	// Create a server with a simple TCP handler.
	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()
			}
		},
	}
	// Start the server – this connects to a DERP relay,
	// generates an ephemeral WireGuard key and begins listening.
	if err := s.Start(); err != nil {
		log.Fatal(err)
	}
	// Print the connection token that the client will use.
	fmt.Println(s.ConnBlob())
	// Block forever (or add your own shutdown logic).
	select {}
}

Run the server with:

go run ./minimal_server.go

The terminal will output a token similar to tcWcL4Q5d7f... that clients use to connect.

Connecting Clients to Your Server

Clients can connect using the CLI wrapper located in cmd/tailcat/tailcat.go or programmatically using the library. Pass the token printed by ConnBlob() as the connection argument:

tailcat <token>

Alternatively, implement a client in Go using the tailcat package to dial the server using the same token string.

Understanding the DERP Integration

The server automatically connects to the nearest Tailscale DERP (Designated Encrypted Relay for Packets) region during startup. This relay handles the initial key exchange and maintains the encrypted tunnel when direct peer-to-peer connectivity is unavailable due to NAT or firewall restrictions. The DERP selection logic and WireGuard configuration occur within the Start() method, requiring no manual intervention.

Summary

  • The tailcat.Server zero-value in tailcat.go provides sensible defaults including ephemeral WireGuard keys and automatic DERP region selection
  • The Server struct is defined at lines 77-85 in tailcat.go, with the Start() method implementing initialization logic at lines 52-60
  • ConnBlob() generates a base64-encoded connection token containing the WireGuard public key and DERP routing information required for client authentication
  • Incoming TCP connections are handled via the configurable OnTCP callback, which receives the port number and returns a net.Conn handler function
  • The CLI implementation in cmd/tailcat/tailcat.go demonstrates production usage of the library for command-line server and client operations

Frequently Asked Questions

What is the difference between Tailcat and standard WireGuard?

Tailcat operates entirely in userspace without requiring kernel modules or root privileges, whereas standard WireGuard typically requires kernel support and administrative access. Tailcat routes traffic through Tailscale's DERP relays when direct UDP connectivity is unavailable, making it functional behind restrictive NATs and corporate firewalls where traditional WireGuard might fail.

How do clients connect to a Tailcat server?

Clients use the base64 token returned by the server's ConnBlob() method. This token encodes the server's WireGuard public key and DERP region information. Pass this token to the tailcat CLI or use it with the Client type in the Go library to establish the encrypted tunnel and begin routing traffic.

Can I run a Tailcat server without handling TCP connections?

Yes. The OnTCP field is optional; if left nil, the server will establish the WireGuard tunnel and DERP connection but reject incoming TCP connections. This configuration is useful when you only need the tunnel infrastructure without exposing services, or when implementing custom protocol handlers outside the OnTCP callback pattern.

Where is the main server logic implemented in the source code?

The core server implementation resides in tailcat.go at the repository root. The Server struct definition appears at lines 77-85, while the initialization logic including DERP discovery and WireGuard setup is contained within the Start() method at lines 52-60. The command-line interface wrapping this library is implemented in cmd/tailcat/tailcat.go.

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 →