Configuration Options for a Tailcat Server: Complete Guide
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. 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 (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.
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.DERPRegionstruct 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 defaulthttps://tailcat.dev/derpmap.jsonendpoint 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 callbackfunc(netip.AddrPort) boolthat 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 factoryfunc(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.PortRangerestricting which ports the server admits. When nil, all ports are allowed.
Command-Line Interface Options
The 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.
# 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.
# 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.
# Restrict to specific clients
tailcat --allow=nodekey:abcdef123456...,nodekey:fedcba654321...
DERP Customization
--derpmap-url: OverridesServer.DERPMapURLto point at a custom DERP map endpoint.--full-address: Embeds the complete DERP node list into the printedConnBlobrather 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) 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:
- Key selection: Loads from
--keypath or generates ephemeral. - DERP resolution: Checks
Server.Region→Server.RegionID→ auto-detect ( probes for lowest latency). - Access control: Parses
--allowlist;nonecreates an empty allow-list blocking all clients. - Port handling: Expands
--serveintoServedTCPPorts,OnTCP, andOnTCPForwardhandlers. - Address formatting:
--full-addressdetermines whetherConnBlobcontains 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:
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:
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:
# 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.Keyor--keyfor persistent addressing; omit for ephemeral. - Bootstrap flexibility: Hard-code a
Region, specify aRegionID, or auto-detect via latency probe. - Security layers:
AllowedClientsprovides allow-list authentication;ServedTCPPortsrestricts the packet filter. - Traffic routing:
OnTCPhandles direct connections;OnTCPForwardenables 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →