# How to Publish Tailcat Tokens Using DNS TXT Records: A Complete Guide

> Learn how to publish Tailcat tokens using DNS TXT records with this complete guide. Connect easily using hostnames instead of raw token strings.

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

---

**Tailcat allows you to publish connection tokens via DNS TXT records by creating a record containing `tailcat=<token>`, enabling users to connect using a hostname instead of a raw token string.**

Tailcat is a secure tunneling tool that identifies servers using connection tokens (the "tc-…" strings). Rather than distributing long base64-encoded tokens manually, you can publish these tokens using DNS TXT records, making it easier to share and rotate credentials across distributed teams.

## How DNS Resolution Works in Tailcat

When you provide an argument containing a dot (`.`) to any Tailcat subcommand, the CLI treats it as a DNS name rather than a raw token. In [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go), the `addrBlobArg` function (lines 50-71) performs a TXT lookup against the hostname and extracts the value prefixed with `tailcat=`. If no such record exists, the CLI aborts with an error.

The resolution happens before the client constructor runs, meaning **any** subcommand that accepts a token—including `ssh`, `ping`, and direct port forwarding—supports DNS-based token resolution automatically.

## Step-by-Step Guide to Publishing Tokens via DNS

### Generate a Stable Server Token

First, create a persistent token that survives server restarts. Without this step, the token embeds a random DERP region that changes on every restart, invalidating your DNS record.

```bash
$ tailcat genkey --fixed-region
tcVx2D4Yg9Y8cZKQ0pVYz8rUeGz3Y6k8...

```

This command stores the private key in `~/.config/tailcat/keys/default.private.json` and outputs the public token starting with `tc`. The `--fixed-region` flag pins the server to a specific DERP region, embedding this information directly into the token string.

### Configure the DNS TXT Record

Create a TXT record for your chosen hostname with the exact value `tailcat=<token>`. The prefix is mandatory; Tailcat strips it during resolution to extract the actual connection token.

For BIND-style zone files:

```text
my-server.example.com. 300 IN TXT "tailcat=tcVx2D4Yg9Y8cZKQ0pVYz8rUeGz3Y6k8..."

```

For Cloudflare or other managed DNS providers:
- **Name**: `my-server.example.com`
- **Content**: `tailcat=tcVx2D4Yg9Y8cZKQ0pVYz8rUeGz3Y6k8...`
- **TTL**: 300 seconds (5 minutes)

Set a modest TTL (300 seconds recommended) to enable rapid key rotation without waiting for long cache expirations.

### Connect Using the Hostname

Once propagated, use the hostname in place of the token for any Tailcat operation:

```bash

# Forward local traffic to server port 8080

$ tailcat my-server.example.com 8080

# SSH through the tunnel

$ tailcat ssh my-server.example.com

# Test connectivity

$ tailcat ping my-server.example.com

```

The CLI automatically resolves the TXT record, validates the `tailcat=` prefix, and establishes the connection using the extracted token.

## Technical Implementation Details

According to the Tailcat source code in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go), the `addrBlobArg` function handles the resolution logic. When the input argument contains a dot, the function queries DNS for TXT records, searches for the entry beginning with `tailcat=`, and returns the substring following the equals sign.

Tokens are case-sensitive, but DNS TXT values preserve the exact string content even when the hostname is lowercased (as browsers and DNS systems typically do). This ensures the client receives the correct token regardless of how the hostname is capitalized during lookup.

## Best Practices for DNS Token Management

- **Always pin the region**: Use `--fixed-region` or explicitly set `--region=<name>` when generating keys. This prevents token invalidation when the server restarts and selects a new random DERP region.
- **Keep TTL short**: A 300-second TTL balances performance with the ability to rotate compromised keys quickly.
- **Secure your DNS**: Since anyone with DNS visibility can read the TXT record, treat published tokens as public credentials and rely on Tailcat's underlying encryption for security, or restrict DNS access using split-horizon DNS for internal zones.

## Programmatic Usage in Go

The Tailcat Go library supports the same DNS resolution for programmatic connections. Use `tailcat.ConnBlob()` with a hostname to resolve the token via TXT lookup:

```go
package main

import (
	"context"
	"log"

	"github.com/tailscale/tailcat"
)

func main() {
	// Resolve hostname – internally performs DNS TXT lookup
	blob := tailcat.ConnBlob("my-server.example.com")

	// Load or generate client-side key
	clientKey := key.NewNode()

	cl := tailcat.NewClient(blob, clientKey)
	defer cl.Close()

	// Dial TCP port through the tunnel
	c, err := cl.DialTCPPort(context.Background(), 8080)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()
	
	// Use connection for read/write operations...
}

```

The `tailcat.ConnBlob` function implements the same logic as the CLI's `addrBlobArg`, performing the TXT record lookup and prefix stripping automatically.

## Summary

- Publish Tailcat tokens by creating DNS TXT records containing `tailcat=<token>` for your chosen hostname.
- Generate stable tokens using `tailcat genkey --fixed-region` to prevent invalidation on server restarts.
- The `addrBlobArg` function in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) (lines 50-71) handles automatic DNS resolution when hostnames contain dots.
- Set DNS TTL to approximately 300 seconds to facilitate rapid key rotation.
- All Tailcat subcommands support DNS-based tokens because resolution occurs before client construction.

## Frequently Asked Questions

### What is the exact format required for the DNS TXT record?

The TXT record must contain the literal string `tailcat=` followed immediately by the token, such as `tailcat=tcVx2D4Yg9Y8cZKQ0pVYz8rUeGz3Y6k8...`. Tailcat searches for this prefix specifically and extracts everything after the equals sign as the connection token. Records without this prefix are ignored.

### Why does my token become invalid after restarting the Tailcat server?

By default, Tailcat generates tokens that embed the currently selected DERP region. When the server restarts, it may select a different region, changing the token string. Use the `--fixed-region` flag when running `tailcat genkey` to embed a specific region in the token, ensuring it remains valid across restarts.

### Can I use DNS TXT records with any Tailcat subcommand?

Yes. Because the DNS lookup occurs in the shared `addrBlobArg` function before the client is constructed, any subcommand that accepts a token argument—including `ssh`, `ping`, and port forwarding—automatically supports hostname resolution via DNS TXT records.

### How does Tailcat handle case sensitivity in DNS lookups?

Connection tokens are case-sensitive, but DNS systems typically lowercase hostnames while preserving the case of TXT record values. Tailcat relies on this behavior: it queries the lowercased hostname but receives the exact token string from the TXT record, preserving uppercase and lowercase characters correctly.