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 BufferView on GitHub (pinned to 57c3357fdb)
Solutions
- Ensure Server.Dir exists and the process user can write to it: mkdir -p /data/tsnet && chown.
- Mount volumes read-write in docker/k8s (remove readOnly: true).
- Free disk space if full.
- 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
- Always set an absolute Server.Dir you created with os.MkdirAll(dir, 0700).
- Mount state volumes read-write in containers; verify with a write probe at startup.
- Run the process as the user that owns Dir.
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
- logpolicy.LoadConfig for %v: %w
- error creating filch: %w
- failed extracting the new tailscale binary from %q: %w
- mkdir %q: %w
- creating state directory: %w
AI-assisted analysis of tailscale/tailscale@57c3357fdb (2026-08-18).
Data as JSON: /api/errors/5123472261ec4e94.
Report an issue: GitHub.