tailscale/tailscale · error

server_key is missing

Error message

server_key is missing

What it means

The fourth failure mode of NodeFile.Check: ServerInfo.Key (json field "server_key"), the server's Noise public key, is the zero key.MachinePublic. Persisting it lets future sessions skip key discovery and prevents MITM on first contact, so Check refuses to write credentials without it. Note: a present-but-malformed server_key string makes ReadNodeFile fail at unmarshal instead; this error is specifically absent/empty.

Source

Thrown at control/tsp/nodefile.go:102

		panic(fmt.Sprintf("NodeFile.AsJSON: %v", err)) // unreachable: all fields marshal successfully
	}
	return append(out, '\n')
}

// Check reports whether nf has all required fields set.
// It returns an error describing the first zero-valued field, if any.
func (nf NodeFile) Check() error {
	if nf.NodeKey.IsZero() {
		return fmt.Errorf("node_key is missing")
	}
	if nf.MachineKey.IsZero() {
		return fmt.Errorf("machine_key is missing")
	}
	if nf.URL == "" {
		return fmt.Errorf("server_url is missing")
	}
	if nf.ServerInfo.Key.IsZero() {
		return fmt.Errorf("server_key is missing")
	}
	return nil
}

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Fetch the key before persisting: k, err := DiscoverServerKey(ctx, serverURL), then ServerInfo{URL: serverURL, Key: k}
  2. Or hard-code the vendor's known Noise public key and pass it via SetControlPublicKey + the NodeFile
  3. If loading existing JSON, add "server_key": "mkey:..."

Example fix

// before
nf := tsp.NodeFile{NodeKey: nk, MachineKey: mk, ServerInfo: tsp.ServerInfo{URL: u}}
tsp.WriteNodeFile(path, nf) // invalid NodeFile: server_key is missing

// after
srvKey, err := tsp.DiscoverServerKey(ctx, u)
if err != nil {
    return err
}
nf := tsp.NodeFile{NodeKey: nk, MachineKey: mk, ServerInfo: tsp.ServerInfo{URL: u, Key: srvKey}}
tsp.WriteNodeFile(path, nf)
Defensive patterns

Strategy: validation

Validate before calling

if nf.ServerInfo.Key.IsZero() {
    k, err := tsp.DiscoverServerKey(ctx, nf.URL)
    if err != nil {
        return err
    }
    nf.ServerInfo.Key = k
}
err := tsp.WriteNodeFile(path, nf)

Type guard

func hasServerKey(nf tsp.NodeFile) bool {
    return !nf.ServerInfo.Key.IsZero()
}

Try / catch

if err := tsp.WriteNodeFile(path, nf); err != nil {
    if strings.Contains(err.Error(), "server_key is missing") {
        // discover then retry the write
        if k, derr := tsp.DiscoverServerKey(ctx, nf.URL); derr == nil {
            nf.ServerInfo.Key = k
            return tsp.WriteNodeFile(path, nf)
        }
    }
}

Prevention

When it happens

Trigger: Writing the NodeFile before DiscoverServerKey has run and without SetControlPublicKey; JSON whose "server_key" is "" or omitted; the struct was built from a partial registration response.

Common situations: Bootstrap code that registers and immediately tries to save credentials, forgetting the server key step; reusing a NodeFile template that never included server_key.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/0052dd3f14bc2954. Report an issue: GitHub.