Build a Private AI Network with Tailscale

Connect 5 devices, deploy Ollama on AWS, and enforce role-based access control.

Introduction

30 Second Summary

You have a laptop at your desk, a phone in your pocket, a Raspberry Pi at home, and cloud servers running on AWS. Getting all of these devices to talk to each other securely usually means wrestling with firewall rules, VPN tunnels, or exposing services to the public internet.

In this project, you will connect 5 devices into a single private network using Tailscale, deploy an AI server in the cloud, configure DNS so every device is reachable by a friendly name, and write access control policies that determine exactly who can reach what.

What You'll Build

A private AI network spanning 5 devices across your home, cloud, and mobile, secured with custom DNS hostnames and role-based access control.

By the end of this project, you'll have:

  • A 5-device Tailnet with custom MagicDNS hostnames where every device is reachable by name.
  • A self-hosted Ollama AI server on AWS EC2, securely accessible from all devices via HTTPS using Tailscale Serve.
  • A tailnet policy file with grants controlling multi-user access: an Admin with full control and a Member with read-only access to specific services.
  • Secret Mission: Configure a subnet router for private VPC access from any device on your network.

Are there any prerequisites?

This project assumes you already have a Tailscale account with Tailscale installed on a Windows laptop (from the beginner project), an AWS account, a Raspberry Pi 5 with Raspberry Pi OS, and an iPhone with App Store access. You will also need a second person (or a second email account) to invite as a Member to your network.

Launch EC2 Instances and Connect All Devices

Your Tailnet currently has one device: your Windows laptop. To build a private AI network, you need cloud servers, a home device, and mobile access all connected securely.

In this step, you will generate a reusable auth key, launch two EC2 instances on AWS, install Tailscale on both, connect your Raspberry Pi 5, and add your iPhone to the Tailnet.

In this step, get ready to:
  • Generate a reusable auth key and launch two EC2 instances with Tailscale installed.
  • Install Tailscale on your Raspberry Pi 5 and join the Tailnet.
  • Install the Tailscale iOS app on your iPhone and verify all 5 devices are connected.
Generate a reusable auth key and launch EC2 instances

An auth key lets you add devices to your Tailnet without opening a browser and signing in manually on each one. Since your EC2 instances are headless servers, an auth key is the only practical way to authenticate them.

  • Open the Tailscale admin console in your browser at https://login.tailscale.com/admin.
  • Navigate to the Keys page in the admin console sidebar.
  • Click Generate auth key.
  • Check the Reusable checkbox so the key can be used for multiple devices.
  • Leave the expiry at the default (90 days is fine for this project).
  • Click Generate key.
  • Copy the auth key and save it somewhere safe (a text file or password manager). You will use it multiple times in this step.

What is an auth key?

An auth key is a pre-authentication token that lets a device join your Tailnet without an interactive browser login. A reusable key can authenticate multiple devices, which is exactly what you need for your two EC2 instances and Raspberry Pi.

Now you will launch the first EC2 instance. This one will be your AI server.

  • Open the AWS Management Console at https://console.aws.amazon.com/ec2/.
  • Click Launch instance.
  • Under Name, enter ai-server.
  • Under Application and OS Images, choose Quick Start, then select Amazon Linux.
  • Under Instance type, select t3.medium from the dropdown.
  • Under Key pair (login), select an existing key pair or click Create new key pair and download the .pem file.
  • Under Network settings, keep the default VPC and ensure the security group allows SSH (port 22) from your IP address.
  • Click Launch instance.

Why t3.medium for the AI server?

Ollama needs at least 4GB of RAM to run small language models. The t3.medium instance type provides 4GB, making it the smallest practical option for hosting an AI model. It costs approximately $0.04/hour.

Next, launch the second instance for general use.

  • Return to the EC2 dashboard and click Launch instance again.
  • Under Name, enter general-ec2.
  • Under Application and OS Images, choose Quick Start, then select Amazon Linux.
  • Under Instance type, select t2.micro (this one is Free Tier eligible).
  • Use the same key pair and security group as the first instance.
  • Click Launch instance.

Wait for both instances to show a Running state in the EC2 Instances list before continuing.

Now SSH into the ai-server instance and install Tailscale.

  • In the EC2 Instances list, select the ai-server instance.
  • Click Connect, then choose the SSH client tab.
  • Copy the SSH command shown and run it in your Windows terminal (PowerShell or the terminal from your Tailscale setup). The default username for Amazon Linux is ec2-user.
  • Install Tailscale on the ai-server by running:
curl -fsSL https://tailscale.com/install.sh | sh
  • Authenticate and join the Tailnet using your auth key by running:
sudo tailscale up --auth-key=your-auth-key-here --hostname=ai-server

What does this command do?

The --auth-key flag authenticates the device to your Tailnet without requiring a browser login. The --hostname flag sets the device name that will appear in your admin console and MagicDNS.

Repeat for the second instance.

  • Open a new terminal session and SSH into the general-ec2 instance using the same process (EC2 console > Connect > SSH client tab).
  • Install Tailscale by running:
curl -fsSL https://tailscale.com/install.sh | sh
  • Authenticate and join the Tailnet by running:
sudo tailscale up --auth-key=your-auth-key-here --hostname=general-ec2

Both EC2 instances are now connected to your Tailnet. You should see them appear in the Machines page of the admin console.

Instance not appearing in the admin console?

Make sure the instance's security group allows outbound traffic on all ports (the default does). Tailscale needs outbound UDP access to establish its WireGuard tunnel. Also double-check that you pasted the auth key correctly with no extra spaces.

help me troubleshoot why my EC2 instance isn't joining my Tailnet after running tailscale up with an auth key

Install Tailscale on your Raspberry Pi 5

Your Raspberry Pi 5 is a home device that will be reachable from anywhere on your Tailnet. The same install script that worked on EC2 works on Raspberry Pi OS.

  • Open a terminal on your Raspberry Pi 5 (either directly or via SSH on your local network).
  • Install Tailscale by running:
curl -fsSL https://tailscale.com/install.sh | sh
  • Start Tailscale and authenticate by running:
sudo tailscale up

What happens next?

The command outputs a URL. Open that URL in a browser on any device where you are signed into your Tailscale account. Click Connect to authorize the Pi to join your Tailnet.

After you authenticate, your Raspberry Pi 5 appears in the Machines page of the admin console.

Getting a permission error on the Pi?

Make sure you include sudo before both the install command and the tailscale up command. The installer needs root privileges to add package repositories and install the binary.

help me fix a permission error when installing Tailscale on my Raspberry Pi 5

Install Tailscale on your iPhone and verify all devices

Your iPhone is the final device. The Tailscale iOS app connects your phone to the Tailnet so you can access all your services from anywhere.

  • On your iPhone, open the App Store.
  • Search for Tailscale and install the app.
  • Open the Tailscale app and tap Get Started.
  • Sign in using the same account you used for your Tailnet (the same identity provider you used on your Windows laptop).
  • Allow the VPN configuration when iOS prompts you.

Your iPhone is now connected. Time to verify the full Tailnet.

  • Open the Tailscale admin console Machines page in your browser.
  • Confirm all 5 devices are listed: your Windows laptop, iPhone, Raspberry Pi 5, ai-server, and general-ec2.
  • Verify from the command line on any device by running:
tailscale status

You should see all 5 devices listed with their Tailscale IP addresses and a status of active or idle.

Missing a device in the list?

Check that the device has an active internet connection. For the iPhone, make sure the Tailscale VPN toggle is enabled in the app. For EC2 instances, verify the auth key has not expired by checking the Keys page in the admin console.

help me figure out why one of my devices is missing from tailscale status

Your 5-device Tailnet is live. Next up, you will configure custom MagicDNS hostnames so every device is reachable by a friendly name instead of an IP address.

Configure MagicDNS Hostnames and Windows Remote Access

Your 5 devices are connected to the Tailnet, but they all have auto-generated names based on their operating system hostnames. Names like laptop-a4og4947 or ip-172-31-42-7 are not helpful when you need to quickly reach a specific device.

In this step, you will rename every device to a meaningful hostname, set up remote file sharing from your Windows laptop to your iPhone, and verify that MagicDNS resolves all your custom names correctly.

In this step, get ready to:
  • Rename all 5 devices to meaningful MagicDNS hostnames in the admin console.
  • Share a folder from your Windows laptop over HTTPS using Tailscale Serve.
  • Verify DNS resolution across all devices with tailscale dns status.
Rename your devices in the admin console

MagicDNS automatically registers a DNS entry for each device using its machine name. When you rename a device, its DNS entry updates instantly across the entire Tailnet.

  • Open the Tailscale admin console in your browser and navigate to the Machines page.
  • Find the device entry for your AI server EC2 instance.
  • Click the three-dot menu (⋮) to the right of the device row.
  • Select Edit machine name... from the dropdown.
  • Uncheck the Auto-generate from OS hostname box so the name stays fixed.
  • Type ai-server as the new machine name and save.
  • Repeat this process for each remaining device, using these names:
  • t2.micro EC2 instance: general-ec2
  • Raspberry Pi 5: rpi-home
  • Windows laptop: my-laptop
  • iPhone: my-iphone

What is MagicDNS?

MagicDNS is Tailscale's built-in DNS system that automatically assigns a DNS name to every device on your Tailnet. Each device becomes reachable at <machine-name>.<tailnet-name>.ts.net.

Because MagicDNS adds search domains, you can also reach devices using just the short machine name (e.g., rpi-home) without typing the full domain.

Now verify that MagicDNS resolves your new hostnames.

  • On your Windows laptop, open PowerShell.
  • Test connectivity to your Raspberry Pi by running this command:
tailscale ping rpi-home
  • You should see a response showing the Tailscale IP address of rpi-home and a latency measurement confirming the device is reachable by name.
  • Test another device to confirm:
tailscale ping ai-server

Ping not resolving the hostname?

Make sure you unchecked Auto-generate from OS hostname when renaming. If the box is still checked, the OS hostname may overwrite your custom name on the next Tailscale restart.

Also verify MagicDNS is enabled by checking the DNS page in the admin console. It should be enabled by default for tailnets created after October 2022.

Help me troubleshoot why tailscale ping is not resolving my custom MagicDNS hostname.

Share a folder from your Windows laptop using Tailscale Serve

Tailscale Serve lets you share a local directory over HTTPS to your entire Tailnet. Only devices on your Tailnet can access the shared content. No ports need to be opened, and TLS certificates are provisioned automatically.

  • On your Windows laptop, open an Administrator terminal by pressing Windows+x and selecting Terminal (Admin) from the menu.
  • Create a folder with sample files by running these commands:
mkdir C:\tmp\shared-files
"Hello from my private Tailnet!" | Out-File C:\tmp\shared-files\hello.txt
"This file is only visible to Tailnet devices." | Out-File C:\tmp\shared-files\readme.txt

Why C:\tmp\shared-files?

Tailscale Serve requires a full, absolute path to the directory you want to share. Using C:\tmp\shared-files keeps it short and avoids spaces in the path that could cause issues.

  • Start sharing the folder to your Tailnet by running:
tailscale serve --bg C:\tmp\shared-files

What does this command do?

tailscale serve --bg C:\tmp\shared-files shares the directory over HTTPS at your device's MagicDNS address.

The --bg flag runs Serve in the background so it persists even if you close the terminal. Your folder becomes accessible at https://my-laptop.<tailnet>.ts.net from any device on your Tailnet.

  • You should see output confirming Serve is running, showing the HTTPS URL and the path being served.

HTTPS certificates prompt

If this is the first time you use Tailscale Serve, the CLI may prompt you to enable HTTPS certificates for your Tailnet. Accept the prompt to continue. This is a one-time setup that provisions automatic TLS certificates for all your devices.

Now verify the shared content is accessible from your iPhone.

  • On your iPhone, open Safari.
  • Navigate to https://my-laptop.<tailnet>.ts.net (replace <tailnet> with your actual tailnet name from the admin console DNS page).
  • You should see a directory listing showing hello.txt and readme.txt.
  • Tap hello.txt to confirm you can read its contents.

Where do I find my tailnet name?

Your tailnet name appears in the DNS page of the admin console. It looks something like yak-bebop.ts.net (two random words followed by .ts.net).

Cannot access the shared folder from your iPhone?

Make sure the Tailscale VPN toggle is active on your iPhone. Open the Tailscale iOS app and confirm it shows Connected.

Double check you are using https:// (not http://). Tailscale Serve only exposes content over HTTPS.

Help me troubleshoot why my iPhone cannot reach my Windows laptop's shared folder over Tailscale Serve.

Inspect your DNS configuration

Tailscale provides a built-in command to inspect the DNS configuration of your local device and the tailnet-wide MagicDNS setup.

  • On your Windows laptop, run this command in PowerShell:
tailscale dns status

What does tailscale dns status show?

This command prints the configuration of your local DNS forwarder and the tailnet-wide MagicDNS configuration. You should see your tailnet's DNS name, the list of search domains (including your MagicDNS domain), and confirmation that MagicDNS is active.

All five of your renamed devices should be resolvable through MagicDNS. The search domains allow you to use just the short name (like ai-server) instead of the full ai-server.<tailnet>.ts.net.

  • Confirm the output shows MagicDNS is active and your tailnet DNS name is listed.

Not seeing MagicDNS in the output?

The tailscale dns command requires Tailscale v1.74.0 or later. Check your version with tailscale version and update if needed.

Help me figure out why tailscale dns status is not showing my MagicDNS configuration on Windows.

Your Tailnet now has human-readable names for every device, and your Windows laptop is sharing files securely to your iPhone over HTTPS. Next up, you will deploy an AI model on your EC2 instance and make it accessible to every device using these same MagicDNS names.

Deploy Ollama AI Server on EC2 with Tailscale Serve

Your 5-device Tailnet is connected and every device is reachable by name. Now it's time to put that network to real use by deploying a private AI server.

You'll install Ollama on your ai-server EC2 instance, configure it for network access, then use Tailscale Serve to expose the AI endpoint exclusively to your Tailnet with automatic HTTPS. No port is opened to the public internet.

In this step, get ready to:
  • Install Ollama and configure it for network access on your EC2 instance.
  • Expose the Ollama API over your Tailnet using Tailscale Serve with automatic TLS.
  • Query the AI server from your iPhone, Raspberry Pi, and Windows laptop to verify it works.
Install and configure Ollama on your AI server

Ollama is an open-source tool for running large language models locally. You'll install it on the ai-server EC2 instance and configure it to accept connections from Tailscale Serve.

  • SSH into your ai-server EC2 instance over your Tailnet by running this command from your Windows laptop terminal:
ssh -i [[PEM_PATH="path/to/your-key.pem"]] ec2-user@ai-server

Why SSH over MagicDNS?

Because both your laptop and the EC2 instance are on the same Tailnet, MagicDNS resolves ai-server to the instance's Tailscale IP automatically. You don't need the AWS public IP anymore.

  • Install Ollama using the official install script by running:
curl -fsSL https://ollama.com/install.sh | sh

What does this command do?

This downloads and executes Ollama's install script. It installs the ollama binary and sets up a systemd service so Ollama starts automatically.

  • Verify Ollama is running by checking its local health endpoint:
curl http://localhost:11434
  • You should see the response Ollama is running in your terminal.

Don't see that response?

Wait about 10 seconds after the install script finishes. The systemd service may need a moment to start. If it still doesn't respond, run sudo systemctl status ollama to check whether the service is active.

Help me troubleshoot this

Ollama is installed, but by default it only listens on 127.0.0.1 (localhost). To let Tailscale Serve proxy traffic to it reliably, you'll configure it to bind to all network interfaces.

  • Open the systemd service override editor by running:
sudo EDITOR=nano systemctl edit ollama.service

What does this command do?

This opens a blank override file for the Ollama service using the nano text editor. Any settings you add here take priority over Ollama's default configuration without modifying the original service file.

  • In the nano editor that opens, paste the following two lines:
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

What does this configuration do?

Setting OLLAMA_HOST=0.0.0.0:11434 tells Ollama to listen on all network interfaces instead of just localhost. This ensures Tailscale Serve can proxy incoming HTTPS requests to Ollama's port.

  • Save the file by pressing Ctrl+O, then Enter, then Ctrl+X to exit nano.
  • Restart the Ollama service to pick up the new configuration:
sudo systemctl restart ollama
  • Confirm Ollama is still running after the restart:
curl http://localhost:11434
  • You should see Ollama is running again.

Service failed to restart?

A common cause is a typo in the override file. Run sudo systemctl status ollama to see the error. Double check that the [Service] header is present and the Environment line uses straight double quotes, not curly quotes.

Help me diagnose the issue

Now pull a small language model so Ollama has something to run.

  • Download the tinyllama model by running:
ollama pull tinyllama
  • Wait for the download to complete. This pulls approximately 600MB of model data.
  • Verify the model is available by running:
ollama list
  • You should see tinyllama listed in the output.

Model download failing or timing out?

The t3.medium instance has 4GB of RAM, which is enough for tinyllama. If the download stalls, check that your EC2 instance has internet access through its VPC's internet gateway. Run ping google.com to confirm network connectivity.

Help me fix the download issue

Expose Ollama over your Tailnet with Tailscale Serve

Ollama is running locally on your EC2 instance, but other Tailnet devices can't reach it yet. Tailscale Serve creates an HTTPS endpoint on your Tailnet that proxies traffic to Ollama's local port.

  • Still on your ai-server SSH session, expose Ollama's port to your Tailnet by running:
sudo tailscale serve --bg 11434

What does this command do?

This tells Tailscale to proxy HTTPS traffic arriving at https://ai-server.<tailnet>.ts.net to localhost:11434 on this machine. Tailscale automatically provisions a TLS certificate for the hostname.

The --bg flag runs the serve process in the background so it persists across reboots.

  • Verify the serve endpoint is active by running:
sudo tailscale serve status
  • You should see output indicating that port 443 is being proxied to http://127.0.0.1:11434.

Why Tailscale Serve instead of opening the port publicly?

Exposing Ollama directly to the internet is a serious security risk. Security researchers have found over 1,000 unprotected Ollama servers exposed online.

Tailscale Serve keeps the AI endpoint accessible only to devices on your Tailnet. No firewall rules, no public IP exposure, and automatic HTTPS encryption.

Getting a certificate error?

Tailscale Serve requires HTTPS certificates to be enabled for your Tailnet. Since you already used tailscale serve on your Windows laptop in the previous step, certificates should already be enabled. If prompted, accept the option to enable HTTPS certificates.

Help me enable HTTPS certificates

Query the AI server from all your devices

The real test is whether every device on your Tailnet can reach the AI server by its friendly hostname. You'll send a chat request from three different devices.

  • On your Raspberry Pi (rpi-home), open a terminal and query the AI server by running:
curl https://ai-server.[[TAILNET="your-tailnet-name"]].ts.net/api/chat -d '{"model":"tinyllama","messages":[{"role":"user","content":"Hello"}],"stream":false}'

What does this request do?

This sends a POST request to Ollama's chat API through the Tailscale Serve HTTPS endpoint. The stream field set to false tells Ollama to return the complete response in one JSON object instead of streaming tokens.

  • You should see a JSON response containing a message field with the AI's reply. The first request may take 20-30 seconds as the model loads into memory.
  • On your Windows laptop, open PowerShell and run the same query:
curl.exe https://ai-server.[[TAILNET="your-tailnet-name"]].ts.net/api/chat -d '{"model":"tinyllama","messages":[{"role":"user","content":"Hello"}],"stream":false}'

Why curl.exe instead of curl?

In PowerShell, curl is an alias for Invoke-WebRequest, which has different syntax. Using curl.exe calls the real Windows curl binary with the same flags as Linux.

  • You should see the same JSON response with the AI's generated message.
  • On your iPhone, open Safari and navigate to:
https://ai-server.[[TAILNET="your-tailnet-name"]].ts.net
  • You should see the text Ollama is running displayed in Safari. This confirms your iPhone can reach the AI server through your Tailnet.

Full chat from iPhone

Safari can only send GET requests, which is enough to verify connectivity. To send a full POST request and get a generated AI answer on your iPhone, use a free REST client app from the App Store and send the same JSON body you used with curl.

Not getting a response from a device?

Make sure the device has Tailscale connected and active. On iPhone, open the Tailscale app and confirm the VPN toggle is on. On the Pi, run tailscale status to confirm it's connected.

If you see a timeout, the model may still be loading. The first request after a cold start can take 30+ seconds on a t3.medium instance.

Help me troubleshoot device connectivity

✔️ Awesome, I've got everything!

Great. Double check you've saved the override file and that tailscale serve --bg 11434 is active on your ai-server.

ⓧ I'd like to double check the full code

Here is the complete contents of the systemd override file on your ai-server:

[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

Your AI server is live and securely accessible from every device on your Tailnet. Next up, you'll configure DNS so your Tailnet can resolve both private AWS hostnames and use a global nameserver for everything else.

Configure Split DNS and NextDNS Global Nameserver

Your AI server is running and every device can reach it by name. But right now, your Tailnet only resolves MagicDNS hostnames.

What about resolving private AWS hostnames from your Tailnet devices, or filtering all DNS traffic through a security-focused resolver? In this step, you will configure split DNS so your Tailnet can resolve private AWS VPC hostnames, then add NextDNS as a global nameserver to filter and secure all DNS queries across your network.

In this step, get ready to:
  • Add a restricted nameserver for AWS VPC internal DNS resolution (split DNS).
  • Create a NextDNS account and add it as the global nameserver for your Tailnet.
  • Verify DNS configuration across multiple devices.
Add a restricted nameserver for AWS VPC DNS

Your EC2 instances live inside an AWS VPC that has its own internal DNS. Private hostnames like ip-10-0-1-25.ec2.internal only resolve within that VPC. Split DNS tells Tailscale to route queries for a specific domain to a specific nameserver instead of the default.

What is split DNS?

Split DNS lets you send DNS queries for certain domains to specific nameservers while sending everything else to the default resolver. This way, your Tailnet devices can resolve private AWS hostnames without exposing those queries to public DNS.

First, you need to find your VPC's DNS resolver IP address. In AWS, the VPC DNS resolver always lives at the base of your VPC's CIDR block plus two.

  • Return to the AWS Console and open the VPC service.
  • Click Your VPCs in the left sidebar.
  • Find the VPC your EC2 instances are in and note its IPv4 CIDR value (e.g., 10.0.0.0/16 or 172.31.0.0/16 for the default VPC).
  • Add 2 to the base of that CIDR to get your DNS resolver IP. For example, 10.0.0.0/16 becomes 10.0.0.2, and 172.31.0.0/16 becomes 172.31.0.2.

Universal VPC DNS address

AWS also provides a universal resolver at 169.254.169.253 that works from any instance regardless of your VPC CIDR. You can use either address as the restricted nameserver.

Now you need to determine your VPC's internal domain. This depends on your AWS region.

  • If your instances are in us-east-1, the internal domain is ec2.internal.
  • For all other regions, the internal domain is <region>.compute.internal (e.g., us-west-2.compute.internal).

With both values in hand, configure split DNS in the Tailscale admin console.

  • Switch back to the Tailscale admin console in your browser.
  • Open the DNS page from the top navigation.
  • Scroll down to the Nameservers section.
  • Click Add nameserver and select Custom.
  • Enter your VPC DNS resolver IP (e.g., 10.0.0.2 or 172.31.0.2).
  • Enable Restrict to search domain.
  • Enter your VPC's internal domain (e.g., ec2.internal or us-west-2.compute.internal).
  • Click Save.

What does this do?

Only DNS queries ending in your VPC's internal domain get routed to the AWS resolver. All other queries continue to use the default MagicDNS or global nameserver. This prevents internal AWS hostnames from leaking to public DNS.

You should see the new restricted nameserver listed under the Nameservers section with the domain restriction shown beside it.

Don't see a Restrict to search domain option?

Make sure you selected Custom from the Add nameserver dropdown. The restrict option only appears for custom nameserver entries, not for the built-in options like NextDNS or Cloudflare.

Help me configure split DNS in the Tailscale admin console.

Create a NextDNS account and add it as the global nameserver

Split DNS handles private domains. For everything else, you want a global nameserver that filters malicious domains, blocks trackers, and secures all DNS traffic across your Tailnet. NextDNS is a personalized DNS service with a native Tailscale integration.

Why NextDNS?

Tailscale has a built-in NextDNS integration that uses DNS over HTTPS (DoH) for encrypted queries. NextDNS offers a free tier with up to 300,000 queries per month, which is more than enough for this project. Once configured, every device on your Tailnet automatically routes DNS through NextDNS without installing anything extra on each device.

Start by creating your NextDNS account and grabbing your profile's IPv6 address.

  • Open a new browser tab and go to https://my.nextdns.io.
  • Click Sign up and create a free account using your email.
  • After signing in, NextDNS creates a default profile (configuration) automatically.
  • Click the Setup tab in the NextDNS dashboard.
  • Scroll down to the Endpoints section.
  • Find and copy one of the IPv6 addresses listed there. It will look something like 2a07:a8c0::xx:xxxx where the last portion is unique to your profile.

Keep this tab open

Leave the NextDNS tab open. You will need the IPv6 address in the next step when configuring Tailscale.

Now add NextDNS to Tailscale as your global nameserver.

  • Switch back to the Tailscale admin console DNS page.
  • In the Nameservers section, click Add nameserver.
  • Select NextDNS from the dropdown list.
  • Paste the IPv6 address you copied from the NextDNS Endpoints section.
  • Click Save. The NextDNS profile saves as a global nameserver for your profile ID.
  • Scroll to the Global nameservers area and enable Override DNS servers.

What does Override DNS servers do?

When enabled, this forces all Tailnet devices to use the Tailscale-configured DNS (NextDNS) instead of whatever DNS is set locally on each device. Without this, devices might bypass NextDNS and use their own local resolver.

Your DNS page should now show NextDNS as a global nameserver and your AWS VPC DNS resolver as a restricted nameserver.

NextDNS not appearing in the dropdown?

The NextDNS option requires Tailscale v1.32 or later on your devices. If you do not see NextDNS in the dropdown, check that your Tailscale installation is up to date.

Help me add NextDNS as a global nameserver in the Tailscale admin console.

Verify DNS configuration on multiple devices

With both nameservers configured, use the tailscale dns status command to confirm everything is active. This command is available in Tailscale v1.74.0 and later.

  • On your Windows laptop, open PowerShell and run this command:
tailscale dns status
  • Check the output for your NextDNS global nameserver and the restricted nameserver for your VPC domain.

What should I see?

The output shows your active DNS configuration. Look for the NextDNS address listed as a global nameserver and your VPC DNS resolver IP listed as a restricted nameserver with the domain restriction beside it. MagicDNS should also show as enabled.

  • SSH into your Raspberry Pi and run the same check:
tailscale dns status
  • Verify the output matches what you saw on your Windows laptop. Both devices should show the same global nameserver and restricted nameserver configuration.
  • Test that regular DNS resolution still works by pinging a public domain from your Raspberry Pi:
ping -c 2 google.com

If the ping resolves and returns packets, your devices are routing DNS queries through NextDNS successfully.

  • Verify MagicDNS still works by pinging a Tailnet device by name:
tailscale ping ai-server

You should still get a successful response from the AI server, confirming that MagicDNS hostnames resolve alongside your new DNS configuration.

DNS queries timing out?

If DNS resolution stops working after enabling Override DNS servers, double-check that the NextDNS IPv6 address you entered is correct. Return to the NextDNS Setup tab and compare the Endpoints section against what you pasted in Tailscale.

If only VPC internal hostnames fail to resolve, confirm the restricted nameserver IP matches your VPC's DNS resolver (CIDR base + 2) and the domain matches your region's format.

Help me troubleshoot my Tailscale DNS configuration not resolving correctly.

Your Tailnet now has a layered DNS setup. MagicDNS handles device-to-device resolution, split DNS routes private AWS queries to the VPC resolver, and NextDNS secures all other traffic. Next up, you will invite a second user and write a tailnet policy to control who can access what.

Invite a Second User and Write a Multi-User Tailnet Policy

Your private AI network is fully operational with DNS, HTTPS services, and five connected devices. The final piece is access control.

Right now, every device on your Tailnet can reach every other device on any port. In a real network, you want different users to have different levels of access. In this step, you will invite a second user, assign them a restricted role, and write a tailnet policy file using grants to enforce least-privilege access.

In this step, get ready to:
  • Invite a second user to your Tailnet with the Member role.
  • Write a tailnet policy file with groups, tags, and differentiated grants.
  • Apply tags to devices and verify role-based access control works.
Invite a second user as a Member

Tailscale has two layers of access control. User roles control who can access the admin console. Grants in the policy file control which devices can talk to each other on the network.

The Member role blocks admin console access while still allowing the user to connect devices and use network resources that grants permit.

  • In the Tailscale admin console, open the Users page.
  • Click Invite external users.
  • Enter the second person's email address.
  • In the role dropdown, select Member.
  • Click Invite to send the invitation.

What is the Member role?

Tailscale has three basic user roles: Owner, Admin, and Member. Members cannot access the admin console, but they can connect devices to the Tailnet and reach resources as permitted by the policy file.

This two-layer model (role for console access, grants for network access) is Tailscale's recommended approach for least-privilege security.

Now the second user needs to accept the invitation and join your Tailnet.

  • Have the second user check their email and accept the invite.
  • Have them install Tailscale on their device and sign in with the same email they were invited with.
  • Return to the admin console Users page and confirm the new user appears with the Member role.

Verify the role separation works correctly.

  • Confirm you (the Owner/Admin) can still access all admin console pages.
  • Have the Member user attempt to navigate to the admin console. They should be blocked from accessing it.

Member can still access the admin console?

Double-check that you selected Member (not Admin) when sending the invitation. You can change a user's role from the Users page by selecting a new role in that user's row.

If the role is correct, ask the Member to sign out and sign back in to refresh their session.

Why can my invited user still see the admin console?

Write the tailnet policy file with groups, tags, and grants

The tailnet policy file is where you define who can access what on your network. You will define groups to organize users, tags to categorize devices, and grants to control which traffic is allowed.

  • In the Tailscale admin console, open the Access controls page.
  • Replace the entire contents of the policy file with the following:
{
  // Define user groups for role-based access
  "groups": {
    "group:admins": ["[[ADMIN_EMAIL=\"your-admin-email@example.com\"]]"],
    "group:readers": ["[[MEMBER_EMAIL=\"member-email@example.com\"]]"]
  },

  // Define who can assign each tag to devices
  "tagOwners": {
    "tag:ai-server": ["group:admins"],
    "tag:personal-device": ["group:admins"],
    "tag:infra": ["group:admins"]
  },

  // Network access grants (deny-by-default, explicit allow)
  "grants": [
    {
      // Admins get full access to all devices on all ports
      "src": ["group:admins"],
      "dst": ["*"],
      "ip": ["*"]
    },
    {
      // Readers can only access AI server and shared files on HTTPS
      "src": ["group:readers"],
      "dst": ["tag:ai-server", "tag:personal-device"],
      "ip": ["tcp:443"]
    }
  ],

  // SSH access rules (separate from grants)
  "ssh": [
    {
      "action": "accept",
      "src": ["group:admins"],
      "dst": ["tag:ai-server", "tag:infra"],
      "users": ["ec2-user", "root"]
    }
  ]
}

What does this policy do?

The policy has four sections that work together:

  • groups organizes users by role. Your email goes in group:admins and the Member's email goes in group:readers.
  • tagOwners defines three tags (tag:ai-server, tag:personal-device, tag:infra) and declares that only admins can assign them.
  • grants controls network traffic. Admins get unrestricted access to all ports. Readers can only reach tag:ai-server and tag:personal-device on TCP port 443 (HTTPS only).
  • ssh is a separate section that controls Tailscale SSH. Only admins can SSH into tagged server and infrastructure devices.

Why grants instead of ACLs?

Grants are the recommended approach for all new tailnet policies. They offer cleaner syntax by separating destinations from port specifications in the ip field.

Grants also support additional features like the via field for routing control through subnet routers. Each grant has an implied accept action, so you never need to write an explicit action field.

  • Replace your-admin-email@example.com with the email address you use to sign in to Tailscale.
  • Replace member-email@example.com with the email address of the Member you just invited.
  • Click Save at the bottom of the Access controls page.

Seeing a syntax error when saving?

The policy file uses HuJSON (JSON with comments). Make sure every string is wrapped in double quotes and all brackets are properly closed.

Check that your email addresses are complete (including the domain) and enclosed in quotes inside the arrays.

Help me fix a syntax error in my Tailscale tailnet policy file.

Apply tags to devices and verify access control

Tags only take effect once you apply them to devices. You will tag your devices through the admin console, then test that the Member can only access what the grants allow.

  • In the admin console, open the Machines page.
  • Find the ai-server row, click the menu icon at the far right, and select Edit tags.
  • Add tag:ai-server and click Save.
  • Repeat for general-ec2: add tag:infra.
  • Repeat for rpi-home: add tag:infra.
  • Repeat for my-laptop (your Windows laptop): add tag:personal-device.

What happens when you tag a device?

When you apply a tag, the device's identity changes from user-owned to tag-owned. The grants in your policy now control access to that device based on its tag rather than the user who authenticated it.

Tags must be defined in tagOwners before you can apply them. You already did this when you saved the policy file.

Now verify that the policy enforces different access levels for Admin and Member.

First, test the Member's access (have the Member run these from their device):

  • Have the Member open a browser and navigate to https://ai-server.<tailnet>.ts.net. They should see a response from Ollama.
  • Have the Member navigate to https://my-laptop.<tailnet>.ts.net. They should see the shared folder contents.

Next, verify the Member is blocked from infrastructure access:

  • Have the Member attempt to SSH into the ai-server EC2 instance. The connection should be refused.
  • Have the Member attempt to reach port 11434 directly (e.g., http://ai-server.<tailnet>.ts.net:11434). The connection should be refused because the grants only permit tcp:443.

Finally, confirm your Admin account retains full access:

  • From your Windows laptop, SSH into the ai-server. The connection should succeed.
  • From your Windows laptop, access port 11434 directly on the ai-server. You should receive a response from the Ollama API.

Member can still SSH into EC2?

Make sure you saved the policy file after editing it. Changes only take effect after saving.

Confirm the ai-server and general-ec2 devices actually have their tags applied. Check the Machines page and look for the tag label on each device row.

If the Member's device was already connected before you applied the policy, have them disconnect and reconnect Tailscale to pick up the new rules.

Why can my Member user still access infrastructure devices after I applied tags and grants?

Your private AI network now has proper role-based access control. The Admin can manage all devices and ports, while the Member can only browse shared files and query the AI server over HTTPS. Next up, you will extend this network even further in the Secret Mission by configuring a subnet router for private VPC access.

Secret mission

Configure a Subnet Router for Private VPC Access

Your Tailnet devices can already reach each other directly. But what about the private resources inside your AWS VPC that don't have Tailscale installed? In this secret mission, you'll turn the general-ec2 instance into a subnet router, enabling any Tailnet device to reach private VPC IPs without Tailscale on those resources. Then you'll lock it down so only Admins can route through it.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. The t3.medium EC2 instance costs approximately $0.04/hour while running, so leaving it on will accumulate charges.

Cost warning

The t3.medium instance (ai-server) costs approximately $0.04/hour. If you forget to stop or terminate it, this adds up to roughly $30/month.

The t2.micro instance (general-ec2) is free tier eligible, but always confirm your AWS free tier status. Always stop or terminate your EC2 instances when you are done with this project.

Resources you used:

  • 2x AWS EC2 instances (ai-server on t3.medium, general-ec2 on t2.micro).
  • NextDNS profile (free tier, configured as Tailscale global nameserver).
  • Ollama model downloaded on the ai-server EBS volume.
  • Tailscale Serve configuration on your Windows laptop (sharing a folder).
  • Tailscale devices added to your tailnet (Raspberry Pi 5, iPhone, 2x EC2 instances).
  • Second user (Member) invited to your tailnet.
  • Tailnet policy file modifications (groups, tags, autoApprovers, grants).
  • Split DNS and global nameserver configuration in the Tailscale admin console.

Keep everything running

No action needed. Choose this if you are still actively building on your private AI network or plan to continue experimenting with Tailscale features.

  • Your ai-server EC2 instance will continue to cost approximately $0.04/hour (roughly $1/day).
  • Your general-ec2 instance is free tier eligible, so no charges while it remains within free tier limits.
  • Tailscale, NextDNS (free tier), and Ollama will continue running at no additional cost.
  • The invited Member user will retain access to your tailnet as defined by your policy file.
  • The reusable auth key remains active in your Tailscale settings. Revoke it if you do not plan to add more devices.

Pause - I'll come back to this later

Stop your EC2 instances and Tailscale Serve to free up costs and resources. Your configuration and files remain intact for when you return.

  • In the AWS Console, navigate to the EC2 dashboard.
  • Select both instances (ai-server and general-ec2).
  • Click Instance state, then select Stop instance.
  • On your Windows laptop, stop the folder share by running the following command in PowerShell:
tailscale serve reset

What does stopping do?

Stopped EC2 instances do not incur compute charges. You will still pay a small amount for EBS storage (the disk volumes attached to each instance).

Your Tailscale devices will show as offline in the admin console but remain registered. When you start the instances again, they reconnect to your tailnet automatically.

Delete - I don't want to use this again

Remove all project resources completely. This cannot be undone.

Terminate EC2 instances
  • In the AWS Console, navigate to the EC2 dashboard.
  • Select the ai-server instance.
  • Click Instance state, then select Terminate instance.
  • Repeat for the general-ec2 instance.
  • Confirm termination when prompted. This permanently deletes the instances and their attached EBS volumes.
Stop Tailscale Serve on your Windows laptop
  • On your Windows laptop, clear the Tailscale Serve configuration by running this in PowerShell:
tailscale serve reset
Remove devices from your tailnet
  • Open the Tailscale admin console.
  • Go to the Machines page.
  • For each device you want to remove (ai-server, general-ec2, rpi-home, my-iphone), select the menu next to the device.
  • Select Remove.
  • In the confirmation dialog, select Remove machine.
  • Your Windows laptop (my-laptop) should remain on the tailnet since Tailscale was installed from a previous project.
Remove the invited user
  • In the Tailscale admin console, open the Users page.
  • Find the Member user you invited.
  • Select the menu next to their name and remove them from the tailnet.
Revert your tailnet policy file
  • In the Tailscale admin console, open the Access Controls page.
  • Remove the groups, tags, autoApprovers, and grants you added during this project.
  • Save the policy file to apply the changes.
Remove DNS configuration
  • In the Tailscale admin console, open the DNS page.
  • Remove the split DNS entry for the AWS VPC domain.
  • Remove NextDNS as the global nameserver.
  • Disable Override DNS servers if you enabled it.
Delete the NextDNS profile
  • Log in to your NextDNS dashboard at my.nextdns.io.
  • Select the profile you created from the top-left dropdown menu.
  • Navigate to Settings within the profile.
  • Scroll to the bottom and click Delete Profile.
Revoke the Tailscale auth key
  • In the Tailscale admin console, go to Settings.
  • Open the Keys section.
  • Find the reusable auth key you generated and revoke it.

That's a Wrap!

That's a Wrap!

Nice work! You just built a private AI network spanning 5 devices across your home, cloud, and mobile.

You've learned how to:

  • Connected 5 devices to a single Tailnet with custom MagicDNS hostnames, then deployed a self-hosted Ollama AI server on EC2 secured with Tailscale Serve for private HTTPS access from any device.
  • Configured split DNS for AWS VPC internal resolution and NextDNS as a global nameserver, giving your entire Tailnet consistent and secure DNS.
  • Invited a second user as a Member and wrote a multi-user tailnet policy using grants to enforce role-based access control. Your Admin gets full access while the Member can only query the AI and browse shared files.
  • Secret Mission: Configured a subnet router for private VPC access with route authorization and policy-based access control via the via field, restricted to Admin-only access.

Ready to quiz yourself?