juanfont/headscale · error
setting up extrarecord manager: %w
Error message
setting up extrarecord manager: %w
What it means
dns.NewExtraRecordsManager failed at startup when dns_config.extra_records_path is set (hscontrol/dns/extrarecords.go:34). The manager creates an fsnotify watcher, stats the file (rejecting directories with ErrPathIsDirectory), parses it as a JSON array of DNS records, and adds a watch. Failure means the path does not exist, is a directory, holds invalid JSON or wrong record fields, or the inotify watch could not be registered.
Source
Thrown at hscontrol/app.go:588
}
h.state.SetDERPMap(derpMap)
// Start ephemeral node garbage collector and schedule all nodes
// that are already in the database and ephemeral. If they are still
// around between restarts, they will reconnect and the GC will
// be cancelled.
go h.ephemeralGC.Start()
ephmNodes := h.state.ListEphemeralNodes()
for _, node := range ephmNodes.All() {
h.ephemeralGC.Schedule(node.ID(), h.cfg.Node.Ephemeral.InactivityTimeout)
}
if h.cfg.DNSConfig.ExtraRecordsPath != "" {
h.extraRecordMan, err = dns.NewExtraRecordsManager(h.cfg.DNSConfig.ExtraRecordsPath)
if err != nil {
return fmt.Errorf("setting up extrarecord manager: %w", err)
}
h.cfg.SetExtraRecords(h.extraRecordMan.Records())
go h.extraRecordMan.Run()
defer h.extraRecordMan.Close()
}
// Start all scheduled tasks, e.g. expiring nodes, derp updates and
// records updates
scheduleCtx, scheduleCancel := context.WithCancel(context.Background())
defer scheduleCancel()
go h.scheduledTasks(scheduleCtx)
// Prepare group for running listeners
errorGroup := new(errgroup.Group)
View on GitHub (pinned to 565fd254d0)
Solutions
- Check the wrapped stage: 'getting file info' (missing path), 'path is directory', 'reading extra records from path' (JSON/parse), or 'creating/adding watcher' (inotify limits)
- Ensure the path is a single regular file containing a JSON array like [{"name":"foo.example.com","type":"A","value":"1.2.3.4"}]
- Validate with jq before starting headscale
- If inotify-limited, raise fs.inotify.max_user_instances/max_user_watches or unset extra_records_path
Example fix
# before dns_config: extra_records_path: /etc/headscale/extra_records/ # directory -> ErrPathIsDirectory # after dns_config: extra_records_path: /etc/headscale/extra_records.json
Defensive patterns
Strategy: validation
Validate before calling
if p := cfg.DNSConfig.ExtraRecordsPath; p != "" {
fi, err := os.Stat(p)
if err != nil { return fmt.Errorf("extra_records_path missing: %s", p) }
if fi.IsDir() { return fmt.Errorf("extra_records_path must be a file: %s", p) }
b, _ := os.ReadFile(p)
var rs []tailcfg.DNSRecord
if err := json.Unmarshal(b, &rs); err != nil {
return fmt.Errorf("extra_records_path is not a JSON array of records: %w", err)
}
} Try / catch
if _, err := dns.NewExtraRecordsManager(path); err != nil {
if errors.Is(err, dns.ErrPathIsDirectory) {
// point config at the records file itself, not its folder
}
// watcher/inotify failures: check fs.inotify limits or drop extra_records_path
} Prevention
- Point extra_records_path at one JSON file, not a directory
- jq-validate the records file before deploying config changes
- On watch-heavy hosts, raise fs.inotify.max_user_instances before enabling the feature
When it happens
Trigger: extra_records_path pointing at a missing file or a directory (explicit ErrPathIsDirectory), a JSON file whose entries do not match the tailcfg.DNSRecord shape (missing/misspelled name/type/value), malformed JSON, or fsnotify/inotify exhaustion (too many watches, ulimit on inotify instances).
Common situations: Ops putting a whole directory of records instead of one file; hand-edited JSON with trailing commas or comments; schema drift after upgrading (record fields renamed); systems with a low fs.inotify.max_user_instances limit where watcher creation fails.
Related errors
- hostname contains invalid IP address
- configuration error: %w
- init state: %w
- building trusted_proxies middleware: %w
- DERP server private key and noise private key are the same:
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/c0d60535239c0395.
Report an issue: GitHub.