til

Caddy + Tailscale for Remote File Serving

Serving a local HTML file over your tailnet with Caddy, Tailscale, and a macOS LaunchAgent.

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:

1
2
caddy file-server --browse --listen :8080 --root "/Users/me/My Job Search/kanban"
tailscale serve --bg 8080

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:

1
2
3
4
:8080 {
    root * "/Users/me/My Job Search/kanban"
    file_server
}

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 just caddy.

Debug with cat /tmp/jfm-caddy.log and launchctl list | grep jfm.


See Also

View page source