tailscale/tailscale · error

error reading configfile: %w

Error message

error reading configfile: %w

What it means

The initial os.ReadFile(path) of the tailscaled config file failed before the watch loop starts (the code requires the file to be readable at startup; only later re-reads happen inside the loop). The error is sent to errCh, which terminates containerboot's config watching.

Source

Thrown at cmd/containerboot/tailscaled.go:226

	if w, err := fsnotify.NewWatcher(); err != nil {
		// Creating a new fsnotify watcher would fail for example if inotify was not able to create a new file descriptor.
		// See https://github.com/tailscale/tailscale/issues/15081
		log.Printf("tailscaled config watch: failed to create fsnotify watcher, timer-only mode: %v", err)
		ticker := time.NewTicker(5 * time.Second)
		defer ticker.Stop()
		tickChan = ticker.C
	} else {
		defer w.Close()
		if err := w.Add(tailscaledCfgDir); err != nil {
			errCh <- fmt.Errorf("failed to add fsnotify watch: %w", err)
			return
		}
		eventChan = w.Events
		errChan = w.Errors
	}
	b, err := os.ReadFile(path)
	if err != nil {
		errCh <- fmt.Errorf("error reading configfile: %w", err)
		return
	}
	prevTailscaledCfg = b
	// kubelet mounts Secrets to Pods using a series of symlinks, one of
	// which is <mount-dir>/..data that Kubernetes recommends consumers to
	// use if they need to monitor changes
	// https://github.com/kubernetes/kubernetes/blob/v1.28.1/pkg/volume/util/atomic_writer.go#L39-L61
	const kubeletMountedCfg = "..data"
	toWatch := filepath.Join(tailscaledCfgDir, kubeletMountedCfg)
	for {
		select {
		case <-ctx.Done():
			return
		case err := <-errChan:
			errCh <- fmt.Errorf("watcher error: %w", err)
			return
		case <-tickChan:
		case event := <-eventChan:

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Verify the exact file exists and is readable: ls -l /path/to/config && cat /path/to/config
  2. If it is a kube Secret, follow the symlink chain and confirm ..data resolves: ls -la /mnt/secret/
  3. Align the mounted key name with the expected filename
  4. Fix file ownership/permissions for the container user

Example fix

# before: configmap key 'config.yaml' but containerboot reads '/etc/ts/tailscaled.conf'
# after: set TS_TAILSCALED_CONFIG=/etc/ts/config.yaml (or rename the key)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := os.ReadFile(cfgPath); err != nil {
	return fmt.Errorf("config unreadable at startup, fix mount: %w", err)
}

Prevention

When it happens

Trigger: Config file absent while its directory exists (Secret mounted with a different key name); dangling symlink such as a broken kubelet ..data link; permission denied on the file itself; path points to a directory.

Common situations: ConfigMap key renamed so the mounted filename changed; Secret not yet projected on a slow node; file permissions 0600 owned by another UID; trailing slash or wrong filename in TS_TAILSCALED_CONFIG.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/a2ab439d88ab7de2. Report an issue: GitHub.