Tailscale Integration
Serve your application on a Tailscale network, expose it to the public internet via Funnel, or run as a standalone node with no daemon — all from elide orb. This page covers the three Tailscale modes, DERP relay co-hosting, MagicDNS, multi-network orchestration, and Headscale support.
---
Three modes
Daemon mode (--tailscale)
Connect to a running tailscaled daemon. Elide binds to the node's Tailscale IP, provisions a browser-trusted TLS certificate from the Tailscale CA, registers MagicDNS, and identifies incoming peers via whois.
elide orb --config server.pkl --tailscaleWhat happens:
- Detects the node's Tailscale IP and DNS name (e.g.
100.64.0.42,my-node.tail1234.ts.net) - Provisions a TLS certificate automatically — trusted by all tailnet devices, no browser warnings
- Registers MagicDNS so Elide resolves other tailnet peers by name
- Enables whois-based peer identification for the
TailscaleAuthmiddleware (auto-injected)
tailscaled running and enrolled (tailscale up). Unix only (Linux, macOS).
Funnel mode (--funnel)
Expose the server to the public internet through Tailscale's relay infrastructure. Anyone can reach it at https://your-node.tail1234.ts.net.
elide orb --config server.pkl --funnel--funnel implies --tailscale. Elide configures the Tailscale serve config via the local API at startup and tears it down on shutdown.
TailscaleAuth middleware is automatically skipped because whois cannot identify external callers.Standalone mode (--tailscale-direct)
Run as a self-contained Tailscale node without tailscaled. Elide embeds the full TS2021 control plane client (Noise IK encryption), WireGuard data plane (userspace via boringtun), DERP relay client, and DISCO endpoint discovery.
elide orb --config server.pkl --tailscale-direct --tailscale-auth-key tskey-auth-...What happens:
- Authenticates to the Tailscale coordination server using the pre-auth key
- Polls the network map to discover peers and IP assignments
- Registers MagicDNS entries for all tailnet peers
- Spawns the WireGuard data plane
- Bridges inbound TCP connections from the tunnel to the HTTP server
- Starts a background thread for continuous map polling, STUN discovery, and MagicDNS updates
TAILSCALE_AUTH_KEY and omit —tailscale-auth-key.When to use standalone mode
- Containers and serverless — single binary joins the tailnet, no sidecar daemon
- Edge deployments — minimal footprint, no privileged socket access
- Ephemeral workloads — pre-auth key via env var, tear down when done
- CI/CD — expose preview environments on the tailnet without system-wide Tailscale install
- Air-gapped / self-hosted — point
controlUrlat a Headscale instance
Generating a pre-auth key
Via the Tailscale admin console or the CLI:
# Reusable, ephemeral, tagged key (recommended for automation)
tailscale api keys create --reusable --ephemeral --tags tag:elide-serverStandalone mode comparison
| Feature | Daemon mode | Standalone mode |
|---|---|---|
| Tailscale IP assignment | Yes | Yes |
| MagicDNS for peer names | Yes | Yes |
| Network map polling | Yes | Yes |
| WireGuard tunnel (data plane) | Yes | Yes (userspace via boringtun) |
| DISCO NAT traversal | Handled by tailscaled | Built-in state machine |
| STUN endpoint discovery | Handled by tailscaled | Periodic via background thread |
| Browser-trusted TLS (via Tailscale) | Yes | No (use —auto-https or PKL tls) |
| Tailscale Funnel | Yes | No |
| Whois peer identification | Yes | Not yet |
| ACL packet filtering | Handled by tailscaled | Parsed from netmap (not yet applied) |
| TUN device creation | No (tailscaled owns tun) | Yes (—tun flag) |
DERP relay
DERP (Designated Encrypted Relay for Packets) relays provide fallback connectivity when direct peer-to-peer WireGuard paths cannot be established due to NAT. Elide can co-host a DERP relay alongside the HTTP server:
elide orb --config server.pkl --tailscale --derpThe relay listens on port 3340 by default and shuts down gracefully on exit. Override the port:
elide orb --config server.pkl --tailscale --derp --derp-port 4000DERP can also be configured in PKL for standalone mode:
networks {
["corp"] {
tailscale {
direct = true
authKey = env("TS_KEY")
derp {
server = true
serverPort = 3478
customRelays { "https://derp.internal.example.com:443" }
}
}
}
}For a standalone DERP relay without the HTTP server, use elide tun derp:
elide tun derp --port 3340---
MagicDNS
When Tailscale is active (any mode), Elide builds a DNS resolver from the Tailscale peer table and registers it as the process-wide resolver. All DNS lookups within the server — HTTP client requests, TLS connections, STUN, DERP — resolve tailnet names without system DNS.
Resolution order:
1. If the hostname matches a known tailnet peer (e.g. other-node.tail1234.ts.net), resolve directly from the cached peer table.
2. Otherwise, fall back to system DNS.
3. IP literals bypass DNS entirely.
The resolver is thread-safe and registered once at startup. In standalone mode, the background control thread updates the peer table on each netmap poll, so new peers become resolvable within seconds.
---
Multi-network orchestration
elide orb supports multiple named network attachments in a single process. Each network has its own data plane, routing rules, and identity.
amends "elide:serve/ElideServer.pkl"
networks {
// Production tailnet with standalone mode
["production"] {
tailscale {
direct = true
authKey = env("TS_PROD_KEY")
disco { heartbeatInterval = 2.s; reprobeInterval = 25.s }
dataPlane { mtu = 1280; bufferPoolSize = 256 }
}
tunnel {
bridgeInbound = true
bridgeOutbound = true
listenPort = 443
}
}
// Office VPN via raw WireGuard
["office-vpn"] {
wireguard { configFile = "./wg-office.conf" }
tunnel { bridgeOutbound = true }
}
// Staging tailnet via Headscale
["staging"] {
tailscale {
direct = true
authKey = env("TS_STAGING_KEY")
controlUrl = "https:<<>>
}
tunnel { bridgeInbound = true }
}
}
servers {
[" api.example.com"] {
handler = new ReverseProxy {
<<>>
upstreams { " office-backend.internal:8080" }
}
}
}Within a single network entry, tailscale and wireguard are mutually exclusive.
Tunnel bridging
Thetunnel block controls how each network's traffic is bridged to the HTTP server:
| Property | Default | Description |
|---|---|---|
bridgeInbound | true | Accept inbound TCP connections from the tunnel. Netstack listens on listenPort and bridges to the HTTP server. |
bridgeOutbound | true | Route outbound HTTP connections through the tunnel when the destination matches tailnetRanges. |
listenPort | 443 | Port netstack listens on for inbound bridging. |
tailnetRanges | 100.64.0.0/10, fd7a:115c:a1e0::/48 | CIDR ranges routed through the tunnel for outbound connections. |
DISCO endpoint discovery
Standalone mode includes the full DISCO state machine for NAT traversal. DISCO probes candidate endpoints and selects the lowest-latency direct path. When direct connectivity is not possible, traffic falls back to DERP relay.
State transitions:
Unknown --> Probing --> Direct --> Degraded --> RelayOnlyA peer transitions from Direct to Degraded after consecutive ping timeouts, then from Degraded to RelayOnly if no successful pong arrives within the degradation timeout.
Tuning via PKL:
tailscale {
disco {
heartbeatInterval = 2.s // NAT pinhole keepalive
reprobeInterval = 25.s // full re-probe cycle
probeTimeout = 5.s // individual ping timeout
degradedAfterFailures = 3 // consecutive failures before degraded
degradedTimeout = 10.s // time in degraded before relay fallback
}
}---
Headscale support
For self-hosted Tailscale, point controlUrl at your Headscale instance:
networks {
["self-hosted"] {
tailscale {
direct = true
authKey = env("HEADSCALE_AUTH_KEY")
controlUrl = "https://headscale.internal.example.com"
}
}
}Everything else — MagicDNS, DISCO, WireGuard data plane, DERP — works identically.
---
Zero-downtime upgrades with Tailscale
When elide orb runs with standalone mode and a zero-downtime upgrade is triggered, the WireGuard network state is included in the handoff to the sidecar. The new process inherits listener file descriptors and re-authenticates with the coordination server to re-establish the data plane. The mechanism is identical to the non-Tailscale case described in the orb overview.
---
Deployment examples
Serve on your tailnet (daemon mode)
elide orb --config server.pkl --tailscaleExpose to the public internet via Funnel
elide orb --config server.pkl --funnelStandalone node in a container
FROM ghcr.io/elide-dev/elide:latest
COPY server.pkl /etc/elide/server.pkl
ENV TAILSCALE_AUTH_KEY=tskey-auth-...
CMD ["elide", "orb", "--config", "/etc/elide/server.pkl", "--tailscale-direct", "--no-tui"]Standalone node with DERP relay
elide orb --config server.pkl \
--tailscale-direct \
--tailscale-auth-key tskey-auth-... \
--derp \
--derp-port 3340 \
--no-tuiMulti-network: tailnet + WireGuard VPN
amends "elide:serve/ElideServer.pkl"
networks {
["tailnet"] {
tailscale { direct = true; authKey = env("TS_KEY") }
tunnel { bridgeInbound = true }
}
["datacenter"] {
wireguard { configFile = "/etc/wireguard/dc.conf" }
tunnel { bridgeOutbound = true }
}
}
servers {
["gateway.example.com"] {
routes {
new {
match { path = "/api/**" }
handler = new ReverseProxy {
// Backend in datacenter, reached via WireGuard
upstreams { "10.200.0.5:8080" }
}
}
}
handler = new StaticFiles { root = "./dashboard" }
}
}See also
elide orb— overview, capabilities, and quick deploy- Orb configuration guide — real-world config patterns
elide tun— standalone WireGuard tunnel, Tailscale connectivity, DERP relay