AdguardTeam/AdGuardHome · warning

watching %s %s: %w

Error message

watching %s %s: %w

What it means

Failed to register a filesystem watch path with the fsnotify watcher inside the TLS manager. The wrapped error is the underlying watcher.Add failure (typically OS-level inotify limits or a path that no longer exists). This is collected into an aggregate error list when the manager sets up watches on certificate/key paths.

Source

Thrown at internal/aghtls/defaultmanager.go:233

	err := mgr.watcher.Remove(p)
	if err != nil {
		errs = append(errs, fmt.Errorf("unwatching %s %s: %w", what, p, err))
	}

	return errs
}

// appendWatchErr starts watching a file at path p described by what and
// appends an error to the errs slice, if any.  Empty p is ignored.
func (mgr *DefaultManager) appendWatchErr(errs []error, what, p string) (result []error) {
	if p == "" {
		return errs
	}

	err := mgr.watcher.Add(p)
	if err != nil {
		errs = append(errs, fmt.Errorf("watching %s %s: %w", what, p, err))
	}

	return errs
}

// Refresh implements the [service.Refresher] interface for *DefaultManager.
func (mgr *DefaultManager) Refresh(ctx context.Context) (err error) {
	mgr.logger.DebugContext(ctx, "refreshing")

	select {
	case mgr.updates <- UpdateSignal{}:
		return nil
	case <-ctx.Done():
		return fmt.Errorf("refreshing: %w", ctx.Err())
	default:
		return nil
	}
}

View on GitHub (pinned to b41aefbe51)

Solutions

  1. Verify the configured certificate and private key paths exist on disk at startup
  2. Raise the inotify watch limit: sysctl fs.inotify.max_user_watches=1048576
  3. If files are replaced atomically, ensure the watched path is a stable directory path rather than the transient file
  4. Inspect the wrapped error to distinguish ENOENT (missing path) from ENOSPC (watch limit)

Example fix

// before
cert_path: /certs/live/example.com/fullchain.pem.new # renamed constantly
// after
cert_path: /certs/live/example.com/fullchain.pem
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range []string{certPath, keyPath} {
    if _, err := os.Stat(p); err != nil {
        return fmt.Errorf("path %s unusable: %w", p, err)
    }
}

Try / catch

if err := mgr.LoadTLSConfig(ctx); err != nil {
    // aggregate error; check strings.Contains for "watching" and the wrapped os error
    log.Warn("cert watch setup failed; hot-reload disabled", "err", err)
}

Prevention

When it happens

Trigger: Calling LoadTLSConfig/Refresh paths where the configured certificate or key path cannot be watched: path deleted or renamed after configuration, path is a non-existent directory, or the OS inotify watch limit (fs.inotify.max_user_watches) is exhausted.

Common situations: Cert-manager or acme.sh replaces certificate files atomically (rename/delete) between config load and watch registration; running in a container with a low inotify limit; misconfigured certificate path pointing to a missing file.

Related errors


AI-assisted analysis of AdguardTeam/AdGuardHome@b41aefbe51 (2026-08-27). Data as JSON: /api/errors/09d1a658a04eea7b. Report an issue: GitHub.