Post

Add Azure VM to Tailscale

Onboard an Azure VM to your tailnet, enable SSH without public exposure, and serve your HTTP app over HTTPS using Tailscale Serve + MagicDNS.

Add Azure VM to Tailscale

Overview

This guide walks you through adding an Azure VM to Tailscale, granting private access without public SSH/RDP, and enabling automatic HTTPS for a local service using Tailscale Serve and MagicDNS.

What You’ll Set Up

  1. Tailscale installed and the VM enrolled in your tailnet
  2. A shared-vm tag and basic ACL posture (optional but recommended)
  3. Tailscale SSH enabled (no public inbound needed)
  4. Device key expiry disabled to avoid surprise logouts
  5. HTTPS for your local web app on port 80 via tailscale serve --bg
  6. Tips for MagicDNS, testing, and hardening

Prerequisites

  • An Azure VM (Linux) with sudo privileges on the instance
  • Tailscale account with admin access to your tailnet
  • Ability to view/update Azure NSG rules (for cleanup/hardening)
  • (Optional) Tailnet policy access (to define tags/ACLs)

Step 1: Install & Enroll Tailscale on VM

From the Tailscale Admin ConsoleMachinesAdd deviceLinux server, generate and copy the install command.

  • (Optional): Add the shared-vm tag for access control and discovery governed by ACLs in Tailscale.
    1
    2
    
    curl -fsSL https://tailscale.com/install.sh | sh
    sudo tailscale up --auth-key=tskey-auth-XXXXXXXXXXXXXXXX --hostname=<vm-name>
    
  • Replace the auth key with one generated for your tailnet.
  • Use a clear hostname, e.g. vm-serve.
  • After enrollment, confirm the node appears under Machines.

Step 2: Enable Tailscale SSH (No Public Inbound)

Enable Tailscale-managed SSH on the VM:

1
sudo tailscale set --ssh

Verify who can SSH in your ACLs (Admin Console → Access controlsSSH rules). This removes the need for public port 22 in your NSG.


Step 3: Disable Device Key Expiry (Service VMs)

For long‑lived service VMs, disable key expiry so they don’t fall off the network:

  • In Admin Console → Machines → select your VM → Disable key expiry.

This might be handled automatically by your Admin Console settings.


Step 4: Serve HTTPS for Local Port 80 (Zero Config Web)

On the VM, if your app listens on http://localhost:80, enable HTTPS with one line:

1
sudo tailscale serve --bg http://localhost:80

This automatically:

  • Provisions a valid certificate for your MagicDNS host (e.g., vm-serve.<tailnet>.ts.net)
  • Terminates HTTPS (443) and proxies to http://localhost:80
  • Auto‑renews the cert; no tailscale cert or Nginx needed

Verify status:

1
tailscale serve status

Test from any device on your tailnet:

1
https://<vm-hostname>.<tailnet>.ts.net

The service is tailnet‑only. To share externally (internet), look into Tailscale Funnel.


Step 5: MagicDNS & Names

If MagicDNS is enabled, your node gets a stable FQDN such as:

1
vm-serve.<tailnet>.ts.net

Inside the tailnet, short names often resolve too:

1
ping vm-serve

You can rename the OS hostname if desired (hostnamectl set-hostname <newname>), but the Azure resource name itself cannot be changed directly (you’d recreate the VM to change the resource name).


Step 6: Hardening & Azure Cleanup

  • Close public SSH/RDP once Tailscale SSH works (update NSG).
  • Ensure only outbound UDP/41641 (Tailscale) is required publicly.
    • This ingress rule will guaruntee a direct peer-to-peer connection to improve performance rather than use default NAT traversal to connect.
  • Restrict who can access the VM via ACLs (groups/tags).
  • Monitor device health from Admin Console → Machines.
  • For web apps, keep the origin bound to localhost (not 0.0.0.0) when using Serve.

Troubleshooting

  • Can’t enroll / auth errors: Verify auth key scope and that the VM’s clock is correct (NTP).
  • serve errors (new CLI): Use new syntax tailscale serve --bg http://localhost:80. Check tailscale serve status.
  • Cert/HTTPS not working: Confirm you’re visiting the device’s MagicDNS name and that you’re on a tailnet‑connected device.
  • SSH denied: Review ACL SSH rules and confirm sudo tailscale set --ssh ran; check journalctl -u tailscaled for hints.

References

This post is licensed under CC BY 4.0 by the author.