How to Configure a Custom DERP Map URL in Tailcat

Yes, Tailcat supports custom DERP map URLs via the DERPMapURL option in the Go library or the -derpmap-url CLI flag, allowing you to override the default https://tailcat.dev/derpmap.json endpoint.

Tailcat is an open-source networking library developed by Tailscale that enables secure NAT traversal using DERP (Designated Encrypted Relay for Packets) relays. While Tailcat ships with a default DERP map hosted at tailcat.dev, production deployments often require self-hosted or geographically optimized relay infrastructure, making the ability to specify a Tailcat custom DERP map URL essential for advanced network configurations.

Default DERP Map Behavior

By default, Tailcat automatically fetches its relay configuration from a centralized endpoint. In tailcat.go, the constant DefaultDERPMapURL is defined as https://tailcat.dev/derpmap.json (lines 90-93), which ConnInfo.Expand uses when no override is provided.

When FetchDERPMap is invoked without custom options, it retrieves the JSON map from this default URL, parses the available DERP regions, and caches the result for subsequent operations. This default behavior requires no configuration but assumes outbound HTTPS access to Tailscale's infrastructure.

Setting a Custom DERP Map URL Programmatically

To override the default endpoint in Go applications, pass tailcat.DERPMapURL() as an option to either ConnInfo.Expand or FetchDERPMap. The implementation in tailcat.go (lines 790-795) inspects this option before falling back to DefaultDERPMapURL.

import (
    "context"
    "log"

    "github.com/tailscale/tailcat"
)

func main() {
    ctx := context.Background()
    pk := tailcat.NewPrivateKey()

    // Override the default DERP map source
    err := pk.Public.Expand(
        ctx,
        tailcat.ExpandForServer,
        tailcat.DERPMapURL("https://my.example.com/derpmap.json"),
    )
    if err != nil {
        log.Fatalf("failed to expand ConnInfo: %v", err)
    }
    // Server now uses DERP regions from your custom map
}

The FetchDERPMap function respects the same option pattern, allowing low-level control over relay discovery when building custom Tailcat integrations.

Using a Custom DERP Map URL via CLI

The tailcat binary exposes the -derpmap-url flag, defined in cmd/tailcat/tailcat.go (lines 58-59), which forwards the value to the library's DERPMapURL option.


# Start a server with custom DERP infrastructure

tailcat server -key mykey -derpmap-url https://corp.example.com/derpmap.json

# Connect a client using the same map

tailcat client -key mykey -derpmap-url https://corp.example.com/derpmap.json <server-blob>

Both server and client processes must reference the same DERP map to ensure consistent relay selection and key discovery.

Web Assembly and Browser Usage

The Tailcat web client supports custom DERP maps through the same configuration mechanism. In cmd/tailcat-web/tailcat-web.go, the -derpmap-url flag is processed identically to the CLI version.

For browser-based applications using the WASM build, the JavaScript API exposes the DERPMapURL option:

import * as tailcat from "https://cdn.jsdelivr.net/npm/@tailscale/tailcat";

const ci = new tailcat.ConnInfo();
await ci.expand(
    tailcat.ExpandForServer, 
    tailcat.DERPMapURL("https://my.example.com/derpmap.json")
);

The WASM implementation in web/main_js.go bridges these options to the underlying Go runtime, ensuring feature parity between native and browser environments.

Caching and Map Persistence

When using a custom DERP map URL, Tailcat maintains the same caching semantics as the default configuration. The library stores fetched maps in-memory or delegates to a user-provided DERPMapCache implementation, preventing redundant HTTP requests during connection retries.

This caching layer applies regardless of whether the source is https://tailcat.dev/derpmap.json or a private endpoint. Applications requiring offline capability can pre-populate the cache before calling ConnInfo.Expand, ensuring DERP functionality remains available without network access to the map URL.

Summary

  • Default endpoint: Tailcat uses https://tailcat.dev/derpmap.json defined in tailcat.go when no custom URL is specified.
  • Programmatic override: Pass tailcat.DERPMapURL("https://...") to ConnInfo.Expand or FetchDERPMap (lines 790-795).
  • CLI flag: Use -derpmap-url in both cmd/tailcat/tailcat.go and cmd/tailcat-web/tailcat-web.go.
  • Cross-platform: Works identically in native Go code, CLI binaries, and WASM browser environments.
  • Caching: Custom maps are cached using the same mechanisms as defaults, with optional DERPMapCache injection.

Frequently Asked Questions

How do I specify a custom DERP map URL in Tailcat?

You can specify a custom URL either programmatically by passing tailcat.DERPMapURL("https://your-url") to ConnInfo.Expand, or via the command line using the -derpmap-url https://your-url flag. Both methods override the default https://tailcat.dev/derpmap.json endpoint defined in the source code.

What is the format of the DERP map JSON file?

The DERP map must be a valid JSON object defining DERP regions, each containing a unique region ID, hostnames, and public keys for relay nodes. The schema matches the tailcfg.DERPMap structure used throughout the Tailscale ecosystem, ensuring compatibility with existing tooling.

Does Tailcat cache custom DERP maps differently than the default?

No, Tailcat applies identical caching logic to custom and default DERP maps. Both use the in-memory cache or a user-provided DERPMapCache implementation. The cache key includes the URL, allowing simultaneous use of multiple distinct maps without collision.

Is the custom DERP map URL supported in the WebAssembly build?

Yes, the WASM client fully supports custom DERP map URLs through the JavaScript DERPMapURL option or the -derpmap-url flag when running tailcat-web. The implementation in web/main_js.go ensures the custom URL propagates to the underlying Go runtime.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →