Server configuration
TSDProxy utilizes the configuration file /config/tsdproxy.yaml for its settings.
The config file path can be overridden using the --config CLI flag:
tsdproxy --config /path/to/tsdproxy.yamlImportant
Environment variable configurations used in versions prior to v0.6.0 are
deprecated and will be removed in future releases. However, some legacy env vars are still read during initial config generation: DOCKER_HOST, TSDPROXY_HOSTNAME, TSDPROXY_AUTHKEY, TSDPROXY_AUTHKEYFILE, TSDPROXY_CONTROLURL, and TSDPROXY_DATADIR.
Sample Configuration File
Warning
Configuration files are case-sensitive.
defaultProxyProvider: default
defaultDNSProvider: cloudflare
defaultTLSProvider: acme
cleanupDNS: true
cleanupTLS: true
dnsProviders:
cloudflare:
provider: cloudflare
apiToken: "your-cloudflare-api-token"
tlsProviders:
acme:
provider: acme
email: "admin@example.com"
docker:
local: # Name of the Docker target provider
host: unix:///var/run/docker.sock # Docker socket or daemon address
targetHostname: host.docker.internal # hostname or IP of docker server (ex: host.docker.internal or 172.31.0.1)
defaultProxyProvider: default # Default proxy provider for this Docker server
autoRestart: true # (optional) Enable automatic re-resolution on backend failure (default: true)
healthCheckEnabled: true # (optional) Enable health probes (default: true)
healthCheckInterval: 30 # (optional) Seconds between health probes (default: 30)
healthCheckFailures: 3 # (optional) Consecutive failures before re-resolution (default: 3)
healthCheckCooldown: 0 # (optional) Fixed cooldown in seconds, 0 for exponential backoff (default: 0)
rateLimitEnabled: true # (optional) Enable rate limiting (default: true)
rateLimitRps: 100 # (optional) Max requests per second per client IP (1–10000, default: 100)
rateLimitBurst: 200 # (optional) Burst capacity per client IP (1–100000, default: 200)
allowContainerFunnel: false # (optional) Allow containers to self-enable Tailscale Funnel — public internet exposure (default: false)
allowTlsValidateDisable: false # (optional) Allow containers to disable TLS certificate validation (default: false)
lists:
critical: # Name of the target list provider
filename: /config/critical.yaml # Path to the proxy list file
defaultProxyProvider: tailscale1 # (Optional) Default proxy provider for this list
defaultProxyAccessLog: true # (Optional) Enable access logs for this list
autoRestart: true # (optional) Enable automatic re-resolution on backend failure (default: true)
healthCheckEnabled: true # (optional) Enable health probes (default: true)
healthCheckInterval: 30 # (optional) Seconds between health probes (default: 30)
healthCheckFailures: 3 # (optional) Consecutive failures before re-resolution (default: 3)
healthCheckCooldown: 0 # (optional) Fixed cooldown in seconds, 0 for exponential backoff (default: 0)
rateLimitEnabled: true # (optional) Enable rate limiting (default: true)
rateLimitRps: 100 # (optional) Max requests per second per client IP (1–10000, default: 100)
rateLimitBurst: 200 # (optional) Burst capacity per client IP (1–100000, default: 200)
tailscale:
providers:
default: # Name of the Tailscale provider
clientId: "your_client_id" # OAuth client ID (generated by Tailscale)
clientSecret: "your_client_secret" # OAuth client secret (generated by Tailscale)
# If clientId and clientSecret are defined, authKey
# and authKeyFile are ignored
authKey: "" # Tailscale auth key (alternative to OAuth)
authKeyFile: "" # Path to a file containing the auth key (ignores authKey if defined)
tags: "tag:example,tag:server" # Default tags for all containers using this provider
# Container-specific tags override these default tags
controlUrl: https://controlplane.tailscale.com # Override the default Tailscale control URL
preventDuplicates: false # Delete stale tailnet devices before creating new nodes (OAuth only)
maxCertConcurrency: 2 # Max parallel TLS cert generation requests (default: 2)
autoProvisionAcl: true # Auto-add missing tags and Funnel attribute in Tailscale ACL (default: false)
dataDir: /data/ # Tailscale data directory
http:
hostname: 127.0.0.1 # HTTP server hostname (changed from 0.0.0.0 — see breaking changes)
port: 8080 # HTTP server port
log:
level: info # Logging level (debug, info, warn, error, fatal, panic, trace)
json: false # Enable JSON logging (true/false)
proxyAccessLog: true # Enable container access logs (true/false)
apiKey: "" # API key for non-Tailscale authentication (optional)
apiKeyFile: "" # Path to a file containing the API key (optional)
admins: [] # Tailscale UserProfile.IDs authorized for admin actions (optional)
# Example: admins: ["12345" # alice@github, "67890" # bob@example.com]
adminAllowLocalhost: false # Permit localhost to bypass admin allowlist (for bootstrapping)
shutdownDrainSeconds: 0 # Seconds to keep listeners open after health goes not-ready before shutdown (0=disabled, max 300)Configuration Sections
tailscale Section
Configures Tailscale integration.
dataDir
Specifies the data directory used by Tailscale. Defaults to /data/.
providers
Defines multiple Tailscale providers. Each provider has the following options:
default: # Provider name
authKey: your-authkey # Tailscale auth key
authKeyFile: "" # Path to auth key file
controlUrl: https://controlplane.tailscale.com # Tailscale control URL
shared: false # share one Tailscale connection for multiple proxies
hostname: "" # hostname for shared server (required when shared: true)| Field | Type | Default | Description |
|---|---|---|---|
shared | boolean | false | When true, all proxies using this provider share a single Tailscale connection (one tsnet.Server). Proxies are routed by SNI (TLS Server Name Indication). Requires a custom domain on every proxy. See Shared Tailscale. Mutually exclusive with services. |
services | boolean | false | When true, uses Tailscale VIP Services API. Each proxy gets an auto-assigned FQDN from Tailscale. Requires hostname, clientId, and clientSecret. No custom domain or UDP support. See Services Mode. Mutually exclusive with shared. |
hostname | string | "" | The Tailscale machine name. Required when shared: true or services: true. |
autoApproveDevices | boolean | false | Auto-approve device registration in Tailscale. Requires OAuth credentials. Useful in services mode where new nodes need approval before they can start. |
clientSecretFile | string | "" | Path to a file containing the OAuth client secret. Overrides clientSecret if both are set. |
autoProvisionAcl | boolean | false | Automatically add missing ACL tags to tagOwners and configure Funnel attribute in your Tailscale ACL policy file. Requires OAuth credentials with policy:write scope. See ACL Auto-Provisioning. |
Example with multiple providers:
tailscale:
providers:
default:
authKey: your-authkey
authKeyFile: ""
controlUrl: https://controlplane.tailscale.com
server1:
authKey: authkey-server1
authKeyFile: ""
controlUrl: http://server1
differentkey:
authKey: authkey-with-different-tags
authKeyFile: ""
controlUrl: https://controlplane.tailscale.comThis example configures three Tailscale providers: default (default server),
server1 (different Tailscale server), and differentkey (default server with
a different auth key for specific tags).
Example with a shared provider:
tailscale:
providers:
shared:
clientId: "your_client_id"
clientSecret: "your_client_secret"
tags: "tag:example"
shared: true
hostname: "shared-proxy"Example with a services/VIP provider:
tailscale:
providers:
services:
clientId: "your_client_id"
clientSecret: "your_client_secret"
tags: "tag:example"
services: true
hostname: "shared-services"preventDuplicates
Controls how TSDProxy handles stale Tailscale devices when the data directory
has been lost. A boolean option (default: false):
| Value | Behavior |
|---|---|
false | Do not check for duplicate devices (default) |
true | Check and remove offline duplicates before creating a new node (requires OAuth) |
TSDProxy logs a warning and disables preventDuplicates if it is set to true
without OAuth credentials (clientId + clientSecret).
tailscale:
providers:
default:
preventDuplicates: trueWarning
This deletes devices from your tailnet. See Prevent Duplicate Machines for safety checks and requirements.
reconcileInterval
Periodic interval for device reconciliation. TSDProxy checks for stale devices matching the provider’s hostname and tags, and removes offline ones.
Set to a duration string (e.g. "5m", "1h"). Default "0" disables periodic
reconciliation. Reconciliation also runs once on startup regardless of this
setting.
tailscale:
providers:
default:
reconcileInterval: "5m"authRetry
Configures the retry policy when tsnet startup fails (e.g. due to transient network issues or temporary Tailscale API errors).
tailscale:
providers:
default:
authRetry:
enabled: true
maxAttempts: 3
initialBackoff: "2s"
maxBackoff: "30s"| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable or disable startup retry |
maxAttempts | integer | 3 | Maximum retry attempts (1–10) |
initialBackoff | duration | "2s" | Initial backoff before first retry |
maxBackoff | duration | "30s" | Maximum backoff cap (exponential growth) |
Tip
Non-recoverable errors (e.g. invalid tags, expired auth keys) are detected
early and stop retrying regardless of maxAttempts.
Tip
For more details, see the Tailscale page.
autoProvisionAcl
When set to true, TSDProxy automatically ensures the provider’s tags exist in
your Tailscale ACL tagOwners and configures the Funnel node attribute — all
via the Tailscale API. Defaults to false.
This saves you from manually editing your ACL policy file when adding new tags.
tailscale:
providers:
default:
clientId: "your_client_id"
clientSecret: "your_client_secret"
tags: "tag:my-service"
autoProvisionAcl: true| Value | Behavior |
|---|---|
false | Read-only check — logs a warning if tags are missing from ACL tagOwners (default) |
true | Auto-adds missing tags to tagOwners (owned by autogroup:admin) and ensures the Funnel attribute is present in nodeAttrs |
Important
Requires OAuth credentials (clientId + clientSecret). The OAuth client
must have the policy:write scope in addition to the device scopes.
Without this scope, TSDProxy cannot read or modify the ACL policy file.
Tip
Tags already present in your ACL are left untouched. autoProvisionAcl only
adds missing entries — it never removes or modifies existing ones.
autoRemoveConflicts
See Services Mode — Auto-remove conflicting devices for documentation.
docker Section
Configures Docker server connections. Multiple Docker servers can be defined:
local: # Docker provider name
host: unix:///var/run/docker.sock # Docker socket or daemon address
targetHostname: 172.31.0.1 # Docker server hostname or IP
defaultProxyProvider: default # Default proxy provider for this Docker serverExample with multiple Docker servers:
docker:
local:
host: unix:///var/run/docker.sock
defaultProxyProvider: default
srv1:
host: tcp://174.17.0.1:2376
targetHostname: 174.17.0.1
defaultProxyProvider: server1This example configures a local Docker server and a remote srv1 server.
Example with a remote Docker server over SSH:
docker:
local:
host: unix:///var/run/docker.sock
defaultProxyProvider: default
remote:
host: ssh://deploy@remote-node.example.com:22
sshPrivateKeyFile: /config/ssh/id_ed25519
sshKnownHostsFile: /config/ssh/known_hosts
targetHostname: 172.31.0.1
defaultProxyProvider: defaulthost
Specifies the Docker socket or daemon address. Defaults to unix:///var/run/docker.sock.
Supported schemes:
| Scheme | Description |
|---|---|
unix:// | Local Docker socket (default) |
tcp:// | Remote Docker daemon over TCP (plain or TLS) |
ssh:// | Remote Docker daemon over SSH tunnel (see below) |
SSH Tunnel
When using ssh:// as the host scheme, TSDProxy establishes a pure Go SSH
connection to the remote host and runs docker system dial-stdio to communicate
with the Docker daemon. No external SSH binary is needed — this works in the
standard scratch-based Docker image.
The SSH URL format is:
ssh://[user@]hostname[:port]- user defaults to
rootif not specified - port defaults to
22if not specified
SSH authentication requires one of:
| Field | Description |
|---|---|
sshPrivateKeyFile | Path to a PEM-encoded private key file (Ed25519, RSA, or ECDSA) |
sshPrivateKeyPassphrase | Passphrase for an encrypted private key (optional) |
sshAgentSocket | Path to an SSH agent UNIX socket (e.g. /run/ssh-agent.sock). Falls back to the SSH_AUTH_SOCK environment variable if not set |
Host key verification requires one of:
| Field | Description |
|---|---|
sshKnownHostsFile | Path to a known_hosts file for host key verification |
sshInsecureSkipHostCheck | Set to true to skip host key verification (insecure, use only for testing) |
Important
Either sshPrivateKeyFile or sshAgentSocket (or SSH_AUTH_SOCK env var) must
be configured. At least one auth method is required.
Either sshKnownHostsFile or sshInsecureSkipHostCheck: true must be configured.
When using sshKnownHostsFile, the file must exist at startup.
docker:
remote:
host: ssh://deploy@remote-node.example.com:22
sshPrivateKeyFile: /config/ssh/id_ed25519
sshKnownHostsFile: /config/ssh/known_hosts
targetHostname: host.docker.internal
defaultProxyProvider: defaultExample with SSH agent forwarding:
docker:
remote:
host: ssh://deploy@remote-node.example.com
sshAgentSocket: /run/ssh-agent.sock
sshKnownHostsFile: /config/ssh/known_hosts
defaultProxyProvider: defaultExample with insecure host key check (testing only):
docker:
remote:
host: ssh://root@remote-node.example.com
sshPrivateKeyFile: /config/ssh/id_ed25519
sshInsecureSkipHostCheck: true
defaultProxyProvider: defaultTo generate a known_hosts entry for your remote host:
ssh-keyscan -t ed25519 remote-node.example.com >> /config/ssh/known_hostsNote
The remote host must have Docker 18.09+ installed (required for docker system dial-stdio).
The SSH user must have permission to access the Docker socket on the remote host.
targetHostname
Specifies the IP address or DNS name of the Docker server. Used for connecting to containers in specific cases.
defaultProxyProvider
Specifies the default Tailscale provider (defined in the tailscale.providers
section) to use for containers on this Docker server. Container-specific labels
override this setting.
tryDockerInternalNetwork
Defaults to false. When set to true, containers default to using
auto-detection of the target URL via connectivity probing. This sets the default
for the per-container tsdproxy.autodetect label. Individual containers can
still override this with the tsdproxy.autodetect label.
docker:
local:
host: unix:///var/run/docker.sock
targetHostname: host.docker.internal
defaultProxyProvider: default
tryDockerInternalNetwork: trueautoRestart
Defaults to true. When enabled, TSDProxy monitors each proxy’s backend health
and automatically re-resolves the target when consecutive health check failures
reach the configured threshold. See Health Check for details.
docker:
local:
host: unix:///var/run/docker.sock
autoRestart: true
healthCheckEnabled: true
healthCheckInterval: 30
healthCheckFailures: 3
healthCheckCooldown: 0healthCheckEnabled
Defaults to true. When set to false, health probes are completely disabled for
all containers using this provider. Individual containers can override this with
the tsdproxy.health_check_enabled Docker label. See Health Check for details.
healthCheckInterval
Seconds between health probes. Must be at least 1. Defaults to 30.
healthCheckFailures
Number of consecutive health check failures before triggering target re-resolution.
Must be at least 1. Defaults to 3.
healthCheckCooldown
Fixed cooldown in seconds between re-resolution attempts while the target remains
unhealthy. Set to 0 (default) to use exponential backoff instead.
allowContainerFunnel
Defaults to false. When set to true, containers can enable
Tailscale Funnel (public internet exposure)
via the tailscale_funnel port option or the legacy tsdproxy.funnel label.
When false (default), any container requesting Funnel will be silently
ignored and a warning logged. This prevents containers from self-granting
public internet exposure without operator approval.
docker:
local:
host: unix:///var/run/docker.sock
allowContainerFunnel: trueCaution
Funnel bypasses Tailscale authentication. Only enable this if you trust all container owners on this Docker host. See Funnel Security.
allowTlsValidateDisable
Defaults to false. When set to true, containers can disable TLS certificate
validation on their proxied connections via the no_tlsvalidate port option or
the legacy tsdproxy.tlsvalidate=false label.
When false (default), any container requesting TLS validation disable will be
silently ignored and a warning logged. This prevents containers from
unilaterally weakening TLS security.
docker:
local:
host: unix:///var/run/docker.sock
allowTlsValidateDisable: trueWarning
Disabling TLS validation allows man-in-the-middle attacks on the connection between TSDProxy and the container. Only enable this if you have containers with self-signed certificates and understand the risk.
rateLimitEnabled
Defaults to true. When enabled, per-IP rate limiting is applied to each HTTP
proxy port to protect backends from abusive clients. Individual containers can
override this with the tsdproxy.ratelimit.enabled Docker label.
rateLimitRps
Max requests per second per client IP. Must be between 1 and 10000. Defaults to
100. Individual containers can override this with the tsdproxy.ratelimit.rps
Docker label.
rateLimitBurst
Burst capacity per client IP. Allows short bursts above the RPS limit. Must be
between 1 and 100000. Defaults to 200. Individual containers can override this
with the tsdproxy.ratelimit.burst Docker label.
Tip
Rate limiting is per-client-IP with a token bucket algorithm. Each connecting IP gets its own bucket. The limiter tracks up to 4096 clients — the oldest idle client is evicted first when the limit is reached.
dnsProviders Section
Configures DNS providers for custom domain support. Each provider manages CNAME records for a specific DNS zone. See Custom Domains for the full guide.
Cloudflare provider
dnsProviders:
cloudflare:
provider: cloudflare
apiToken: "your-cloudflare-api-token"| Field | Required | Description |
|---|---|---|
provider | yes | Must be cloudflare or magicdns |
apiToken | yes | Cloudflare API token with Zone:DNS:Edit and Zone:Zone:Read |
apiTokenFile | no | Path to a file containing the API token (overrides apiToken) |
defaultDNSProvider
The default DNS provider used when a container does not specify
tsdproxy.dnsprovider. Must match a provider name defined in dnsProviders.
defaultDNSProvider: cloudflaretlsProviders Section
Configures TLS providers for custom domain certificate provisioning.
Tailscale provider
Wraps Tailscale’s built-in CertPair for .ts.net domains. The config entry
name can be anything — TSDProxy resolves the provider type from the provider
field.
tlsProviders:
my-tailscale:
provider: tailscale| Field | Required | Description |
|---|---|---|
provider | yes | Must be tailscale |
ACME provider
Uses certmagic with DNS-01 challenge via the configured DNS provider.
tlsProviders:
acme:
provider: acme
email: "admin@example.com"| Field | Required | Default | Description |
|---|---|---|---|
provider | yes | - | Must be acme or tailscale |
email | yes | - | Email for ACME account registration |
ca | no | Let’s Encrypt Production | ACME directory URL |
certStorage | no | data directory | Path to store certificates |
defaultTLSProvider
The default TLS provider used when a container does not specify
tsdproxy.tlsprovider. Must match a provider name defined in tlsProviders,
or be a tailscale-type provider (resolved by provider field).
defaultTLSProvider: acmecleanupDNS
When set to true, TSDProxy removes DNS CNAME records when a proxy stops.
Defaults to true.
cleanupDNS: truecleanupTLS
When set to true, TSDProxy removes TLS certificates when a proxy stops.
Defaults to true. Set to false to preserve certs across restarts (e.g.
when using externally-managed certificates, or to avoid Let’s Encrypt rate
limits). Independent from cleanupDNS — each flag controls its own resource.
cleanupTLS: truehttp Section
Configures the built-in HTTP server that serves the dashboard and health endpoints.
hostname
The bind address for the HTTP server. Defaults to 127.0.0.1 (localhost only).
When running inside Docker, the hostname is automatically overridden to 0.0.0.0
so that port-mapped access (-p 8080:8080) works without manual configuration.
Warning
The default changed from 0.0.0.0 to 127.0.0.1 in v2.2.0 for security.
If you need the dashboard accessible on all interfaces outside Docker,
set hostname: 0.0.0.0 explicitly.
port
The port for the HTTP server. Defaults to 8080.
shutdownDrainSeconds
Seconds to keep the HTTP listeners open after the health endpoint starts
returning not-ready (/health/ready/ → 503) but before shutting down the
server. This gives upstream load balancers time to see the failing health
probe and stop routing traffic to this instance. Defaults to 0 (disabled —
shutdown proceeds immediately). Range: 0–300 seconds.
The shutdown sequence is:
- Health endpoint set to not-ready (
/health/ready/returns 503) - Wait
shutdownDrainSecondsfor load balancer convergence - Graceful HTTP server shutdown
- Proxy teardown
shutdownDrainSeconds: 30Tip
Set this when running TSDProxy behind a load balancer (e.g. Docker Swarm
or a reverse proxy health-checking /health/ready/).
log Section
level
Defines the logging level. Options are debug, info, warn, error, fatal, panic, or trace.
The default is info.
json
Enables JSON-formatted logging when set to true. Defaults to false.
proxyAccessLog
Enables access logging for proxied requests. Defaults to true. Can be
overridden per-container with the tsdproxy.containeraccesslog label or
per-list with defaultProxyAccessLog.
API Key Authentication
In addition to Tailscale identity, TSDProxy supports API key authentication for non-Tailscale clients (scripts, CI pipelines, monitoring tools).
apiKey: "your-secret-api-key"Or via a file:
apiKeyFile: "/run/secrets/tsdproxy-api-key"When configured, include the key in API requests:
curl -H "Authorization: Bearer your-secret-api-key" http://localhost:8080/api/v1/proxiesNote
API keys grant full admin access to all endpoints. Treat them like passwords —
store in secrets managers, use apiKeyFile with Docker secrets, and rotate regularly.
apiKey
A static API key for authenticating non-Tailscale clients. If both apiKey and
apiKeyFile are set, apiKeyFile takes precedence.
apiKeyFile
Path to a file containing the API key. The file is read at startup and must contain exactly one line with the key.
telemetry Section
Configures OpenTelemetry export for distributed tracing. Disabled by default.
telemetry:
enabled: false
endpoint: "localhost:4317"
insecure: falseenabled
Enable or disable OpenTelemetry tracing export. Defaults to false.
endpoint
The OTLP gRPC endpoint to send traces to. Defaults to localhost:4317.
insecure
When true, connects to the telemetry endpoint without TLS. Defaults to false.
webhooks Section
Configures webhook notifications for proxy status changes. See Notifications for detailed recipes.
webhooks:
- url: "https://ntfy.sh/my-topic" # Target URL (required)
type: ntfy # ntfy | discord | slack | generic (default)
events: # Optional — send all events if omitted
- Running
- Stopped
- Error
headers: # Optional custom HTTP headers
Authorization: "Bearer token123"Multiple webhooks can be configured simultaneously:
webhooks:
- url: "https://ntfy.sh/alerts"
type: ntfy
events: [Running, Error, Stopped]
- url: "https://discord.com/api/webhooks/123/abc"
type: discord
events: [Error]url
The HTTP endpoint to send notifications to. Required.
type
The message format. Supported values:
| Type | Format | Content-Type |
|---|---|---|
ntfy | Plain text body | text/plain |
discord | Discord embed JSON | application/json |
slack | Slack Block Kit JSON | application/json |
generic (default) | JSON payload | application/json |
events
Optional list of status names that trigger a notification. Names are case-insensitive. Available events: Initializing, Starting, Authenticating, Running, Stopping, Stopped, Error, Paused. If omitted, all status changes are sent.
headers
Optional map of HTTP headers added to every request. Custom headers override defaults (including Content-Type).
Admin Allowlist
Controls access to sensitive dashboard actions (restart, pause, resume, reauth).
admins:
- "12345" # alice@github
- "67890" # bob@example.com
adminAllowLocalhost: falseadmins
A list of Tailscale UserProfile.ID values authorized to use admin endpoints.
All tailnet users can view proxy status and preferences (viewer role). Only
users in this list (or authenticated via API key) can perform admin actions.
Use /api/whoami through a Tailscale connection to discover your ID.
adminAllowLocalhost
When true, requests originating from loopback (127.0.0.0/8, ::1) or
RFC 1918 private networks (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
bypass the admin allowlist. Defaults to false. Intended only for
bootstrapping — enable temporarily, then disable once your ID is in the list.
The private-network check enables adminAllowLocalhost to work with Docker
port mapping, where requests arrive from the Docker bridge gateway
(e.g. 172.17.0.1) rather than 127.0.0.1.
Warning
With adminAllowLocalhost: true, any process on the host or Docker
network can call admin endpoints without authentication.
Configuration File Lifecycle
Auto-Generation
On first run, TSDProxy generates a default config automatically.
Live Reload
- Proxy list files reload automatically on changes. No restart needed.
- Main config changes require a restart.
Validation
Config files are strictly validated. Unknown keys or invalid values cause load failures.