NoTailCar supports three exit-node postures:
- No exit node - the allocation joins the Tailnet, but public internet traffic uses the client node's normal route.
- Preferred exit node - the guard asks Tailscale to route traffic through one selected exit node.
- Fail-closed pinned egress - the worker does not start unless an exit node is selected and the observed public IP matches an allowlist.
Before setting an exit node in a NoTailCar job:
- The exit-node device is online.
- The exit-node device advertises exit-node capability in Tailscale.
- The auth key or tag used by the Nomad sidecar is allowed by your Tailnet ACL/tag policy.
- You know whether the workload should fail open or fail closed.
- If pinned egress matters, you know the public IPs that should be observed from the exit node.
| Goal | ts_exit_node_primary |
ts_exit_node_fallbacks |
ts_require_exit_node |
ts_egress_expected_ips |
|---|---|---|---|---|
| Tailnet only, normal internet route | "" |
"" |
"false" |
"" |
| Use one exit node if available | "exit-us-east" |
"" |
"false" |
"" |
| Require one exit node | "exit-us-east" |
"" |
"true" |
"" |
| Try fallbacks | "exit-us-east" |
"exit-us-central,exit-us-west" |
"true" |
"" |
| Pinned public egress | "exit-us-east" |
"exit-us-central" |
"true" |
"203.0.113.10,203.0.113.11" |
Prefer stable Tailnet node names such as exit-us-east over raw Tailnet addresses in public docs and examples. In private deployments, either form can work as long as Tailscale accepts it for --exit-node.
NoTailCar includes var files for the main egress-worker example:
# no exit node
nomad job plan \
-var-file=examples/egress-worker/vars/no-exit-node.hcl \
examples/egress-worker/egress-worker.nomad.hcl
# one required exit node
nomad job plan \
-var-file=examples/egress-worker/vars/exit-node-primary.hcl \
examples/egress-worker/egress-worker.nomad.hcl
# required exit node with fallbacks
nomad job plan \
-var-file=examples/egress-worker/vars/exit-node-fallbacks.hcl \
examples/egress-worker/egress-worker.nomad.hcl
# fail closed unless public egress IP is expected
nomad job plan \
-var-file=examples/egress-worker/vars/required-pinned-egress.hcl \
examples/egress-worker/egress-worker.nomad.hclUse nomad job run with the same -var-file after reviewing the plan.
flowchart TB
Start["egress-guard starts"] --> Socket["wait for tailscaled socket"]
Socket --> HasExit{"exit node configured?"}
HasExit -- no --> Require{"TS_REQUIRE_EXIT_NODE=true?"}
Require -- yes --> FailNoExit["fail before worker starts"]
Require -- no --> UpNoExit["tailscale up without exit node"]
HasExit -- yes --> TryPrimary["try primary exit node"]
TryPrimary --> PrimaryOk{"success?"}
PrimaryOk -- yes --> Verify
PrimaryOk -- no --> TryFallbacks["try fallback nodes in order"]
TryFallbacks --> AnyOk{"any success?"}
AnyOk -- no --> FailAll["fail before worker starts"]
AnyOk -- yes --> Verify["optional public IP verification"]
UpNoExit --> Verify
Verify --> Verified{"expected IPs set and matched?"}
Verified -- mismatch --> FailIP["fail before worker starts"]
Verified -- none/match --> Ready["write state file and start worker"]
After a real allocation runs:
nomad alloc logs <alloc-id> egress-guard
nomad alloc logs <alloc-id> workerThe guard writes a state file inside the allocation:
/alloc/tailscale/egress-selectedIt includes the selected exit node, advertised hostname/tags, and readiness timestamp.