Set Up a Tailscale VPN Mesh Network

Connect two devices over an encrypted WireGuard mesh using Tailscale.

Introduction

30 Second Summary

Think about all the devices you own. Your phone, your laptop, maybe a desktop at home. What if they could all talk to each other securely from anywhere in the world without complicated network settings or open ports?

In this project, you will set up a private VPN mesh network using Tailscale that connects two of your devices with encrypted, zero-config networking. You'll prove the connection works by hosting a web page on one device and accessing it from the other.

What You'll Build

A secure Tailscale VPN mesh that connects two devices and serves private web content over encrypted HTTPS connections using MagicDNS and Tailscale Serve.

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

  • A Tailscale tailnet with two devices connected in a secure WireGuard mesh.
  • A simple web page hosted on your server device, exposed privately using Tailscale Serve and MagicDNS hostnames.
  • A verified encrypted HTTPS connection from your client device to your server, proving your VPN works across any network.
  • Secret Mission: Send files directly between your devices using Taildrop, proving your tailnet works for more than just web traffic.

Are there any prerequisites?

You need two devices to complete this project. This can be two computers, or one computer and a phone. They do not need to be on the same local network.

You also need an email account with Google, Microsoft, Apple, or GitHub for Tailscale's SSO login.

Set Up Your Tailscale Account

Tailscale is a mesh VPN that creates encrypted connections between your devices using WireGuard. Unlike traditional VPNs that route all traffic through a central server, Tailscale connects your devices directly to each other.

In this step, you'll create a free Tailscale account, install it on your first device (your "server"), and confirm it's connected to your personal network called a tailnet.

In this step, get ready to:
  • Create a Tailscale account using SSO.
  • Install Tailscale on your server device.
  • Verify your device appears in your tailnet.
Create a Tailscale account

Tailscale uses your existing identity provider to create your account. No new password to remember.

  • Open your web browser and go to https://login.tailscale.com/start.
  • Sign up using one of the SSO identity providers: Google, Microsoft, Apple, or GitHub.
  • On the "Welcome to Tailscale" page, select Personal use.

What just happened?

Tailscale created a tailnet for you. A tailnet is your own private network that only your devices can join. Think of it as your personal, encrypted internet that follows you everywhere.

The Personal plan is free forever and supports unlimited devices. You'll never be charged for this project.

Why Tailscale over a manual WireGuard setup?

A manual WireGuard VPN requires you to open firewall ports, manage cryptographic keys, and configure IP routing on every device. Tailscale handles all of this automatically. You get the same encryption with zero configuration.

Install Tailscale on your server device

Your server device is the computer where you'll host content later in the project. It needs Windows 10 or later.

  • On your server device, open your browser and go to https://tailscale.com/download.
  • Download the Windows .exe installer.
  • Run the installer by double-clicking the downloaded file.
  • After installation completes, a Tailscale icon appears in your system tray (bottom-right corner of your taskbar).
  • Right-click the Tailscale icon in the system tray.
  • Select Log in.
  • Your browser opens. Sign in with the same SSO provider you used to create your account.

What does logging in do?

When you log in, Tailscale registers this device in your tailnet. It assigns the device a stable 100.x.x.x IP address that stays the same no matter what Wi-Fi network you're on.

Don't see the Tailscale icon in the system tray?

Click the small up arrow (^) next to your system tray to reveal hidden icons. Windows sometimes hides new tray icons by default.

If you still don't see it, try restarting your computer. The Tailscale service starts automatically after a reboot.

Help me find the Tailscale icon on my Windows taskbar.

Verify your device appears in the admin console

The admin console is your dashboard for managing all devices in your tailnet.

  • Open your browser and go to https://login.tailscale.com/admin/machines.
  • Look for your server device in the list. It appears with your computer's hostname as its machine name.
  • Confirm it shows a 100.x.x.x IP address in the row next to the machine name.

What is this 100.x.x.x address?

Tailscale assigns each device a unique IP in the 100.x.y.z range. This is your device's stable address on your tailnet. Other devices in your tailnet will use this address (or the machine name via MagicDNS) to connect to it.

Device not showing up in the admin console?

Make sure you completed the login step. Right-click the Tailscale icon in your system tray and check if it says "Connected" or "Log in". If it says "Log in", you still need to authenticate.

Also confirm you're checking the admin console with the same account you used to sign in on the device. If you have multiple Google or Microsoft accounts, you may have signed in with different ones.

My device isn't appearing in the Tailscale admin console after installing and logging in.

Your server device is now part of your tailnet. Next up, you'll add a second device and prove that the two can communicate securely over Tailscale's encrypted mesh.

Add Your Client Device to the Tailnet

Your server device is connected to your Tailscale tailnet. Right now it is a network of one, which is not very useful.

In this step, you will install Tailscale on a second device (your "client") and prove that the two devices can communicate securely over the encrypted mesh. By the end, a simple ping command will confirm that your private network spans both machines.

In this step, get ready to:
  • Install Tailscale on your client device and log in with the same SSO credentials.
  • Verify both devices appear in the Tailscale admin console.
  • Test the connection by pinging the server from the client using MagicDNS.
Install Tailscale on your client device

Your client device is the second device you will use to access content hosted on your server. This can be another computer, a laptop, or even your phone.

  • On your client device, open a web browser and go to:
  • https://tailscale.com/download
  • Download the appropriate installer for your client device's operating system.
  • Run the installer.
  • After installation, a Tailscale icon appears in your system tray (Windows) or menu bar (macOS).
  • Right-click the Tailscale icon and select Log in.
  • Authenticate with the same SSO provider (Google, Microsoft, Apple, or GitHub) and the same account you used for your server device.

Why the same SSO account?

Both devices must be logged in with the same identity to join the same tailnet. A tailnet is your personal mesh network. Only devices authenticated under your account can see each other.

Don't see the Tailscale icon after installing?

On Windows, check the hidden icons area in your system tray (click the small arrow near the clock). On macOS, look in the top menu bar.

If you still cannot find it, try restarting your client device after installation.

Help me troubleshoot Tailscale not appearing after installation.

Verify both devices appear in the admin console

The admin console is your central dashboard for managing all devices on your tailnet. It shows each device's Tailscale IP address and machine name.

  • Open a browser on either device and navigate to:
  • https://login.tailscale.com/admin/machines
  • Confirm that both your server device and your client device are listed.
  • Each device has its own 100.x.x.x IP address and a machine name (usually your computer's hostname).

What are these 100.x.x.x addresses?

Tailscale assigns each device a stable 100.x.x.x IP address. These addresses stay the same no matter what physical network you connect to. They are internal to your tailnet and not reachable from the public internet.

Only see one device in the admin console?

Make sure you logged in on your client device with the exact same account (same SSO provider, same email). If you used a different email, the client joins a separate tailnet.

Also confirm the Tailscale icon on your client shows a connected state. Right-click the icon and check that it says "Connected" rather than "Disconnected".

Help me figure out why my second device is not appearing in the admin console.

Test the connection with ping

Seeing both devices in the console proves they have joined your tailnet. Now you will confirm they can actually communicate by pinging the server from the client.

  • On your client device, open a terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Ping your server device by its machine name by running this command:
ping [[SERVER_MACHINE_NAME="your server's machine name from the admin console"]]

What is MagicDNS?

MagicDNS automatically registers DNS names for every device in your tailnet. Instead of remembering 100.x.x.x IP addresses, you can reach any device by its friendly machine name (the hostname shown in the admin console).

MagicDNS is enabled by default for all tailnets. No manual configuration needed.

You should see reply lines appearing in your terminal, confirming that your client can reach your server over the encrypted tailnet.

  • Press Ctrl+C to stop the ping when you see several successful replies.

Ping not getting replies?

Double-check the machine name you typed. It must match the name shown in the admin console exactly (lowercase, no spaces).

On Windows, your firewall may block ping (ICMP) by default. Try accessing the server's Tailscale IP directly in a browser instead: type http://100.x.x.x (replacing with your server's actual Tailscale IP from the admin console). If the browser connects, your tailnet is working and the issue is just ICMP being blocked.

Help me troubleshoot why ping is not working between my Tailscale devices.

Both devices are now connected in your private WireGuard mesh and can reach each other by name. Next up, you will create a web page on your server and expose it securely to your tailnet.

Create and Serve Content on the Server

Both of your devices are now connected in a secure WireGuard mesh. You can ping between them using friendly hostnames instead of IP addresses.

Now you need something to prove that the connection works for real traffic. In this step, you will create a web page on your server device and make it securely available to your entire tailnet using Tailscale Serve.

In this step, get ready to:
  • Create a demo web page on your server device.
  • Start a local Python web server to host the page.
  • Use Tailscale Serve to expose your server over HTTPS to your tailnet.
Create a simple web page

You need a piece of content to serve over your VPN. A simple HTML page that confirms the connection is working will give you a clear visual signal when you access it from your client device.

  • On your server device, open a terminal (PowerShell on Windows, Terminal on macOS).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the project folder and move into it by running these commands:
mkdir tailscale-demo
cd tailscale-demo
  • Confirm the folder exists by running:
ls

You should see the empty tailscale-demo folder listed (or you are now inside it with no files yet).

  • Create a new file called index.html inside the tailscale-demo folder. Open it in any text editor (Notepad on Windows, TextEdit on macOS, or a code editor if you have one).
  • Paste the following HTML code into the file:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tailscale VPN Demo</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 600px;
            margin: 80px auto;
            padding: 20px;
            text-align: center;
            background-color: #f8f9fa;
        }
        .success-box {
            background-color: #d4edda;
            border: 2px solid #28a745;
            border-radius: 12px;
            padding: 40px;
            margin: 20px 0;
        }
        h1 {
            color: #155724;
            margin-bottom: 10px;
        }
        p {
            color: #333;
            font-size: 1.1em;
            line-height: 1.6;
        }
        .info {
            background-color: #e7f3ff;
            border: 1px solid #b8daff;
            border-radius: 8px;
            padding: 20px;
            margin-top: 20px;
            text-align: left;
        }
        .info h2 {
            color: #004085;
            font-size: 1em;
            margin-top: 0;
        }
        .info ul {
            color: #004085;
            padding-left: 20px;
        }
    </style>
</head>
<body>
    <div class="success-box">
        <h1>Connection Successful!</h1>
        <p>If you can see this page, your Tailscale VPN is working correctly.</p>
        <p>This content is being served securely over your private tailnet.</p>
    </div>
    <div class="info">
        <h2>What just happened:</h2>
        <ul>
            <li>Your client device connected to your server over Tailscale's encrypted WireGuard mesh</li>
            <li>MagicDNS resolved your server's hostname to its Tailscale IP</li>
            <li>Tailscale Serve proxied this page over HTTPS with an auto-provisioned TLS certificate</li>
            <li>No ports were opened on your firewall and no traffic left your private tailnet</li>
        </ul>
    </div>
</body>
</html>

What does this page do?

This HTML page has two sections. The green success-box div displays a clear "Connection Successful" message so you instantly know the VPN is working when you load the page from another device.

The blue info div explains what is happening behind the scenes. It describes the WireGuard mesh, MagicDNS resolution, HTTPS proxying, and the fact that no firewall ports were opened.

  • Save the file as index.html inside the tailscale-demo folder on your Desktop.
  • Back in your terminal, confirm the file was created by running:
ls

You should see index.html listed in the output.

Don't see index.html?

Make sure you saved the file inside the tailscale-demo folder on your Desktop, not somewhere else. Check that your terminal's current directory matches where you saved it by running pwd (macOS/Linux) or cd (Windows, with no arguments, prints the current directory).

If you used TextEdit on macOS, make sure you saved as plain text (Format > Make Plain Text) and used the .html extension.

help me find my index.html file

✔️ Awesome, I've got everything!

Great. Make sure you have saved index.html before moving on.

ⓧ I'd like to double check the full code

Here is the full index.html file for comparison:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tailscale VPN Demo</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            max-width: 600px;
            margin: 80px auto;
            padding: 20px;
            text-align: center;
            background-color: #f8f9fa;
        }
        .success-box {
            background-color: #d4edda;
            border: 2px solid #28a745;
            border-radius: 12px;
            padding: 40px;
            margin: 20px 0;
        }
        h1 {
            color: #155724;
            margin-bottom: 10px;
        }
        p {
            color: #333;
            font-size: 1.1em;
            line-height: 1.6;
        }
        .info {
            background-color: #e7f3ff;
            border: 1px solid #b8daff;
            border-radius: 8px;
            padding: 20px;
            margin-top: 20px;
            text-align: left;
        }
        .info h2 {
            color: #004085;
            font-size: 1em;
            margin-top: 0;
        }
        .info ul {
            color: #004085;
            padding-left: 20px;
        }
    </style>
</head>
<body>
    <div class="success-box">
        <h1>Connection Successful!</h1>
        <p>If you can see this page, your Tailscale VPN is working correctly.</p>
        <p>This content is being served securely over your private tailnet.</p>
    </div>
    <div class="info">
        <h2>What just happened:</h2>
        <ul>
            <li>Your client device connected to your server over Tailscale's encrypted WireGuard mesh</li>
            <li>MagicDNS resolved your server's hostname to its Tailscale IP</li>
            <li>Tailscale Serve proxied this page over HTTPS with an auto-provisioned TLS certificate</li>
            <li>No ports were opened on your firewall and no traffic left your private tailnet</li>
        </ul>
    </div>
</body>
</html>
Start a local web server

Python includes a built-in HTTP server that can serve files from any folder. This is the fastest way to get a web server running without installing anything extra.

  • In the same terminal (which should still be inside the tailscale-demo folder), start the web server by running:
python -m http.server 8080

What does this command do?

python -m http.server 8080 starts a simple HTTP server on port 8080. It serves every file in the current directory to anyone who connects. The terminal will stay occupied running the server until you stop it with Ctrl+C.

  • Verify the server is running by opening a web browser on your server device.
  • Navigate to http://localhost:8080 in the address bar.

You should see the green "Connection Successful" page you just created.

Page not loading at localhost:8080?

If the command failed with an error about python not being found, try python3 -m http.server 8080 instead. On macOS and Linux, the command is often python3.

If you see "Address already in use", another process is using port 8080. Try a different port like python -m http.server 8081 and adjust the port in later commands accordingly.

If Python is not installed on your system, download it from python.org/downloads and install it before continuing.

help me get the Python HTTP server running

Why Python instead of a full web server?

Python's built-in HTTP server requires zero installation on most systems and zero configuration. It keeps the focus on learning Tailscale networking rather than web server setup.

In production, you would use a proper web server like Nginx or Apache. For this demo, Python's one-liner is all you need to create real HTTP traffic across your tailnet.

Use Tailscale Serve to expose the server to your tailnet

Your Python server is only accessible at localhost:8080 right now. Other devices on your tailnet cannot reach it because the port is not exposed. Tailscale Serve solves this by proxying your local port over HTTPS and making it available at a memorable hostname.

  • Open a new terminal window on your server device. Keep the Python server running in the first terminal.
  • Expose your local server to your tailnet by running:
tailscale serve 8080

What does this command do?

tailscale serve 8080 tells Tailscale to proxy traffic from your local port 8080 and serve it over HTTPS to all devices in your tailnet. Tailscale automatically provisions a TLS certificate so the connection is encrypted end-to-end.

The command runs in the foreground and displays the URL where your server is now accessible. Press Ctrl+C when you want to stop serving.

If Tailscale prompts you to enable HTTPS certificates for your tailnet, follow the on-screen instructions to allow it. This is a one-time setup.

  • Look at the terminal output. You should see a message like:
Available within your tailnet:
https://server-machine-name.tailnet-name.ts.net

|-- / proxy http://127.0.0.1:8080

Press Ctrl+C to exit.

The HTTPS URL shown is how other devices in your tailnet will access your server. Note this URL down because you will use it in the next step.

Tailscale Serve not starting?

Make sure Tailscale is still connected. Right-click the Tailscale icon in your system tray (Windows) or menu bar (macOS) and confirm it shows as connected.

If you see a prompt about enabling HTTPS, follow it. Tailscale Serve requires HTTPS certificates to be enabled in your tailnet. The interactive CLI flow will guide you through enabling it.

If you get a permission error, try running the command as an administrator (right-click your terminal and select Run as administrator on Windows).

help me get Tailscale Serve working

Your server is now live on your private tailnet. In the next step, you will switch to your client device and access this page over the encrypted connection to prove the VPN works end-to-end.

Access the Server Content from Your Client Device

Your server is running, and Tailscale Serve is exposing it securely to your private tailnet. Now it's time for the payoff.

In this step, you'll switch to your client device and prove the whole system works. You'll load your web page over an encrypted connection and verify the HTTPS certificate that Tailscale provisioned automatically.

In this step, get ready to:
  • Access your web page from the client device using the Tailscale Serve URL.
  • Verify the HTTPS certificate is valid.
  • Test the connection from a different network.
Open the Tailscale Serve URL from the client

When you ran tailscale serve 8080 in the previous step, the terminal displayed an HTTPS URL. This is the address where your server's content is now accessible to any device in your tailnet.

  • Switch to your client device (your second computer or phone).
  • Open a web browser.
  • In the address bar, type the HTTPS URL that was displayed in your tailscale serve output on the server.

The URL follows this format:

https://server-machine-name.tailnet-name.ts.net
  • Replace server-machine-name with the machine name shown in the tailscale serve output (your server's hostname).
  • Replace tailnet-name with your tailnet's DNS name (also shown in the output).
  • Press Enter to navigate to the page.

You should see the "Connection Successful!" heading in a green box, along with a list of what just happened.

What just happened?

Your client device used MagicDNS to resolve the server's hostname to its Tailscale IP address. Then it connected through the WireGuard encrypted tunnel to reach your server.

Tailscale Serve proxied your local Python HTTP server over HTTPS, so the browser received a fully encrypted page without you configuring any certificates manually.

Page not loading?

  • Make sure Tailscale is connected on your client device. Check for the Tailscale icon in your system tray and confirm it shows "Connected".
  • Confirm the tailscale serve command is still running on your server. If you closed that terminal, re-run tailscale serve 8080 in a new terminal on the server.
  • Verify the Python HTTP server is still running on port 8080. If it stopped, navigate back to the tailscale-demo folder and run python -m http.server 8080 again.
  • Double-check the URL for typos. Copy it directly from the tailscale serve output if possible.

Help me troubleshoot why my Tailscale Serve page is not loading on my client device.

Verify the secure connection

Loading the page proves your tailnet works. Now confirm that the connection is encrypted with a valid TLS certificate.

  • Look at the address bar in your browser on the client device.
  • Confirm you see a lock icon (or shield icon, depending on your browser) to the left of the URL.
  • Click the lock icon to view certificate details.

The certificate is issued for your .ts.net domain. Tailscale automatically provisioned it from Let's Encrypt without any manual configuration on your part.

Why is there a valid HTTPS certificate?

Tailscale Serve automatically provisions TLS certificates from Let's Encrypt for your tailnet's DNS name. Your browser trusts these certificates just like any public website's certificate.

The connection between your devices is already encrypted by WireGuard at the network layer. The TLS certificate adds a second layer that your browser recognizes, so it shows the lock icon instead of a security warning.

Test from a different network (optional but recommended)

Tailscale creates direct peer-to-peer connections regardless of network location. This means your devices can reach each other even if they are on completely different networks.

  • If your client device is a phone, disconnect it from Wi-Fi.
  • Switch to mobile data (cellular).
  • Open the same HTTPS URL in your phone's browser.
  • Confirm that the "Connection Successful!" page loads.

Why does this work across different networks?

Traditional VPNs require port forwarding or firewall configuration to allow incoming connections. Tailscale uses NAT traversal to punch through firewalls automatically.

Your two devices find each other through Tailscale's coordination server, then establish a direct WireGuard tunnel between them. No traffic routes through a central server. The connection is peer-to-peer and fully encrypted.

Only have two computers on the same Wi-Fi?

You can simulate different networks by using a phone hotspot for one device. Alternatively, take your laptop to a coffee shop and try accessing the URL from there. The page loads just the same because Tailscale does not depend on your local network.

You just proved that your private VPN mesh works from anywhere. Your next challenge is the Secret Mission, where you'll send files directly between your devices using Taildrop.

Secret mission

Send Files Between Devices with Taildrop

Your tailnet can do more than serve web pages. Taildrop lets you send files directly between your devices over encrypted peer-to-peer connections, with no cloud intermediary and no file size limit. Prove your mesh network works for real-world file sharing.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on Tailscale's free Personal plan and local processes. There are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Python HTTP server running on port 8080 (server device).
  • Tailscale Serve session exposing your server to the tailnet (server device).
  • The tailscale-demo folder with index.html (server device).
  • Tailscale client installed on server device.
  • Tailscale client installed on client device.
  • Both devices registered in your tailnet (admin console).
  • Tailscale account (free Personal plan).

Keep everything running

No action needed. Choose this if you plan to keep using your tailnet for connecting devices securely.

  • Your Tailscale Serve session and Python HTTP server will continue running until you close the terminal windows or restart your computer.
  • Tailscale remains installed and connected on both devices. The Personal plan is free, so there are no charges.
  • You can add more devices to your tailnet at any time.

Pause - I'll come back to this later

Shut down running processes to free up system resources while keeping Tailscale installed for later use.

  • In the terminal running Tailscale Serve on your server device, press Ctrl+C to stop the serve session.
  • In the terminal running the Python HTTP server on your server device, press Ctrl+C to stop the server.
  • Leave Tailscale installed on both devices. It will idle in the background with minimal resource usage.
  • When you come back, restart both processes and your tailnet connection picks up immediately.

Delete - I don't want to use this again

Remove all project resources and uninstall Tailscale from both devices.

  • In the terminal running Tailscale Serve on your server device, press Ctrl+C to stop the serve session.
  • In the terminal running the Python HTTP server on your server device, press Ctrl+C to stop the server.
  • Delete the tailscale-demo folder on your server device. Open a terminal and run the command for your operating system:

Windows

Remove-Item -Recurse -Force ~\Desktop\tailscale-demo

macOS

rm -rf ~/Desktop/tailscale-demo
  • Remove both devices from your tailnet. Open the Machines page of the admin console at https://login.tailscale.com/admin/machines.
  • For each device, select the menu icon next to it, then select Remove.
  • In the confirmation dialog, select Remove machine.
  • Uninstall Tailscale from both devices. On each device, open Settings.
  • Select Apps, find Tailscale, and press Uninstall.
  • To delete your tailnet entirely, open the General settings page of the admin console at https://login.tailscale.com/admin/settings/general.
  • Select Delete tailnet, follow the confirmation steps, then select Delete to confirm.

Nice Work!

Nice Work!

You just built your own private VPN mesh network from scratch. Two of your devices are now securely connected over an encrypted WireGuard tunnel, and you proved it works by serving a web page from one device and viewing it from the other.

You've learned how to:

  • Created a Tailscale tailnet and connected two devices in a secure WireGuard mesh with zero manual key management or port forwarding.
  • Used MagicDNS and Tailscale Serve to expose a local web server privately over HTTPS with an auto-provisioned TLS certificate.
  • Accessed content on your server from your client device over an encrypted peer-to-peer connection that works across different networks without firewall configuration.
  • Sent files directly between devices with Taildrop, proving your tailnet handles more than just web traffic.

Ready to quiz yourself?