# How to Generate Persistent WireGuard Keys for Tailcat

> Learn how to generate persistent WireGuard keys for Tailcat. Discover how Tailcat bundles private keys and DERP region info for reuse, ensuring secure connections.

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

---

**Tailcat generates persistent WireGuard identities by bundling a node's private key with DERP region information into a `PrivateKey` struct, serializing it to JSON, and storing it in the user's config directory for reuse across program restarts.**

Tailcat, the peer-to-peer connectivity tool from the `tailscale/tailcat` repository, relies on persistent WireGuard keys to maintain stable node identities. Unlike ephemeral keys that change on every restart, persistent keys allow firewall rules and ACLs to remain valid across server reboots and application restarts.

## Understanding Tailcat's Key Architecture

At the core of Tailcat's identity system is the `PrivateKey` struct defined in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go). This structure combines a WireGuard private key with connection metadata required for DERP (Designated Encrypted Relay for Packets) region announcement.

### The PrivateKey Structure

The `PrivateKey` type encapsulates a `key.NodePrivate` alongside a `ConnInfo` field that stores DERP region details. The constructor `tailcat.NewPrivateKey` ([source](https://github.com/tailscale/tailcat/blob/main/tailcat.go#L194-L202)) generates a fresh cryptographic key pair while leaving the region configuration flexible for later assignment.

```go
// Simplified representation of the key generation
pk := tailcat.NewPrivateKey()
// pk now contains a new WireGuard key pair and empty connection info

```

### Storage Location and Format

Persistent keys are serialized as JSON and written to disk according to the XDG Base Directory specification. The `keyPath` helper in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) ([source](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L70-L79)) resolves the storage path to:

```

$XDG_CONFIG_HOME/tailcat/keys/<name>.private.json

```

If `XDG_CONFIG_HOME` is unset, Tailcat defaults to `~/.config/tailcat/keys/`. The JSON format preserves both the private key material and the public connection string, enabling the same node identity to be reloaded on subsequent invocations.

## Generating Keys via the CLI

The `tailcat genkey` command provides a complete interface for creating and managing persistent keys. The implementation in [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go) ([source](https://github.com/tailscale/tailcat/blob/main/cmd/tailcat/tailcat.go#L981-L1024)) handles key generation, region selection, and file I/O.

### Command Flags

- **`--key=<name>`**: Specifies the filename for the key (stored as `<name>.private.json`).
- **`--client`**: Generates a client-only key without DERP region assignment, printing the public key for server allow-lists.
- **`--region=<id|code|substring>`**: Pins a specific DERP region (or `auto` for dynamic selection).
- **`--fixed-region`**: Resolves the nearest region immediately and bakes it into the key, skipping future latency probes.
- **`--embed-derp-map`**: Includes DERP map nodes directly in the key for faster bootstrapping.
- **`--force`**: Overwrites existing key files without prompting.
- **`--delete`**: Removes a previously saved key file.
- **`--list`**: Displays all saved key names in the config directory.

### Server Key Generation

To create a persistent server key with automatic region selection:

```bash
tailcat genkey --key=my-server

```

This writes to `$XDG_CONFIG_HOME/tailcat/keys/my-server.private.json` and prints the derived public key for peer configuration.

### Client Key Generation

For client-only nodes that connect to an existing server:

```bash
tailcat genkey --client

```

This creates [`client-default.private.json`](https://github.com/tailscale/tailcat/blob/main/client-default.private.json) and outputs the public key to stdout, which you can paste into server `--allow` lists.

## Programmatic Key Generation in Go

For tools that embed Tailcat functionality, generate keys programmatically using the same primitives as the CLI.

### Creating and Saving a Key

The following example generates a key, assigns a specific DERP region, and persists it to disk:

```go
import (
    "encoding/json"
    "os"
    "path/filepath"

    "github.com/tailscale/tailcat"
)

func makePersistentKey(name string, regionID int) error {
    // Generate fresh WireGuard identity
    pk := tailcat.NewPrivateKey()
    
    // Assign DERP region (e.g., 1 for US-West)
    pk.Public.RegionID = regionID
    
    // Serialize to JSON
    data, err := json.MarshalIndent(pk, "", "\t")
    if err != nil {
        return err
    }
    
    // Ensure directory exists with restricted permissions
    cfgDir, _ := os.UserConfigDir()
    path := filepath.Join(cfgDir, "tailcat", "keys", name+".private.json")
    
    if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
        return err
    }
    
    // Write with 0600 permissions (owner read/write only)
    return os.WriteFile(path, data, 0600)
}

```

### Loading a Persisted Key

To reuse an existing identity across application restarts:

```go
func loadPersistentKey(name string) (*tailcat.PrivateKey, error) {
    cfgDir, err := os.UserConfigDir()
    if err != nil {
        return nil, err
    }
    
    path := filepath.Join(cfgDir, "tailcat", "keys", name+".private.json")
    raw, err := os.ReadFile(path)
    if err != nil {
        return nil, err
    }
    
    var pk tailcat.PrivateKey
    if err := json.Unmarshal(raw, &pk); err != nil {
        return nil, err
    }
    
    return &pk, nil
}

```

## Key Persistence and Reuse

Because Tailcat stores the complete `PrivateKey` struct—including the `key.NodePrivate` material—on disk, the same node public key is presented to peers every time the process starts. This persistence mechanism is critical for maintaining stable peer relationships: servers can maintain consistent `--allow` lists, and clients avoid triggering new handshake requirements on every connection attempt.

The JSON storage format also separates configuration from code, allowing operators to ship pre-generated keys to new instances or back up identity files for disaster recovery.

## Summary

- **Persistent storage**: Tailcat saves WireGuard keys to `$XDG_CONFIG_HOME/tailcat/keys/<name>.private.json` as JSON.
- **Core API**: Use `tailcat.NewPrivateKey()` in [`tailcat.go`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) to generate fresh cryptograhic identities.
- **CLI workflow**: The `tailcat genkey` command handles generation, region pinning, and file persistence automatically.
- **Security**: Key files are created with `0600` permissions and stored outside the repository in the user's config directory.
- **Stability**: Persistent keys enable stable ACLs and peer allow-lists across application restarts.

## Frequently Asked Questions

### Where are Tailcat WireGuard keys stored on disk?

Tailcat stores persistent keys in JSON format under `$XDG_CONFIG_HOME/tailcat/keys/<name>.private.json`. If the XDG environment variable is unset, the fallback location is `~/.config/tailcat/keys/`. Each key file contains the private key material and connection metadata required to reestablish the same node identity.

### How do I regenerate or rotate an existing Tailcat key?

Use the `--force` flag with the `genkey` command to overwrite an existing key file. For example: `tailcat genkey --key=my-server --force`. To completely remove a key from disk, use `tailcat genkey --key=<name> --delete`.

### What is the difference between client and server keys in Tailcat?

Server keys include DERP region information in their `ConnInfo` field, allowing the node to announce itself as a relay endpoint. Client keys, generated with the `--client` flag, omit this region data and are designed solely for initiating outbound connections to servers. Client keys print their public key to stdout for easy addition to server allow-lists.

### Can I use the same Tailcat key file on multiple machines?

No. Each Tailcat key represents a unique WireGuard node identity. Reusing the same [`private.json`](https://github.com/tailscale/tailcat/blob/main/private.json) file on multiple machines would cause key collisions and routing conflicts. Generate distinct keys for each node using `tailcat genkey --key=<unique-name>` and authorize each public key independently on your servers.