Files

5.8 KiB

name, description, version, author, license, platforms, metadata
name description version author license platforms metadata
tailscale-infrastructure-access Set up Tailscale for private infrastructure access — install on servers, authenticate, standard for services that don't need public exposure. 1.1.0 ShoNuff MIT
linux
macos
hermes
tags
tailscale
vpn
networking
access
security

Tailscale Infrastructure Access

Standard for accessing services that don't need public internet exposure. Vaultwarden, future internal tools, and anything without a customer-facing requirement stays behind Tailscale.

Principles

  • No public ports for internal services — Tailscale-only, or SSH tunnel as fallback. No DNS records, no Let's Encrypt, no exposed endpoints for administrative tools.
  • One Tailscale network per infrastructure — All IT Pro Partner servers join the same tailnet. The user authenticates via their Google account on each device.
  • Zero-config mesh — Tailscale handles NAT traversal, DERP relays, and key exchange. No WireGuard configs, no port forwarding, no public IP needed.
  • UFW must allow the service port — Tailscale gets traffic to the server, but UFW/iptables still needs to permit the port. Tailscale does not bypass the local firewall. After setting up a Tailscale-only service, verify with ufw status and add the port if missing.

Installation (server side)

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up

This prints a one-time auth URL. Send it to the user to open in their browser (phone, laptop, any device logged into their Google account). The command times out after 15s if the URL isn't opened — that's expected, the URL is still valid. Just re-run tailscale up for a fresh URL.

Rename the server from the auto-generated hostname to a clean name:

tailscale set --hostname app1

Installation (client devices)

Device Download
macOS tailscale.com/download-mac
iPhone/iPad App Store → "Tailscale"
Linux `curl -fsSL https://tailscale.com/install.sh

On first launch, sign in with the same Google account used to authenticate the server. Devices auto-discover each other and appear in tailscale status.

Post-setup

After the server shows Connected in tailscale status:

# Verify connection — lists all connected devices
tailscale status

# Get the tailnet IP (100.x.x.x)
tailscale ip -4

# Access services via tailnet IP instead of public IP
# http://100.x.x.x:8080 → Vaultwarden
# ssh root@100.x.x.x → SSH via tailnet (if public SSH is closed later)

Deploying a Tailscale-only service (Vaultwarden pattern)

  1. Deploy the service to ~/docker/<service>/ with docker-compose.yml + .env + CHANGELOG.md
  2. Verify it works locally: curl -s http://localhost:<port>/
  3. Allow the port through UFW: ufw allow <port>/tcp comment '<description>'
  4. Set the service's DOMAIN / PUBLIC_URL / base URL to the Tailscale access URL — if using raw tailnet IP, set to http://100.x.x.x:port. If using Tailscale Serve for HTTPS (recommended — avoids browser mixed-content warnings on Vaultwarden), set it to the .ts.net URL:
    tailscale serve --bg --https 443 --set-path / http://127.0.0.1:<port>
    
    Then set DOMAIN to https://<hostname>.tail<random>.ts.net
  5. The service is now available at https://<hostname>.tail<random>.ts.net/ — valid HTTPS cert, tailnet-only
  6. Once confirmed working, close the UFW port so the service has zero public surface: ufw delete allow <port>/tcp
  7. Log the change: bash ~/.hermes/scripts/changelog.sh "Networking" "Tailscale Serve enabled for <service> at https://..."

Vaultwarden specific: The web vault is a JavaScript SPA that calls API endpoints from the browser. If DOMAIN is wrong or points to HTTP from HTTPS (or vice versa), the requests fail silently and the spinner never stops. Fix is always docker-compose restart with correct DOMAIN. Additionally, SIGNUPS_ALLOWED must be temporarily true for the first user to register, then locked to false. The admin panel at /admin uses ADMIN_TOKEN from .env.

Security consideration

Once Tailscale is operational and all team devices are connected, the public SSH port can be closed (UFW/iptables deny 0.0.0.0/0 :22, allow only tailnet subnet). This eliminates the public attack surface entirely.

Defer this until all team devices have Tailscale installed and confirmed working.

Service-access policy

Service Public Tailscale-only Why
Vaultwarden Passwords — zero public surface
Future internal tools Default for non-customer tools
admin-ai (LiteLLM) Hermes needs it from its own subnet
Hermes Telegram Needs internet for bot polling

Pitfalls

  • tailscale up prints an auth URL and times out after 15s — that's expected. The URL expires after a few minutes. Just re-run tailscale up to get a fresh one.
  • The tailscale IP (100.x.x.x) can change if the machine re-authenticates. Use the machine name (visible in tailscale status or the admin console) for a stable reference.
  • If the server shows "Logged out" after a reboot, run tailscale up again.
  • UFW does not auto-deny by default — a new service on a new port is accessible from the public internet unless explicitly blocked. For Tailscale-only services, confirm with ufw status that either the port is allowed (at minimum) or the port has a tailnet-only exception (ideal).
  • Vaultwarden's web vault needs the correct DOMAIN — it uses this to build API URLs. If users see a loading spinner that never resolves, check docker logs vaultwarden for POST /identity/connect/token requests, then fix DOMAIN and restart.
  • To rename a device in Tailscale: tailscale set --hostname <new-name>. Takes effect immediately in tailscale status and the admin console.