# Configuration Options for a Tailcat Server: Complete Guide

> Master Tailcat server configuration with this guide. Explore programmatic and command-line options for keys, DERP regions, access control, and traffic. Optimize your server setup.

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

---

**A Tailcat server can be configured either programmatically by constructing a `tailcat.Server` struct or via command‑line flags, with both approaches exposing identical knobs for keys, DERP regions, access control, and traffic handling.**

The `tailscale/tailcat` repository provides a lightweight, embedded relay server that exposes its configuration through the `Server` struct defined in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go). Whether you are integrating Tailcat as a library into a Go application or deploying it as a standalone binary, understanding these configuration options controls identity persistence, bootstrap connectivity, and security policy.

## Core Configuration Fields

The `Server` struct in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) (lines 277–352) defines every tunable parameter. These fields accept values directly when instantiating the server programmatically.

### Identity and Keys

The `Key` field (`Server.Key` at line 277) holds the node private key used for WireGuard identity. If left zero, the server generates an **ephemeral key** on each start, resulting in a new address token every time. Providing a persistent key enables stable addressing across restarts.

```go
srv := &tailcat.Server{
    Key: privKey, // key.NodePrivate value
}

```

### Logging

`Server.Logf` (line 282) accepts a logger function matching `func(format string, args ...interface{})`. When nil, it defaults to `log.Printf`. This controls verbosity for DERP region selection and connection diagnostics.

### DERP Region Selection

Three fields govern how the server discovers its bootstrap relay:

- **`Server.Region`** (line 286): Accepts a `*tailcfg.DERPRegion` struct directly, bypassing map lookups entirely. Use this when operating a private DERP server.
- **`Server.RegionID`** (line 290): An integer ID referencing the default DERP map. When zero, the serverauto-selects the nearest region via latency probe.
- **`Server.DERPMapURL`** (line 294): Overrides the default `https://tailcat.dev/derpmap.json` endpoint for fetching the DERP map.
- **`Server.DERPMapCache`** (line 298): Implements caching for fetched maps. If nil, an in-process memory cache is used.

### Access Control

Security boundaries are enforced through two fields:

- **`Server.AllowedClients`** (line 304): A slice of `[]key.NodePublic`. Only these clients may connect; an empty slice permits all connections.
- **`Server.AllowProxy`** (line 312): A callback `func(netip.AddrPort) bool` that determines whether a specific TCP/UDP proxy destination is permitted when operating in SOCKS5 proxy mode.

### Traffic Handling

Inbound connection behavior is defined by three fields:

- **`Server.OnTCP`** (line 324): A handler factory `func(port uint16) func(net.Conn)` for connections directed to the server’s own address. Return nil to send RST.
- **`Server.OnTCPForward`** (line 334): A handler factory for connections relayed through the server in exit-node mode.
- **`Server.ServedTCPPorts`** (line 338): A slice of `[]filter.PortRange` restricting which ports the server admits. When nil, all ports are allowed.

## Command-Line Interface Options

The [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) file (lines 48–58) maps CLI flags directly to the `Server` struct fields inside the `server()` function (lines 74–88).

### Key Management

The `--key` flag accepts a path to a `*.private.json` file or the literal string `new` to force ephemeral key generation. This directly sets `Server.Key`.

```bash

# Use a persistent key file

tailcat --key=myserver.private.json

# Force ephemeral (default if file missing)

tailcat --key=new

```

### Service Declaration

The `--serve` flag (parsed in `parsePortSet` at line 660) accepts a comma-separated list of ports or service names (`all`, `exit-node`, `no-auth-ssh`). This populates `ServedTCPPorts`, `OnTCP`, and `OnTCPForward` automatically.

```bash

# Serve ports 8080 and 22, plus built-in SSH

tailcat --serve=8080,22,no-auth-ssh

```

### Client Restrictions

The `--allow` flag accepts a comma-separated list of client public keys (prefixed with `nodekey:`) or `none` to deny all. The CLI populates `Server.AllowedClients` via `Server.AddAllowedClient`.

```bash

# Restrict to specific clients

tailcat --allow=nodekey:abcdef123456...,nodekey:fedcba654321...

```

### DERP Customization

- **`--derpmap-url`**: Overrides `Server.DERPMapURL` to point at a custom DERP map endpoint.
- **`--full-address`**: Embeds the complete DERP node list into the printed `ConnBlob` rather than just a region ID, producing a self-contained but longer address token.

### Output Formatting

The `--json` flag emits the server address as JSON (`{ "listenAddr": "<token>" }`) on stdout for programmatic parsing.

### Environment Variables

`TAILCAT_ADDR_FILE` (handled at lines 34–47 in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go)) specifies a file path or TCP address where the server writes its connection token immediately after startup.

## Configuration Precedence and Interaction

The `server()` function initializes the `Server` struct following a strict precedence:

1. **Key selection**: Loads from `--key` path or generates ephemeral.
2. **DERP resolution**: Checks `Server.Region` → `Server.RegionID` → auto-detect ( probes for lowest latency).
3. **Access control**: Parses `--allow` list; `none` creates an empty allow-list blocking all clients.
4. **Port handling**: Expands `--serve` into `ServedTCPPorts`, `OnTCP`, and `OnTCPForward` handlers.
5. **Address formatting**: `--full-address` determines whether `ConnBlob` contains full DERP node details or a compact region ID.

## Practical Implementation Examples

### Programmatic Server Setup

This example configures a server with a fixed DERP region, specific port restrictions, and a custom TCP handler:

```go
package main

import (
	"log"
	"net"

	"github.com/tailscale/tailcat"
	"tailscale.com/types/key"
	"tailscale.com/types/logger"
	"tailscale.com/wgengine/filter"
)

func main() {
	priv := key.NewNode() // Or load from disk
	
	srv := &tailcat.Server{
		Key:            priv,
		Logf:           logger.StdLogger(log.Printf),
		RegionID:       302, // San Francisco region
		AllowedClients: []key.NodePublic{}, // Allow all
		ServedTCPPorts: []filter.PortRange{{First: 8080, Last: 8080}},
		OnTCP: func(port uint16) func(net.Conn) {
			return func(c net.Conn) {
				c.Write([]byte("HTTP/1.1 200 OK\r\n\r\nHello from Tailcat!\n"))
				c.Close()
			}
		},
	}

	if err := srv.Start(); err != nil {
		log.Fatal(err)
	}
	log.Printf("Token: %s", srv.ConnBlob())
	select {} // Block forever
}

```

### Basic CLI Usage

Run an ephemeral server with auto-selected DERP:

```bash
tailcat

# Selected bootstrap relay region 302, San Francisco

# 🐈 Server listening with new address: tcomFwWCCcjS...

```

### Advanced CLI with Persistent Keys

Generate a persistent key, then run with full address embedding:

```bash

# Generate key

tailcat genkey --key=myserver

# Run with restrictions

tailcat --key=myserver \
        --serve=22,no-auth-ssh \
        --allow=nodekey:abc123... \
        --full-address \
        --json

```

## Summary

- **Two configuration methods**: Direct struct manipulation in Go or CLI flags that map 1:1 to fields.
- **Identity control**: Use `Server.Key` or `--key` for persistent addressing; omit for ephemeral.
- **Bootstrap flexibility**: Hard-code a `Region`, specify a `RegionID`, or auto-detect via latency probe.
- **Security layers**: `AllowedClients` provides allow-list authentication; `ServedTCPPorts` restricts the packet filter.
- **Traffic routing**: `OnTCP` handles direct connections; `OnTCPForward` enables exit-node relaying.

## Frequently Asked Questions

### What is the difference between Server.Region and Server.RegionID?

`Server.Region` accepts a complete `*tailcfg.DERPRegion` struct, allowing you to hard-code a private DERP server and bypass map lookups entirely. `Server.RegionID` is an integer referencing a region in the fetched DERP map (default or custom via `--derpmap-url`). When both are zero, the server performs automatic latency-based selection.

### How do I generate a persistent key for my Tailcat server?

Use the built-in key generation command: `tailcat genkey --key=filename`. This writes a [`filename.private.json`](https://github.com/tailscale/tailcat/blob/main/filename.private.json) file. Reference it in subsequent runs with `--key=filename`. Programmatically, save the `key.NodePrivate` bytes to disk and load them into `Server.Key`.

### Can I restrict which clients connect to my Tailcat server?

Yes. Populate `Server.AllowedClients` with a slice of `key.NodePublic` values, or use the `--allow` CLI flag with comma-separated node keys. An empty slice (CLI default) permits all clients. Specifying `--allow=none` creates an empty allow-list that rejects every connection attempt.

### What DERP region does Tailcat use by default?

If neither `Server.Region` nor `Server.RegionID` is set (and no `--derpmap-url` override exists), the server fetches the default map from `https://tailcat.dev/derpmap.json` and selects the nearest region via latency probe. This auto-detection runs during `Server.Start()` and can be observed via the configured `Logf` logger.