Tailscale - Ephemeral Server - Easily create short-use VPNs anywhere

Tailscale Logo

Automatically Deploy a Tailscale Exit Node on GCP Using Bash

Tailscale makes creating software-defined networks easy. This is a quote right from their site but it’s 100% true. In this post I want to talk about a script I made that will allow you to easily set up a Google Cloud linux machine, install and authroize Tailscale with an ephemeral authentication token, and then set up network rules in Google Cloud to allow connection. This is a great, really easy way to get around any geofenced conent on the internet, if you found yourself needing something like that.

For this to work, you will need a Google Cloud account, as well as the Google Cloud CLI set up on whichever machine you are running this on. You’ll also need to be an admin in your Tailscale Tailnet to be able to generate the auth keys.

What the Script Does

This script handles provisioning a cloud-based Tailscale exit node using the Google Cloud Platform (GCP) and Tailscale’s API, and connects it to your Tailnet without any interaction on your part. It sets up Linux to be an exit node (so you can route traffic through that node), as well as setting up Tailscale SSH, which is a fantastic way to just connect to the box if needed.

In this example, I have it set to be an ephemeral key, which will remove itself from Tailscale shortly after the machine goes offline, but you wouldn’t necessarily need to do that, just be cautious with the type of auth key you are generating. The idea is to set up a machine to easily and quickly connect to, then have it destroy itself as soon as you’re done. For the amount I use this, it makes it effectively free.

Additionally, I use tags to change ownership to the tailnet instead of me specifically. In this case I use the tag “streaming”, which I put on my Plex and Jellyfin server, and is set with several rules that could potentially allow streaming from whatever geography you were connecting to. You can change the tag to whatever makes the most sense for your use case.

Note - Whatever tag or group or users you set up make sure to set the ACL rules correctly in Tailscale. I will do another post about that in more depth.


Step-by-Step Breakdown

1. Generate a Tailscale Ephemeral Auth Key

The script first makes a POST request to the Tailscale API to generate a one-time-use authentication key. It enables:

  • Ephemeral mode (expires when the device disconnects)
  • Preauthorization (no manual approval needed)
  • Tagging (tag:streaming in this example)
curl -X POST https://api.tailscale.com/api/v2/tailnet/<your-tailnet>/keys

This key allows the new VM to automatically join your Tailscale network without interaction.


2. Validate the API Response

After sending the request, the script parses the response to check for a successful status code and then extracts the newly issued auth key.


3. Generate a GCP Instance Startup Script

Using a temporary file, the script creates a startup script that will run on the new GCP instance when it boots up. This script:

  • Installs Tailscale
  • Brings the node online with the generated auth key
  • Enables SSH via Tailscale
  • Advertises itself as an exit node with a tag
  • Sets up basic IP forwarding and NAT (via iptables)
  • Ensures the firewall rules persist across reboots

4. Provision the GCP VM

Finally, it provisions a VM using the gcloud CLI, specifying:

  • Machine type and zone
  • Boot disk size
  • Debian OS
  • IP forwarding enabled
  • Metadata to run the startup script
  • Proper scopes and network tags

Once the instance boots, it will automatically connect to your Tailscale network and act as an SSH-accessible exit node.


Final Output

After the script runs, you’ll have a fully functional, cloud-hosted Tailscale exit node with minimal effort. It’s perfect for setting up private, secure tunnels for streaming, remote work, or general internet privacy. In my experience it is up and going and ready to connect to in about 30 seconds.

This version will only create the server for you. In my workflow I just manually delete the instance when I’m done, but I will work on a version that potentially does that in the script itself. Still trying to think through how I want to do that exactly. Once you delete the server in Google Cloud, however, shortly after the machine in Tailscale will also remove itself if you used the ephemeral key option.

This little experiment of mine was mostly a proof of concept for myself, but I’m already thinking of a few other crazy things I could try with what I learned during this.

Here’s the script in full -

#!/bin/bash

# === CONFIG ===
TAILSCALE_API_KEY="<your-tailscale-api-key>"
TAILNET="<your-tailnet-domain>"
GCP_PROJECT="<your-gcp-project-id>"
ZONE="<your-gcp-zone>"
INSTANCE_NAME="tailscale-node"
MACHINE_TYPE="e2-micro"
DISK_SIZE="10GB"

echo "πŸ”’ Generating Tailscale ephemeral auth key with tag:streaming..."

# === Step 1: Call the Tailscale API and capture the full response
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "https://api.tailscale.com/api/v2/tailnet/$TAILNET/keys" \
  -u "$TAILSCALE_API_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "capabilities": {
      "devices": {
        "create": {
          "ephemeral": true,
          "preauthorized": true,
          "tags": ["tag:streaming"]
        }
      }
    }
  }')

# === Step 2: Separate JSON and HTTP status
HTTP_BODY=$(echo "$RESPONSE" | sed '$d')
HTTP_STATUS=$(echo "$RESPONSE" | tail -n1)

# === Step 3: Parse key or show debug info
if [[ "$HTTP_STATUS" != "200" ]]; then
  echo "❌ Failed to create Tailscale auth key. HTTP status: $HTTP_STATUS"
  echo "Response body:"
  echo "$HTTP_BODY"
  exit 1
fi

AUTHKEY=$(echo "$HTTP_BODY" | jq -r '.key')

if [[ -z "$AUTHKEY" || "$AUTHKEY" == "null" ]]; then
  echo "❌ Auth key was not found in the response."
  echo "Full response:"
  echo "$HTTP_BODY"
  exit 1
fi

echo "βœ… Tailscale auth key created: $AUTHKEY"

# === Step 4: Build startup script with injected auth key
STARTUP_SCRIPT=$(mktemp)
cat > "$STARTUP_SCRIPT" <<EOF
#!/bin/bash
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
sysctl -p
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up --authkey=$AUTHKEY --ssh --advertise-exit-node --advertise-tags=tag:streaming
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
iptables-save > /etc/iptables/rules.v4
EOF

echo "πŸš€ Creating GCP instance '$INSTANCE_NAME' in '$ZONE'..."

gcloud compute instances create "$INSTANCE_NAME" \
  --project="$GCP_PROJECT" \
  --zone="$ZONE" \
  --machine-type="$MACHINE_TYPE" \
  --image-family="debian-12" \
  --image-project="debian-cloud" \
  --boot-disk-size="$DISK_SIZE" \
  --can-ip-forward \
  --tags=tailscale-exit-node \
  --metadata-from-file startup-script="$STARTUP_SCRIPT" \
  --scopes=https://www.googleapis.com/auth/cloud-platform

rm "$STARTUP_SCRIPT"

echo "βœ… Instance created and fully configured as a Tailscale exit node with SSH."
Links