Tailscale Ephemeral Nodes: Your 1,000 Minutes Go Reach?
Tailscale free plan dey give 1,000 ephemeral minutes every month. See how GitHub Actions deploy and throwaway container dey use am, and why tailscale logout go save your minutes.
Tailscale ephemeral nodes: di short answer
Tailscale ephemeral nodes na devices wey join your tailnet for short time, do one work, then comot by demself. Di free Personal plan dey give 1,000 minutes every month for dis kind node. For most small deploy pipeline, dat one go reach, as long as every job comot from di tailnet di moment e finish.
Dis guide na for developer wey open di Tailscale pricing page, see "1,000 mins per month for ephemeral resources", and begin calculate. Maybe you dey push code to GitHub from Lagos, and GitHub Actions dey deploy am go your VPS (virtual private server). Maybe you dey start container wey you go delete after one hour. Di question na di same: dis work go finish my free minutes? And if e finish, dem go ask me for dollar card wey my bank no too like?
We go answer am with real numbers. First, wetin ephemeral node be. Then di exact pricing rule and di minute maths for one normal deploy pattern. Then one question wey di docs no answer. After dat, two setups wey you fit copy: GitHub Actions job wey deploy to your VPS over SSH with no public SSH port, and container sidecar for throwaway work.
Wetin make a Tailscale node ephemeral?
Tailscale explain am for di knowledge base article KB 1111. Ephemeral node na device wey Tailscale go remove from your tailnet by itself after e don quiet for some time. Normal device no dey do dat. Your laptop go stay for di admin console list until you remove am by hand.
You fit make ephemeral node two ways:
- Generate ephemeral auth key for di Keys page of di admin console, then use am to log in. Auth key na secret string wey dey start with
tskey-auth-. E dey let machine join your tailnet without person click "Log in" for browser. - Start
tailscaledwith--state=mem:. Dis flag mean say di daemon no go save im state for disk. Di flag dey since Tailscale v1.22.
With auth key, di login na one line:
sudo tailscale up --auth-key=tskey-auth-XXXXXXXXXXXXtailscale status suppose show di machine with im 100.x address after dat. For di admin console, di machine go carry Ephemeral label.
Two facts from KB 1111 matter pass everything for dis guide:
- Tailscale dey remove ephemeral device "anywhere normally from 30 to 60 minutes after the last activity". Na so di docs talk am, word for word.
tailscale logoutremove di device from di tailnet immediately. If you starttailscaledwith--state=mem:(v1.30 or newer), di daemon dey do dat logout by itself when e exit.
So ephemeral node get two ways to leave. One na di slow cleanup wey Tailscale dey do. Di other na di fast one wey you control.
Wetin di Tailscale pricing page talk about ephemeral minutes?
I read tailscale.com/pricing on 2026-10-04. Dis na wetin e talk dat day. E fit change after dat date, so check am again before you plan big.
Di data wey dey behind dis chart
[
{
"plan": "Personal (free)",
"ephemeral_minutes_per_month": "1,000",
"tagged_resources_included": 50
},
{
"plan": "Standard",
"ephemeral_minutes_per_month": "1,000",
"tagged_resources_included": 50
},
{
"plan": "Premium",
"ephemeral_minutes_per_month": "10,000",
"tagged_resources_included": 50
}
]Personal (di free plan) and Standard both get 1,000 ephemeral minutes every month. Premium get 10,000. Every plan include 50 tagged resources.
Di rule wey people dey miss na di four-hour rule. KB 1111 talk am like dis: if ephemeral node dey for di tailnet four hours or more, e no go count against your minutes. Tailscale go count am as standard tagged device instead. So container wey run for two days no go chop your minute pool. E go take one of your 50 tagged slots. Di docs no talk clear whether di minutes wey di node use before e reach four hours go return to your pool, so no build plan on top of dat one.
Wetin dis mean be say di minute pool na for short jobs. Jobs wey last few minutes and run plenty times for month. Na exactly so CI (continuous integration) job dey behave.
One more thing make we clear. Dis Tailscale minutes no be di same as GitHub Actions minutes. GitHub dey count di time wey your runner dey run. Tailscale dey count di time wey ephemeral node dey inside your tailnet. Na two different meters. Di full gist of di free plan, including di six users and di unlimited user devices, dey for di full list of Tailscale free plan limits. If you wan know wetin happen when you pass di free line, read how Tailscale pricing really work for one person or small team.
How many ephemeral minutes one GitHub Actions deploy go use?
Make we calculate one normal pattern. Your deploy job build di code, join di tailnet, copy files go di VPS over SSH (secure shell), restart di service, then log out. Di part wey di node dey inside di tailnet na about 3 minutes. Di build no count if you put di Tailscale step after di build. We use 30-day month.
Di data wey dey behind dis chart
[
{
"label": "5 deploy a day, logout",
"minutes_per_month": 450,
"jobs_per_month": 150,
"minutes_per_job": 3
},
{
"label": "10 deploy a day, logout",
"minutes_per_month": 900,
"jobs_per_month": 300,
"minutes_per_job": 3
},
{
"label": "15 deploy a day, logout",
"minutes_per_month": "1,350",
"jobs_per_month": 450,
"minutes_per_job": 3
},
{
"label": "1 deploy a day, no logout, if wait count",
"minutes_per_month": "1,440",
"jobs_per_month": 30,
"minutes_per_job": 48
}
]Five deploys a day give 150 jobs for month. Dat one come to 450 minutes. Ten deploys a day give 900 minutes, still under di line. Fifteen a day give 1,350 minutes, and dat one don pass di free pool.
Di last row na di one wey fit shock you. Suppose you no log out, and suppose di 30 to 60 minutes cleanup wait dey count. We take 45 minutes as di middle. Then every job go cost 48 minutes instead of 3. Even one deploy a day go reach 1,440 minutes for month, so you go pass di line before di month end. Na why logout matter pass any other setting for dis guide.
Two habits keep di number small:
- Put di Tailscale step after build and test, just before di deploy step. Di node dey present from di moment e join, so every minute of build wey happen after join na minute wey you fit save.
- Set
timeout-minutesfor di job. Job wey hang no go hold di node inside di tailnet for hours.
Di docs no talk how dem dey round di minutes, whether per second or per started minute. So budget with whole minutes and leave space.
Di 30 to 60 minutes cleanup: e dey count against di free minutes?
Dis na di part wey I no fit answer for you, and I no go pretend. KB 1111 talk say di node go disappear 30 to 60 minutes after di last activity. Di pricing page talk say ephemeral resources get minute pool. Neither page talk whether di quiet time between di last packet and di removal dey enter di pool. As of 2026-10-04, I no see any Tailscale doc wey settle am.
Di good news be say you no need di answer. tailscale logout remove di node at once. Once di node no dey di tailnet again, nothing remain to count, whether Tailscale count di cleanup wait or not. So di rule for dis guide simple: every job wey join di tailnet must run tailscale logout before e finish. If your setup use --state=mem:, di daemon dey do am for you when e exit. Still check di admin console once, make you see am with your eye.
How to prepare di tailnet: tags and access rules
Before any CI job join, give am tag. Tag na label like tag:ci wey own di device instead of person. Di Tailscale policy file, wey hold your ACL (access control list), dey use tags to decide which device fit reach which one.
Open Access controls for di admin console. Add di two tag owners and one grant inside di policy wey dey there already. No delete di rules wey you get before. Dis grant allow only port 22 and port 8080 from CI machines to your server, nothing else:
{
"tagOwners": {
"tag:ci": ["autogroup:admin"],
"tag:server": ["autogroup:admin"]
},
"grants": [
{ "src": ["tag:ci"], "dst": ["tag:server"], "ip": ["tcp:22", "tcp:8080"] }
]
}Port 22 na for di SSH deploy. Port 8080 na for di container health check wey we go do later. If your app no listen for 8080, comot am.
Next, tag di VPS. Go Machines, open di VPS, choose Edit ACL tags, and add tag:server. Di VPS now be tagged resource, so e take one of di 50 slots.
Then create OAuth client for di GitHub Action. OAuth na di standard way to give app limited access without your password. For di admin console, under Settings, open OAuth clients. Some newer consoles fit call dis page Trust credentials. Give di client write access to Auth Keys and attach tag:ci. Copy di client ID and di secret. For your GitHub repository, save dem as secrets TS_OAUTH_CLIENT_ID and TS_OAUTH_SECRET. Di action use dis client to make fresh ephemeral key for every run, so you no need to rotate auth key by hand every few months.
Deploy from GitHub Actions to your VPS over SSH, with no public SSH port
First, prepare di VPS. Create deploy user wey fit restart only one service:
sudo useradd -m -s /bin/bash deploy
sudo mkdir -p /srv/myapp
sudo chown deploy:deploy /srv/myapp
echo 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp' | sudo tee /etc/sudoers.d/deploy-myapp
sudo chmod 440 /etc/sudoers.d/deploy-myapp
sudo visudo -cvisudo -c suppose print parsed OK for every file. If e print syntax error, fix di file before you log out, because broken sudoers file fit block sudo for everybody.
For your laptop, make one SSH key pair just for deploy, then put di public half for di VPS:
ssh-keygen -t ed25519 -f ~/.ssh/gh_deploy -N "" -C gh-deploy
cat ~/.ssh/gh_deploy.pubsudo install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
echo 'ssh-ed25519 AAAA...your-key... gh-deploy' | sudo tee /home/deploy/.ssh/authorized_keys
sudo chown deploy:deploy /home/deploy/.ssh/authorized_keys
sudo chmod 600 /home/deploy/.ssh/authorized_keysNow save three things for GitHub. Di content of ~/.ssh/gh_deploy (di private key) go inside secret DEPLOY_SSH_KEY. Di VPS host key go inside secret VPS_KNOWN_HOSTS. Get am from your laptop while your laptop dey on di tailnet: ssh-keyscan -t ed25519 my-vps. Di name my-vps go inside repository variable VPS_HOST. Use di same name for di scan and for di variable, because SSH dey match di host key by name.
Now di workflow. Save am as .github/workflows/deploy.yml:
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
- name: Build
run: ./build.sh
- name: Tailscale
uses: tailscale/github-action@v4.2.0
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:ci
ping: ${{ vars.VPS_HOST }}
- name: Deploy over the tailnet
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
VPS_HOST: ${{ vars.VPS_HOST }}
run: |
install -d -m 700 ~/.ssh
printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
rsync -az --delete ./dist/ "deploy@${VPS_HOST}:/srv/myapp/"
ssh "deploy@${VPS_HOST}" 'sudo systemctl restart myapp'Change ./build.sh and ./dist/ to your own build command and output folder. Di action dey pinned to release v4.2.0, di latest tag as of October 2026. Pinning mean say new release no go change your pipeline without you know. If you want am tighter, pin to di full commit SHA of dat release.
Wetin di job log suppose show: di Tailscale step end with line wey start with pong from, because di ping input wait until di VPS answer. Di deploy step finish with no error. At di end of di job, GitHub run one extra step wey dem call Post Tailscale. Di action README talk say nodes wey di action create "are marked as Ephemeral" and "log out immediately after finishing their CI run". Na for dat Post step di logout dey happen. Open Machines for di admin console after di run. Di CI node no suppose dey there.
Close di public SSH port
Once deploy dey work over di tailnet, port 22 no need to face di internet again. Keep one SSH session open over di tailnet before you touch di firewall. If you dey use ufw:
sudo ufw allow in on tailscale0 to any port 22 proto tcp
sudo ufw delete allow OpenSSH
sudo ufw status verboseufw status verbose suppose show port 22 allowed only on tailscale0. If your old rule na 22/tcp instead of OpenSSH, delete dat one instead. Open new SSH session over di tailnet to test before you close di old one. Many providers get separate network firewall for dia panel, so close 22 there too.
How to run throwaway container with Tailscale sidecar
Sometimes di short job no be GitHub Actions. E fit be one container wey run test against your VPS, then you delete am. Di pattern na sidecar: one Tailscale container hold di network, and your job container share am.
First, generate ephemeral auth key for di Keys page. Turn on Ephemeral, attach tag:ci, and turn on Reusable if you go run di job plenty times. Then write compose.yaml:
services:
tailscale:
image: tailscale/tailscale:v1.102.5
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_HOSTNAME=scratch-job
- TS_USERSPACE=false
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
restart: "no"
job:
image: alpine:3.21
network_mode: service:tailscale
depends_on:
- tailscale
command: ["wget", "-qO-", "http://${VPS_TS_IP}:8080/health"]TS_USERSPACE=false mean kernel networking, so di job container fit reach tailnet addresses directly. Dat na why di file need /dev/net/tun and NET_ADMIN. We no set TS_STATE_DIR. Tailscale docs talk say without am, every restart create new node for di admin console. For throwaway work, na exactly wetin we want.
Run am like dis. Get VPS_TS_IP from tailscale ip -4 on di VPS:
export TS_AUTHKEY=tskey-auth-XXXXXXXXXXXX
export VPS_TS_IP=<output of tailscale ip -4 on the VPS>
docker compose up -d tailscale
docker compose exec tailscale tailscale status
docker compose run --rm job
docker compose exec tailscale tailscale logout
docker compose downtailscale status suppose list scratch-job and your VPS. Di job suppose print di health response from your app. After tailscale logout, scratch-job comot from di Machines page at once. If you skip di logout line and run only docker compose down, you dey leave di cleanup to di slow Tailscale timer, and dat na di open question from before. No skip am.
For service wey go run for weeks, dis pattern no fit. Dat one need state volume and stable name, and running Tailscale as sidecar for Docker Compose show di long-life setup.
Ephemeral, tagged or user device: which one you go choose?
Tailscale get three kinds of device for dis matter, plus one option wey no add any node. Choose on purpose, no be by accident:
- User device: your laptop or your phone. Person own am, and di Personal plan allow unlimited user devices. Use am for machine wey you dey sit in front of.
- Tagged device: server wey dey run all day, like your VPS. Tag own am, no be person. E take one of di 50 tagged slots.
- Ephemeral node: job wey last minutes. E draw from di minute pool. If e stay four hours or more, Tailscale count am as tagged resource instead.
- No new node at all: if your GitHub runner dey run on your own VPS, di VPS don already join di tailnet. Di job no need ephemeral node, and no minute go count. Running self-hosted GitHub Actions runner on your VPS show how to set am up.
Di simple rule: if e go live less than four hours and you no wan clean am by hand, make am ephemeral. If e go live for days, tag am and give am state. If na person dey carry am, leave am as user device.
Wetin dis error messages mean?
requested tags [tag:ci] are invalid or not permitted: di tag no dey tagOwners for di policy file, or di OAuth client no carry dat tag. Add di tag for both places.
Host key verification failed.: di VPS_KNOWN_HOSTS secret no match di name for VPS_HOST. Scan again with di exact same name.
ssh: Could not resolve hostname: MagicDNS (Tailscale own name service) no answer for di runner. Put di 100.x address for VPS_HOST, then scan di host key again with dat address.
Connection timed out on port 22: di packets reach Tailscale, but di grant no allow dem, so Tailscale drop dem with no reply. Check say di VPS carry tag:server and di grant list tcp:22.
Di way out: Headscale get no minute meter
Headscale na open-source control server wey you fit run yourself. Di normal Tailscale clients dey connect to am. E support ephemeral nodes, and e no get minute pool at all:
sudo headscale users list
sudo headscale preauthkeys create --user 1 --reusable --ephemeral --expiration 24hPut di user ID from users list for --user. Headscale delete inactive ephemeral node after di timeout wey dey your config.yaml. Di example config set am to 30m. Di name of dat setting don change between releases, so check di example config for your own version. Di price na say you now run di control server yourself, and you handle upgrades and backups. Self-hosting Headscale as your own Tailscale control server waka through di full setup.
But for most people, di free plan go do. Everything for dis guide work on di Personal plan: ephemeral keys, tags, grants, di OAuth client and di GitHub Action. You no need dollar card for any of am. Just log out at di end of every job, and di minutes go last.
FAQ
Will my GitHub Actions deploys finish di Tailscale free ephemeral minutes?
For most small projects, no. If di Tailscale step join just before deploy and di node dey for di tailnet about 3 minutes, ten deploys a day for 30-day month come to 900 minutes, under di free pool of 1,000. Fifteen deploys a day pass am. Join di tailnet late in di job, and make sure di node log out at di end.
Does tailscale logout remove ephemeral node immediately?
Yes. Tailscale KB 1111 talk say tailscale logout remove di device from di tailnet immediately. Without logout, di automatic cleanup normally take 30 to 60 minutes after di last activity. If tailscaled run with --state=mem: on v1.30 or newer, di daemon do di logout by itself when e exit.
Di 30 to 60 minutes cleanup wait dey count against my free minutes?
Di Tailscale docs no talk, as of 2026-10-04. Di knowledge base give di cleanup time, and di pricing page give di minute pool, but neither one connect di two. Na why di safe answer na to run tailscale logout at di end of every job. Di node comot at once, so nothing remain to count either way.
Wetin happen if ephemeral node stay inside di tailnet more than four hours?
Tailscale stop to count am against your ephemeral minutes and count am as standard tagged device instead. On di Personal plan, dat mean e take one of di 50 included tagged resource slots. Long-running container no go drain di minute pool, but plenty of dem fit fill your tagged slots.