Skip to content

Exposure Modes

Exposure mode controls how the daemon is reachable over the network. Most setups stay on local mode. You only need a tunnel mode when something external (GitHub webhooks, CI, a second machine) needs to reach the daemon.

ModeConfig valueRequired processReachabilityRisk
LocallocalNoneLoopback onlyLowest
Tailscale Servetailscale-servetailscaledYour Tailscale network (called a tailnet), HTTPSLow
Tailscale Funneltailscale-funneltailscaledPublic internet via TailscaleHigh
Cloudflare Tunnelcloudflare-tunnelcloudflaredPublic internet via CloudflareHigh

The netclaw init wizard covers this at step 9:

Exposure mode selection in the netclaw init wizard, with Local highlighted as the recommended option

Options marked with a warning triangle expose the daemon to the public internet. Tailscale Serve is the recommended remote mode: tailnet-only access, no public exposure.

Set the mode in the Daemon section of ~/.netclaw/config/netclaw.json.

No Daemon section needed. The daemon binds to 127.0.0.1:5199 and is only reachable from the same machine.

{}
{
"Daemon": {
"ExposureMode": "tailscale-serve"
}
}

Tailscale Serve creates an HTTPS endpoint on your tailnet that proxies to the daemon’s local port. Only devices on your tailnet can reach it. Then run tailscale serve to proxy your tailnet hostname to 127.0.0.1:5199.

{
"Daemon": {
"ExposureMode": "tailscale-funnel"
}
}

Funnel extends Serve to the public internet. Anyone with the URL can reach the daemon, though netclaw’s device authentication still applies. Configure it with tailscale funnel. Only use Funnel when you need public internet access — Tailscale Serve covers everything else.

{
"Daemon": {
"ExposureMode": "cloudflare-tunnel"
}
}

Routes traffic through Cloudflare’s network to the daemon. Set up cloudflared with a Cloudflare Tunnel pointed at 127.0.0.1:5199, and pair it with a Cloudflare Access policy to restrict who can connect.

FieldTypeDefaultNotes
Hoststring127.0.0.1IP address the daemon binds to
Portint5199TCP port
ExposureModestringlocallocal, tailscale-serve, tailscale-funnel, or cloudflare-tunnel
{
"Daemon": {
"Host": "127.0.0.1",
"Port": 5199,
"ExposureMode": "tailscale-serve"
}
}

Case-insensitive — tailscale-serve, TailscaleServe, and TAILSCALE-SERVE all work.

Docker users: if you switch to a tunnel mode, update your container’s port binding to match the Host and Port values here. See Docker Deployment for details.

Override any field with NETCLAW_Daemon__ prefixed env vars:

Terminal window
NETCLAW_Daemon__ExposureMode=tailscale-serve
NETCLAW_Daemon__Host=127.0.0.1
NETCLAW_Daemon__Port=5199

Double underscores separate path segments, following the .NET configuration convention.

Host, Port, and ExposureMode require a daemon restart — they aren’t hot-reloaded. Other config changes trigger an automatic restart; the daemon drains active sessions and restarts itself.

Terminal window
# systemd
systemctl --user restart netclaw
# Docker
docker restart netclaw

Tunnel modes make inbound webhooks possible. External services like GitHub or CI systems can trigger autonomous runs via HTTP POST. The netclaw init wizard asks about this right after exposure mode selection:

Inbound webhook toggle in the init wizard

They do nothing in local mode.

Non-local modes require at least one paired device or an alternative remote authentication scheme. Without one, the daemon refuses to start — remote clients have no way to authenticate.

The init wizard handles this: it generates a bootstrap device token when you pick a tunnel mode, writing it to ~/.netclaw/config/devices.json and ~/.netclaw/config/secrets.json. For manual setup, pair a device before starting the daemon:

Terminal window
netclaw daemon pair

If the mode requires tailscaled or cloudflared and that process isn’t running, you’ll see this in the daemon logs (~/.netclaw/logs/daemon.log or journalctl --user -u netclaw for systemd):

Daemon startup aborted: ExposureMode is 'tailscale-serve' but the required
tunnel process 'tailscaled' is not running. Start 'tailscaled' before starting
Netclaw, or set ExposureMode to 'local' in netclaw.json.

If no paired devices exist and no alternative auth scheme is configured:

Daemon startup aborted: ExposureMode is 'tailscale-serve' but no paired devices
exist and no alternative remote authentication scheme is configured. Pair a device
with 'netclaw daemon pair' or configure another remote auth scheme before starting
Netclaw.

Both are fatal. The daemon won’t start until you fix the underlying issue.

Run netclaw doctor first — it has a dedicated exposure-mode check.

Netclaw doctor output showing health check diagnostics

Startup aborted with “required tunnel process is not running.”

Start the required process first:

Terminal window
# Tailscale modes
sudo systemctl start tailscaled
# Cloudflare Tunnel
sudo systemctl start cloudflared

Or switch to local mode if you don’t need remote access:

{
"Daemon": {
"ExposureMode": "local"
}
}

Startup aborted with “no paired devices exist and no alternative remote authentication scheme is configured.”

Pair a device:

Terminal window
netclaw daemon pair

Or re-run the init wizard, which generates a bootstrap token automatically when you select a tunnel mode:

Terminal window
netclaw init

netclaw doctor reports a warning: “ExposureMode is ‘local’ but bind address is not loopback.”

The daemon is reachable beyond localhost without tunnel protection. Either bind to loopback:

{
"Daemon": {
"Host": "127.0.0.1"
}
}

Or switch to the exposure mode that reflects how the daemon is actually reachable:

{
"Daemon": {
"Host": "127.0.0.1",
"ExposureMode": "tailscale-serve"
}
}