I wanted to serve a local HTML file and access it from my phone via Tailscale. I already knew Caddy as a quick file server, so the manual version was two commands:
| |
Page is now at https://<machine-name>.<tailnet>.ts.net. Tailscale handles HTTPS. The full guide is here.
Surviving reboots #
tailscale serve --bg persists on its own. Caddy needs a macOS LaunchAgent. I created ~/Library/LaunchAgents/com.jfm.caddy.plist that runs caddy run --config /path/to/Caddyfile, with RunAtLoad and KeepAlive set to true. Load it with launchctl load.
The Caddyfile itself is minimal:
| |
A Caddyfile isn’t strictly necessary for this — the CLI one-liner works fine. I only used one because the LaunchAgent plist was getting unwieldy with every CLI argument as a separate <string> element.
The learning: paths and spaces #
Three contexts, three different quoting rules, all demanding absolute paths:
- CLI: quoted shell string works —
--root "/Users/me/My Job Search/kanban" - Caddyfile: must be a quoted absolute path.
~doesn’t expand, relative paths silently fail. - Plist XML: each arg is its own
<string>element so spaces are fine, but still no~expansion. The binary must be/opt/homebrew/bin/caddy, not justcaddy.
Debug with cat /tmp/jfm-caddy.log and launchctl list | grep jfm.