Expose one tailnet-only endpoint to the internet without opening the service

My tweet-queue service listens only on its Tailscale IP. Great for safety, annoying when I'm off the tailnet and want to post. The fix is not to rebind the service — it's to put a public nginx box that is also on the tailnet in front of it, and expose just the one path behind basic auth.

On the edge host (already a tailnet member, with a wildcard *.dev DNS record pointing at it):

server {
    listen 80;
    server_name x.dev.example.com;
    client_max_body_size 32m;          # match the backend's own body limit

    location = /add {
        auth_basic           "x-auto-poster";
        auth_basic_user_file /etc/nginx/.htpasswd;   # reuse the existing user file
        proxy_pass http://100.91.4.56:8090;          # backend's Tailscale IP
        proxy_set_header Authorization "";           # don't leak Basic creds upstream
    }

    location / { return 404; }
}

Then sudo certbot --nginx -d x.dev.example.com --redirect adds TLS and the HTTP→HTTPS redirect. With wildcard DNS there's no DNS step at all.

Three details that matter:

  • location = /add plus a catch-all 404 means only the endpoint you meant to share is reachable — not every route the backend happens to have.
  • proxy_set_header Authorization "" stops nginx's Basic header from reaching the backend. If the backend ever grows its own auth (or uses Authorization for something else, like an API key selector), a forwarded Basic header produces confusing 401/403s.
  • Reusing one .htpasswd across sites is convenient, but every user in it can now hit every site that points at it. Fine for a single-user box; split the file once that stops being true.

Test it from outside: no creds should give 401, other paths 404.

curl -s -o /dev/null -w "%{http_code}\n" https://x.dev.example.com/add   # 401
curl -u user:pass -X POST https://x.dev.example.com/add \
  -H 'Content-Type: application/json' -d '{"text":"hello from anywhere"}'

Comments

  1. Markdown is allowed. HTML tags allowed: <strong>, <em>, <blockquote>, <code>, <pre>, <a>.