tailscale/tailscale · error
error waiting for tailscaled socket: %w
Error message
error waiting for tailscaled socket: %w
What it means
tailscaled.go:55 is hit inside the wait loop for the tailscaled socket: os.Stat(cfg.Socket) returned an error that is not fs.ErrNotExist (the loop tolerates a missing file and keeps polling). Typically EACCES/permission denied on the socket path or its parent directory, or the path exists but is not a regular unix socket file.
Source
Thrown at cmd/containerboot/tailscaled.go:55
cmd.Env = append(os.Environ(), "TS_CERT_SHARE_MODE="+cfg.CertShareMode)
}
log.Printf("Starting tailscaled")
if err := cmd.Start(); err != nil {
return nil, nil, fmt.Errorf("starting tailscaled failed: %w", err)
}
// Wait for the socket file to appear, otherwise API ops will racily fail.
log.Printf("Waiting for tailscaled socket at %s", cfg.Socket)
for {
if ctx.Err() != nil {
return nil, nil, errors.New("timed out waiting for tailscaled socket")
}
_, err := os.Stat(cfg.Socket)
if errors.Is(err, fs.ErrNotExist) {
time.Sleep(100 * time.Millisecond)
continue
} else if err != nil {
return nil, nil, fmt.Errorf("error waiting for tailscaled socket: %w", err)
}
break
}
tsClient := &local.Client{
Socket: cfg.Socket,
UseSocketOnly: true,
}
return tsClient, cmd.Process, nil
}
// tailscaledArgs uses cfg to construct the argv for tailscaled.
func tailscaledArgs(cfg *settings) []string {
args := []string{"--socket=" + cfg.Socket}
switch {
case cfg.KubeSecret != "":
args = append(args, "--state=kube:"+cfg.KubeSecret)View on GitHub (pinned to cfe32b8be6)
Solutions
- Check the stat error in the log (it is wrapped with %w) to confirm permission vs other causes
- Ensure the socket directory is writable by the container user (fsGroup, runAsUser, or writable emptyDir/hostPath)
- Verify TS_SOCKET points at a socket file path, not a directory
- Run with the stock image defaults, which provision /var/run/tailscale correctly
Example fix
# before
securityContext:
runAsNonRoot: true
# volume mounted read-only at /var/run/tailscale
# after
securityContext:
runAsNonRoot: true
fsGroup: 1000
volumes:
- name: run
emptyDir: {} # writable socket dir Defensive patterns
Strategy: validation
Validate before calling
// Preflight: the socket path must be stat-able by this process (parent dir traversable).
dir := filepath.Dir(socketPath)
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
log.Fatalf("socket dir %s unusable: %v", dir, err)
}
if _, err := os.Stat(socketPath); err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Fatalf("socket path %s not checkable (permissions?): %v", socketPath, err)
} Prevention
- Mount an writable emptyDir at /var/run/tailscale for non-root proxies
- Set fsGroup matching the socket dir ownership when runAsNonRoot
- Never point TS_SOCKET at a read-only volume or a directory path
When it happens
Trigger: TS_SOCKET (or the default /var/run/tailscale/tailscaled.sock) lives in a directory the container cannot traverse due to read-only mounts, wrong fsGroup, or SELinux; the path points at a directory; the socket was created by a different UID.
Common situations: Non-root securityContext with restrictive volumeMounts on /var/run/tailscale; mounting a hostPath with root-owned perms; overriding TS_SOCKET to a path under a read-only volume.
Related errors
- timed out waiting for tailscaled socket
- failed to add fsnotify watch: %w
- error syncing egress service config: %w
- error retrieving egress service configs: %w
- error retrieving current egress proxy status: %w
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/36e1941143502705.
Report an issue: GitHub.