---
name: tailscale-networking-patterns
description: "Expose local services to the internet using Tailscale."
---

# Tailscale Networking Patterns

## Trigger
User wants to:
- Access a local file or webpage from their phone
- Share a self-hosted service with someone who is not on their Tailscale tailnet
- Get a public HTTPS URL for a local service quickly
- Preview a webpage, dashboard, or wiki on mobile

## Prerequisites
- Tailscale installed and running on the target machine
- `tailscale` CLI available (e.g., `which tailscale` returns a path)

## Pattern 1: Tailscale Funnel (Fastest)

Expose a local port to the public internet via a Tailscale-generated HTTPS URL.

```bash
# Start a local HTTP server on a free port (e.g., 8765)
python3 -m http.server 8765 --directory /path/to/files --bind 127.0.0.1

# In another terminal, create the funnel
sudo tailscale funnel --bg 8765
```

**Result:** A public URL like `https://hermes.tail7f9885.ts.net/` is printed.

**Access:** Anyone with the URL can access it. No Tailscale login required.

**To stop:**
```bash
sudo tailscale funnel --https=443 off
```

### Pitfalls
- Funnel requires `sudo` on most systems
- The URL is tied to your tailnet name and machine name — it is stable for that machine but not customizable without a DNS record
- Funnel exposes the port to the entire internet. Do not use for sensitive services without authentication.
- If the local server is bound to `127.0.0.1` only, Funnel can still reach it. If bound to a specific IP, ensure it matches.

## Pattern 2: Python HTTP Server + Funnel (One-Liner Workflow)

For quickly sharing a single file or directory:

```bash
# Serve current directory
cd /home/thesage && python3 -m http.server 8765 --bind 127.0.0.1 &
sudo tailscale funnel --bg 8765
```

**File access:** `https://your-url/filename.html`

## Pattern 2b: Permanent HTTP Server via systemd

When the user needs the URL to stay alive across reboots and crashes (e.g. a mobile web app they update frequently), wrap the Python server in a systemd service:

```bash
sudo tee /etc/systemd/system/tailscale-inbox.service > /dev/null << 'EOF'
[Unit]
Description=Tailscale Inbox HTTP Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=thesage
WorkingDirectory=/home/thesage/tailscale-inbox
ExecStart=/usr/bin/python3 -m http.server 8765 --bind 127.0.0.1
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now tailscale-inbox
```

**Check:** `sudo systemctl status tailscale-inbox` should show `active (running)`.

**Why this matters:**
- The funnel (`tailscale funnel --bg`) is already persistent once started.
- The local HTTP server is NOT persistent by default. If the machine reboots or the process dies, the funnel serves a broken backend until the server is restarted.
- A systemd service closes that gap and makes the entire chain reliable.

**Template:** See `templates/tailscale-inbox.service` for a ready-to-use unit file.

## Pattern 3: Alternative — Cloudflare Tunnel

If Tailscale Funnel is unavailable or the user wants a cleaner domain:

```bash
# Install cloudflared
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

# Login and create tunnel
cloudflared tunnel login
cloudflared tunnel create my-share
cloudflared tunnel route dns my-share my-share-yourname.trycloudflare.com
cloudflared tunnel run --url http://localhost:8765 my-share
```

**Pros:** Free random subdomain, no tailnet required, works from any machine
**Cons:** More setup, requires Cloudflare account

## Pattern 4: ngrok (Temporary / Testing)

```bash
ngrok http 8765
```

**Pros:** Instant, dead simple
**Cons:** Free tier URLs change every session, limited bandwidth

## Mobile Access Checklist

When the user wants to view something on their phone:
1. Start a local HTTP server on a fixed port
2. Ensure the file is in the served directory
3. Run `sudo tailscale funnel --bg <port>`
4. Copy the HTTPS URL and send it to the user
5. Remind them to open it on their phone browser

## Security Notes
- Funnel bypasses your firewall — the port is public
- Only funnel ports with non-sensitive content (wikis, previews, dashboards)
- Never funnel SSH (port 22), database ports, or admin panels without auth
- Use `tailscale funnel --bg` for short-lived shares; turn it off when done

## Related Skills
- `proxmox-homelab` — for creating the LXC/VM that runs the service
- `proxmox-selfhosting` — for deploying persistent services

## Pitfalls
- Forgetting to bind the HTTP server to `127.0.0.1` (it defaults to `0.0.0.0` which is less secure)
- Not telling the user to open the URL on their phone explicitly
- Leaving the funnel running indefinitely after the task is done
- Attempting Funnel on a machine without Tailscale installed