tailscale/tailscale · error
use POST
Error message
use POST
What it means
Returned as HTTP 405 by disconnectControl when PermitWrite passed but the method is not POST. Disconnecting control is a state-changing action, and LocalAPI pins such actions to POST; GET is rejected so a URL click or prefetch cannot silently detach the node from control.
Source
Thrown at ipn/localapi/localapi.go:729
}
if servePprofFunc == nil {
http.Error(w, "not implemented on this platform", http.StatusServiceUnavailable)
return
}
servePprofFunc(w, r)
}
// disconnectControl is the handler for local API /disconnect-control endpoint that shuts down control client, so that
// node no longer communicates with control. Doing this makes control consider this node inactive. This can be used
// before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the
// peers to switch over to another replica whilst still maintaining th existing peer connections.
func (h *Handler) disconnectControl(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "access denied", http.StatusForbidden)
return
}
if r.Method != httpm.POST {
http.Error(w, "use POST", http.StatusMethodNotAllowed)
return
}
h.b.DisconnectControl()
}
func (h *Handler) reloadConfig(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "access denied", http.StatusForbidden)
return
}
if r.Method != httpm.POST {
http.Error(w, "use POST", http.StatusMethodNotAllowed)
return
}
ok, err := h.b.ReloadConfig()
var res apitype.ReloadConfigResponse
res.Reloaded = ok
if err != nil {View on GitHub (pinned to 6e0912f979)
Solutions
- Send an empty POST: curl -X POST .../localapi/v0/disconnect-control.
- Keep drain/failover automation on POST and add a lint/check for the method.
- Remember the endpoint takes no body and returns 200 with no content on success.
Example fix
# before $ curl --unix-socket /var/run/tailscale/tailscaled.sock http://local-tailscaled.sock/localapi/v0/disconnect-control 405 use POST # after $ curl -X POST --unix-socket /var/run/tailscale/tailscaled.sock http://local-tailscaled.sock/localapi/v0/disconnect-control
Defensive patterns
Strategy: validation
Validate before calling
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
"http://local-tailscaled.sock/localapi/v0/disconnect-control", nil)
resp, err := http.DefaultClient.Do(req) Try / catch
if resp.StatusCode == http.StatusMethodNotAllowed {
return errors.New("disconnect-control must be invoked with POST")
} Prevention
- Keep an endpoint table (path -> method) for LocalAPI calls used by ops scripts.
- Never paste action endpoints into browsers/prefetchable contexts; they are POST-only by design.
When it happens
Trigger: GET /localapi/v0/disconnect-control (browser, curl without -X POST, prefetcher); DELETE or PUT probes from API explorers.
Common situations: Copy-pasting the endpoint into a browser to 'test' it; HTTP clients configured with GET for idempotency checks (health-check tools that probe endpoints with GET); typos in scripts using curl -d without -X POST is actually POST, but curl without any body flag defaults to GET.
Related errors
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/623d37627c801540.
Report an issue: GitHub.