Building a Tailscale Subnet Router on AWS with Terraform
4 min read

Building a Tailscale Subnet Router on AWS with Terraform

From zero to working Tailscale Subnet Router on AWS using Terraform. Access a Private EC2 from your laptop without a public IP. Fully automated!

terraform
aws
tailscale
networking
devops
Share

Today I finished a pretty fun challenge: building a Tailscale Subnet Router on AWS using Terraform. Infrastructure as Code from start to finish.

Let me walk you through it.

What I Built

Here's the AWS infrastructure I created:

  • VPC with CIDR 10.1.0.0/16
  • Public Subnet — where the Tailscale Subnet Router lives, with internet access through an Internet Gateway
  • Private Subnet — where the Private EC2 lives, with no internet access at all
  • EC2 Subnet Router — Ubuntu, Tailscale installed, IP forwarding enabled, UFW locked down
  • EC2 Private — truly private, no public IP, no internet access

And the coolest part: I can access that Private EC2 from my laptop — just through Tailscale, using its internal AWS IP (10.1.x.x). No traditional VPN, no NAT Gateway, no public IP needed.

Architecture

It's actually pretty simple:

My Laptop → Tailscale → EC2 Subnet Router → AWS Private Network → EC2 Private

My laptop and the Private EC2 are connected through Tailscale. The Subnet Router acts as a bridge — it receives traffic from Tailscale and forwards it into the internal AWS network.

Workflow

Everything is written as Terraform code:

  1. Terraform init — set up the AWS provider
  2. Terraform apply — create all 13 resources
  3. Auto-bootstrap — when EC2 boots, it automatically installs Tailscale, enables IP forwarding, and advertises the route
  4. Approve route in Tailscale Admin Console — so subnet traffic can flow through
  5. Test connectivity — ping, SSH, nc from laptop to Private EC2
  6. UFW Lockdown — disable SSH access via public IP, only allow through Tailscale
  7. Cleanupterraform destroy, revoke auth key

Everything is automated except route approval and UFW lockdown.

Results

Everything went smoothly after deployment:

  • ✅ Laptop can ping the Private EC2 (10.1.2.54)
  • ✅ Laptop can SSH into the Private EC2 without a public IP
  • ✅ Laptop can nc/telnet to the Private EC2's ports
  • ✅ Subnet Router can access the internet (via IGW)
  • ❌ Private EC2 cannot access the internet (as designed)
  • ✅ After UFW lockdown, SSH via public IP is blocked, but via Tailscale it still works

Lessons Learned

A few things I picked up from this lab:

  1. Source/Destination Check must be disabled — When an EC2 acts as a subnet router, AWS drops packets that aren't destined for the instance itself by default. You must set source_dest_check = false.

  2. Never hardcode auth keys — Tailscale auth keys are sensitive. Store them in environment variables (TF_VAR_tailscale_auth_key), not in terraform.tfvars files that might get committed.

  3. UFW is more flexible than Security Groups — SGs operate at the AWS level with limited control. UFW inside the VM gives you finer control: "allow SSH only from the Tailscale interface, block from the internet." Perfect for this kind of lab.

  4. Routes need manual approval — Even when Tailscale shows as connected, the subnet route (10.1.0.0/16) still needs to be approved through the Admin Console. It's not automatic.

  5. Ephemeral keys are a lifesaver — When the EC2 is destroyed, the device automatically leaves your Tailnet. No manual device cleanup needed.

Challenges Faced

  • Auth key was logged out — Had to regenerate. Lesson: always check /var/log/user-data.log when something goes wrong.
  • UFW lockdown timing — Wasn't sure when to enable UFW. The answer: do it after all tests pass, so you can still troubleshoot via public IP.
  • Test script uses public IP — Once UFW was active, test-connectivity.sh failed because it connects via public IP. Solution: test manually through the Tailscale IP.

Conclusion

This lab proves that:

You can access an EC2 in a Private Subnet without a public IP, NAT Gateway, or traditional VPN — just using Tailscale Subnet Router.

Infrastructure is fully automated with Terraform. Bootstrapping happens automatically on first boot. And the security model works: after UFW lockdown, public SSH is blocked, but Tailscale access keeps working.

Repository: https://github.com/stayrelevantid/tailscale-aws

Enjoyed this article? Share it!

Share

Diskusi & Komentar

Artikel Terkait