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

> Learn how to publish Tailcat addresses using DNS TXT records. Connect via human-readable hostnames instead of raw Tailcat addresses with this complete guide.

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

---

**Tailcat resolves server addresses from public DNS TXT records by looking for a `tailcat=` prefix, enabling you to connect using human-readable hostnames instead of raw Tailcat addresses.**

Tailcat, Tailscale's peer-to-peer networking tool, supports **DNS-based address discovery** through specially formatted TXT records. This feature lets you publish a Tailcat address in DNS and connect using a simple hostname like `example.com`. This article explains exactly how the mechanism works, how to configure it, and the security considerations you must address.

## How DNS TXT Record Resolution Works in Tailcat

The DNS lookup logic resides in **[`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go)**. When you supply a hostname to any Tailcat sub-command, the client performs the following steps:

1. Calls `net.Resolver.LookupTXT` to fetch all TXT records for the hostname
2. Scans each returned string for the prefix `tailcat=`
3. Extracts and parses the remainder as a standard Tailcat address
4. Proceeds with the connection using the resolved address

The core lookup implementation appears at [lines 709-722](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L709-L722):

```go
// Simplified excerpt from cmd/tailcat/tailcat.go
txt, err := net.Resolver.LookupTXT(ctx, dnsName)
for _, t := range txt {
    if s, ok := strings.CutPrefix(t, "tailcat="); ok {
        // s contains the Tailcat address
        addr = s
        break
    }
}

```

If no `tailcat=` record exists, the client aborts with a fatal error ([lines 720-722](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L720-L722)).

## Creating a DNS TXT Record for Tailcat

To publish your Tailcat address, add a TXT record with the exact prefix `tailcat=` followed by your full address.

### BIND Zone File Example

```text
example.com.    IN  TXT  "tailcat=tc-1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a8b9c0d1e2f3g4h5i6j7k"

```

### Obtain Your Tailcat Address

Generate or retrieve your address using:

```bash

# Generate a new key and address

tailcat genkey --key=default

# Or start a server to see its address

tailcat serve ssh

# Server prints: tc-xxxxxxxxxxxxxxxx...

```

### Verify the Record

```bash
dig TXT example.com +short

# Expected output: "tailcat=tc-1a2b3c4d5e6f..."

```

## Using DNS-Resolved Addresses with Tailcat Commands

Once published, use the hostname with any command that accepts a `<tc-addr>` argument:

```bash

# SSH via hostname

tailcat ssh example.com

# List directory contents

tailcat ls example.com:/var/log

# Copy files

tailcat cp localfile.txt example.com:/remote/path/

```

The client transparently resolves `example.com` to the embedded Tailcat address before establishing the connection.

## Critical Security Considerations

The source code contains explicit warnings about the risks of public DNS TXT records. In **[`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go)** at [lines 1071-1076](https://github.com/tailscale/tailcat/blob/main/tailcat.go#L1071-L1076), a comment states:

> A TXT record is public, so the contained address should be considered secret unless additional authentication is used.

### Why This Matters

| Risk | Mitigation |
|------|-----------|
| **Address enumeration** — Anyone can query your DNS and discover the Tailcat address | Always require client authentication |
| **Unauthorized connections** — No-auth services exposed via DNS are easily found and abused | Never publish `--no-auth` addresses in DNS |
| **Traffic interception** — Passive observers see the address in DNS queries | Use in combination with Tailcat's built-in encryption |

### Recommended Server Configuration

```bash

# Require client authentication (safer for public DNS)

tailcat serve --allow=$(tailcat printpub) ssh

# NEVER do this with DNS-published addresses:

# tailcat serve --no-auth ssh  # DANGEROUS with public TXT records

```

The CLI prints warnings when a DNS-based address is detected without proper authentication ([lines 328-336](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L328-L336)).

## Programmatic DNS Resolution

Implement the same lookup logic in your own Go applications:

```go
package main

import (
    "context"
    "fmt"
    "net"
    "strings"
)

func resolveTailcatTXT(name string) (string, error) {
    ctx := context.Background()
    r := net.Resolver{}
    
    txts, err := r.LookupTXT(ctx, name)
    if err != nil {
        return "", err
    }
    
    for _, t := range txts {
        if s, ok := strings.CutPrefix(t, "tailcat="); ok {
            return s, nil
        }
    }
    
    return "", fmt.Errorf("no \"tailcat=\" TXT record found for %q", name)
}

```

This mirrors the implementation in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) at [lines 715-718](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L715-L718).

## Key Source Files

| File | Purpose |
|------|---------|
| [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) | DNS-TXT lookup implementation (`LookupTXT` call, prefix parsing) |
| [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) | Core address handling; security warnings for public TXT records |
| [`cmd/tailcat/ssh.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/ssh.go) | DNS hostname support for SSH connections |
| [`cmd/tailcat/ls.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/ls.go) | DNS hostname support for directory listing |

## Summary

- **Prefix requirement**: TXT records must start with `tailcat=` followed by the full address
- **Lookup location**: Resolution happens in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) using Go's `net.Resolver.LookupTXT`
- **Security imperative**: Public DNS means public addresses; always require client authentication via `--allow`
- **Broad compatibility**: Works with `ssh`, `ls`, `cp`, and any other command accepting `<tc-addr>`

## Frequently Asked Questions

### What happens if multiple TXT records contain `tailcat=`?

Tailcat uses the **first matching record found**. The implementation at [lines 715-718](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L715-L718) iterates through TXT strings and returns immediately upon finding a `tailcat=` prefix. Publish only one Tailcat address per hostname to avoid ambiguity.

### Can I use a CNAME record instead of publishing directly on my domain?

Yes. **CNAME records work transparently** because Tailcat resolves the canonical name before performing the TXT lookup. Point your alias to a host that carries the `tailcat=` record, or ensure both the CNAME and target have appropriate records.

### Is there a length limit for the Tailcat address in DNS?

Standard DNS TXT records support **255 characters per string** and multiple strings per record. Tailcat addresses typically fit within a single string. If your address exceeds 255 characters, your DNS provider should concatenate multiple strings automatically; Tailcat joins them before parsing.

### Does Tailcat cache DNS TXT record results?

**No built-in caching** is implemented in the current source code. Each Tailcat command performs a fresh `LookupTXT` call. For frequent connections, consider using the raw Tailcat address directly or implementing local caching in wrapper scripts.