Skip to content

Tailscale

The NAT instance can optionally join your tailnet as a subnet router (exposing VPC CIDRs to the tailnet) and/or an exit node. It is a natural fit: the instance already forwards traffic, and the same HA/cattle model applies — nodes are disposable and replacements join automatically.

Configuration variables are documented in Configuration; this page covers the recommended tailnet setup and why it works with instance lifecycles.

Use a reusable, ephemeral, pre-approved, tagged auth key:

Property Why
Reusable ASG-replaced instances present the same key from SSM. A one-time key would fail on the second launch.
Ephemeral Dead nodes are removed from the tailnet automatically shortly after going offline — no offline corpses to clean up in the admin console.
Pre-approved The node itself needs no manual device approval (relevant if your tailnet has device approval enabled).
Tagged (e.g. tag:nat) Enables autoApprovers for routes/exit-node (see below), and tagged nodes have no node-key expiry — long-lived instances never drop off the tailnet at the default 180-day expiry.

Without tags + autoApprovers, every ASG replacement is a new node whose advertised routes sit unapproved in the admin console — the tunnel comes up but no traffic routes through it until a human clicks approve.

Setup

1. Define the tag and auto-approvals in the tailnet policy

In the admin console → Access controls, add to the policy file (adjust CIDRs to the routes your instances advertise):

{
  "tagOwners": {
    "tag:nat": ["autogroup:admin"],
  },
  "autoApprovers": {
    "routes": {
      "10.0.0.0/16": ["tag:nat"],
      "10.1.0.0/16": ["tag:nat"],
    },
    "exitNode": ["tag:nat"],
  },
}

autoApprovers means any node joining with tag:nat gets its advertised routes (and exit-node status) approved automatically — zero-touch replacements. An advertised route is auto-approved when it equals or is a subset of an approver entry, so with tailscale_advertise_routes=auto use an entry broad enough to cover any VPC your instances may land in (e.g. "10.0.0.0/8": ["tag:nat"]) — a detected CIDR with no matching entry sits unapproved until a human clicks approve.

2. Create the auth key

Admin console → Settings → KeysGenerate auth key:

  • Reusable: on
  • Expiration: up to 90 days (see rotation note below)
  • Ephemeral: on
  • Pre-approved: on
  • Tags: tag:nat

Because the key carries the tag, nodes join tagged automatically — no --advertise-tags needed in tailscale_extra_args.

Rotation: auth keys expire after at most 90 days. Existing nodes keep running past key expiry (their identity is already established); only new launches fail. Rotate the key into SSM before it expires, or automate rotation via the Tailscale API.

3. Store the key in SSM Parameter Store

aws ssm put-parameter \
  --name /pelotech-nat/tailscale-authkey \
  --type SecureString \
  --value 'tskey-auth-...'

4. Grant the instance profile access

  • ssm:GetParameter on the parameter (plus kms:Decrypt if it uses a customer-managed KMS key)
  • ec2:DescribeTags (only needed for the Name-tag hostname default; skip it if you set tailscale_hostname explicitly)

5. Configure the instance

# /etc/pelotech-nat.conf
tailscale_enabled=true
tailscale_auth_key_ssm=/pelotech-nat/tailscale-authkey
tailscale_advertise_routes=auto
tailscale_exit_node=true

tailscale_advertise_routes=auto detects the VPC's IPv4 CIDRs (primary + secondary) from instance metadata at boot — no per-VPC configuration and no extra IAM permissions. Auto-detection only sees the instance's own VPC; if the instance should also route to peered VPCs, Transit Gateway attachments, or on-prem ranges, list all CIDRs explicitly instead.

Instance lifecycle behavior

Restart / reboot. A node's identity (node key, tailnet IP) lives in /var/lib/tailscale — the auth key is only a join credential. On reboot, tailscaled reconnects with the saved identity and the boot script skips tailscale up entirely. Same node, same IP; single-use or expired keys don't matter here.

Replacement (ASG). A fresh instance has no Tailscale state (the AMI bakes none), so it presents the auth key and joins as a new node with a new tailnet IP. The old node goes offline and — with an ephemeral key — is removed automatically. With autoApprovers, the new node's routes are live immediately. Hostname collisions (old node still lingering with the same Name tag) are cosmetic: the new node becomes e.g. pelotech-nat-1 in MagicDNS until the old entry is garbage-collected.

Multiple instances, one key. Fully supported — a reusable key handed to N instances produces N independent nodes, each with its own identity. Instances advertising the same routes form an HA subnet router set: Tailscale keeps one active per route and fails over to another advertiser within seconds if it goes offline. The same applies to exit nodes (clients pick exit nodes by name, so distinct hostnames help).

Config changes. tailscale up only runs on a node's first login. After changing tailscale_* variables on a running instance, re-run tailscale up manually with the new flags (or tailscale logout and restart pelotech-nat.service). Replacement instances always pick up the current config.

FIPS consideration

Tailscale's WireGuard tunnel uses ChaCha20-Poly1305, which is not FIPS 140-3 approved; the system-wide FIPS crypto policy still governs TLS, SSH, and other OpenSSL-based protocols. Treat the overlay as an accepted risk if you require strict FIPS-only operation — see the note in Configuration.