#tailscale#wireguard#vpn#mobile#self-hosting

Tailscale (and WireGuard) for accessing your self-hosted tracker on mobile

How to reach your homelab calorie stack from outside the LAN without exposing it to the internet.

The problem

Your tracker stack is on a Pi at home. You want to log a meal from a coffee shop. You don’t want to expose the Pi to the public internet.

Three reasonable options:

  1. Tailscale. Hosted control plane, generous free tier, MagicDNS. Five minutes to set up.
  2. Plain WireGuard. Self-hosted control plane (= no control plane), more cycles, more privacy.
  3. Headscale. Self-hosted Tailscale-compatible coordination server. Best of both, more setup.

We’ll cover all three. We use Tailscale, because the operational savings are real and the trust trade-off is acceptable to us. Your calculus may differ.

Tailscale path

Install Tailscale on the Pi:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --ssh --advertise-tags=tag:nutrition

Install on your phone (iOS / Android / GrapheneOS via F-Droid). Sign into the same tailnet.

Now your phone can hit the Pi at its 100.x.y.z Tailscale IP. The Caddy at https://nutrition.tail-scale.example.ts.net works the same as the LAN URL — Tailscale’s MagicDNS resolves it.

For LAN-equivalent UX, configure your tracker app’s “custom OFF endpoint” to the tailnet hostname. ONT’s developer settings accept any URL.

TLS via Tailscale

sudo tailscale cert nutrition.tail-scale.example.ts.net

This gets you a real Let’s Encrypt cert tied to your tailnet’s MagicDNS name. Mount the cert into your Caddy container or have Caddy load it directly with tls /var/lib/tailscale/....

ACLs

Restrict access. By default everything in your tailnet can reach everything else. We narrow this:

{
  "acls": [
    {
      "action": "accept",
      "src": ["mariano@example.com"],
      "dst": ["tag:nutrition:443", "tag:nutrition:80"]
    }
  ],
  "tagOwners": {
    "tag:nutrition": ["mariano@example.com"]
  }
}

Now only your account can reach the nutrition node, only on the Caddy ports.

Plain WireGuard path

If you don’t want Tailscale’s control plane in the loop:

# on the Pi
sudo apt install wireguard
wg genkey | tee privatekey | wg pubkey > publickey

/etc/wireguard/wg0.conf:

[Interface]
PrivateKey = <pi-private-key>
Address = 10.0.0.1/24
ListenPort = 51820

[Peer]
PublicKey = <phone-public-key>
AllowedIPs = 10.0.0.2/32

wg-quick up wg0 to bring the interface up.

Open UDP/51820 on your router (port-forward to the Pi). This is the only public exposure — and only WireGuard authenticates, so the public surface is tight.

Configure the WireGuard app on your phone with the matching peer config. Done.

WireGuard is faster than Tailscale (no DERP relay), uses less battery, and has zero coordination dependency. The cost: you maintain it. Adding a second device is a manual config edit and key exchange.

Headscale path

Headscale is an open-source server that speaks the Tailscale coordination protocol. You get the Tailscale apps’ UX but a control plane you run.

docker run -d --name headscale \
  -v /var/lib/headscale:/var/lib/headscale \
  -p 127.0.0.1:8080:8080 \
  -p 127.0.0.1:9090:9090 \
  headscale/headscale:0.23 headscale serve

Put a Caddy in front of it on a public domain. Configure Tailscale clients with --login-server=https://headscale.example.org.

Trade-off: you now run another service. We did this for six months and went back to Tailscale because we’d rather not maintain it. If your threat model excludes “Tailscale Inc. as a coordination party,” Headscale is a clean answer.

What about Cloudflare Tunnel

You can run cloudflared and have a public DNS name that proxies to your Pi without opening any port. We do this for some other personal services. We don’t do it for the nutrition stack because:

  • The OFF mirror endpoint, even with auth, is still a publicly-resolvable URL — that’s a different surface than a tailnet.
  • Cloudflare in the path means Cloudflare in the path. For most things we’re fine with that. For health-adjacent personal data, less so.

If you’re in a country with restricted home internet (CGNAT, mandatory ISP filtering), Cloudflare Tunnel may still be the right answer.

Latency

We measured round-trip time from a phone on a coffee-shop wifi to the home Pi over each path:

PathRTT (median)Notes
Tailscale direct28 msNAT-traversed, p2p
Tailscale relayed92 msDERP relay, when p2p fails
Plain WireGuard24 msdirect, port-forwarded
Headscale direct27 mssimilar to Tailscale

Direct paths are essentially indistinguishable. Tailscale relayed is roughly 4x slower than direct, which matters for typing-fast search-as-you-type but not for occasional barcode scans.

  • Default: Tailscale. Five minutes, ACLs, MagicDNS, real TLS, generous free tier.
  • If you exclude SaaS coordination: plain WireGuard.
  • If you want self-hosted Tailscale UX: Headscale.

References