tailscale/tailscale · error

logpolicy.Config.Save for %v: %w

Error message

logpolicy.Config.Save for %v: %w

What it means

On first start, tsnet creates its log configuration file at <Dir>/tailscaled.log.conf; when no such file exists it generates a fresh config and saves it. This error wraps the Save failure, which is almost always a filesystem problem with the Server.Dir: missing directory, no write permission, or disk full.

Source

Thrown at tsnet/tsnet.go:1101

		})
		if err != nil {
			return "", err
		}
	}
	return authKey, nil
}

func (s *Server) startLogger(closePool *closeOnErrorPool, health *health.Tracker, tsLogf logger.Logf) error {
	if testenv.InTest() {
		return nil
	}
	cfgPath := filepath.Join(s.rootPath, "tailscaled.log.conf")
	lpc, err := logpolicy.ConfigFromFile(cfgPath)
	switch {
	case os.IsNotExist(err):
		lpc = logpolicy.NewConfig(logtail.CollectionNode)
		if err := lpc.Save(cfgPath); err != nil {
			return fmt.Errorf("logpolicy.Config.Save for %v: %w", cfgPath, err)
		}
	case err != nil:
		return fmt.Errorf("logpolicy.LoadConfig for %v: %w", cfgPath, err)
	}
	if err := lpc.Validate(logtail.CollectionNode); err != nil {
		return fmt.Errorf("logpolicy.Config.Validate for %v: %w", cfgPath, err)
	}
	s.logid = lpc.PublicID

	s.logbuffer, err = filch.New(filepath.Join(s.rootPath, "tailscaled"), filch.Options{ReplaceStderr: false})
	if err != nil {
		return fmt.Errorf("error creating filch: %w", err)
	}
	closePool.add(s.logbuffer)
	c := logtail.Config{
		Collection:   lpc.Collection,
		PrivateID:    lpc.PrivateID,
		Stderr:       io.Discard, // log everything to Buffer

View on GitHub (pinned to 57c3357fdb)

Solutions

  1. Ensure Server.Dir exists and the process user can write to it: mkdir -p /data/tsnet && chown.
  2. Mount volumes read-write in docker/k8s (remove readOnly: true).
  3. Free disk space if full.
  4. Set an absolute Dir you control, e.g. os.MkdirAll(dir, 0700) before constructing the Server.

Example fix

// before
s := &tsnet.Server{Dir: "/var/lib/tsnet", ...} // /var/lib/tsnet not writable

// after
dir := "/var/lib/tsnet"
if err := os.MkdirAll(dir, 0o700); err != nil { log.Fatal(err) }
s := &tsnet.Server{Dir: dir, ...}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the Dir is writable before constructing the Server.
if err := os.MkdirAll(dir, 0o700); err != nil { return err }
probe := filepath.Join(dir, ".writable")
if err := os.WriteFile(probe, nil, 0o600); err != nil {
    return fmt.Errorf("tsnet Dir %s not writable: %w", dir, err)
}
os.Remove(probe)

Type guard

func isLogConfigSaveError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "logpolicy.Config.Save")
}

Prevention

When it happens

Trigger: Server.Dir pointing to a directory that does not exist, is read-only, or is owned by another user; disk full; running the binary as a user without write access to Dir; Dir on a read-only mounted volume.

Common situations: Running in a container where the state volume is mounted read-only; forgetting Dir (empty string means current working directory, which may be read-only); non-root service with Dir under /root; ephemeral filesystems that are full.

Related errors


AI-assisted analysis of tailscale/tailscale@57c3357fdb (2026-08-18). Data as JSON: /api/errors/5123472261ec4e94. Report an issue: GitHub.