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
- Verify the exact file exists and is readable: ls -l /path/to/config && cat /path/to/config
- If it is a kube Secret, follow the symlink chain and confirm ..data resolves: ls -la /mnt/secret/
- Align the mounted key name with the expected filename
- 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
- Smoke-test mounts in CI (render the pod spec, check the projected files exist)
- Name ConfigMap/Secret keys to match the expected filename exactly
- Prefer atomic projections (kubelet ..data) and avoid hand-managed symlinks
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
- failed to add fsnotify watch: %w
- failed to add fsnotify watch: %w
- error retrieving egress service configs: %w
- error determining tailnet target IPs: %w
- failed to watch tailscaled config: %w
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/a2ab439d88ab7de2.
Report an issue: GitHub.