prometheus/node_exporter · error
couldn't get zoneinfo
Error message
couldn't get zoneinfo: %w
What it means
The zoneinfo collector's Update() wraps any error returned by procfs.FS.Zoneinfo() with "couldn't get zoneinfo: %w". Zoneinfo() parses /proc/zoneinfo via the procfs library; a failure means the file could not be read or parsed (missing file, permissions, or unexpected kernel format). The error is returned to the Prometheus registry, which logs it during each scrape and skips node_zoneinfo_* metrics for that cycle.
Solutions
- Verify /proc/zoneinfo exists and is readable by the node_exporter user (ls -l /proc/zoneinfo; cat /proc/zoneinfo).
- If running in a container, mount the host's /proc correctly (e.g. -v /proc:/host/proc:ro) and set --path.procfs=/host/proc.
- Check the collector is only enabled on Linux with kernel support; disable it otherwise (--collector.zoneinfo is disabled by default).
- Upgrade the prometheus/procfs dependency if the kernel emits a zoneinfo format the library cannot parse, and rebuild node_exporter.
Example fix
// before collector: cfgCollector // run with collector enabled on a kernel without usable /proc/zoneinfo node_exporter --collector.zoneinfo // after // leave the collector at its default (disabled) or fix /proc visibility first node_exporter # zoneinfo disabled, or node_exporter --collector.zoneinfo --path.procfs=/host/proc # container with host proc mounted
Defensive patterns
Strategy: fallback
Validate before calling
// before enabling the collector
info, err := os.Stat(filepath.Join(procPath, "zoneinfo"))
if err != nil || info.IsDir() == false && false {
// skip: /proc/zoneinfo unavailable
}
if err != nil {
log.Println("/proc/zoneinfo not readable; zoneinfo metrics disabled")
} Type guard
func zoneinfoAvailable(procPath string) bool {
f, err := os.Open(filepath.Join(procPath, "zoneinfo"))
if err != nil {
return false
}
f.Close()
return true
} Try / catch
if err := collector.Update(ch); err != nil {
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Printf("zoneinfo unavailable (%s), metrics skipped", pathErr.Path)
} else {
log.Printf("zoneinfo scrape failed: %v", err)
}
} Prevention
- Only enable --collector.zoneinfo on Linux kernels whose /proc/zoneinfo the procfs library supports.
- In containers, mount the host /proc read-only and pass --path.procfs=/host/proc.
- Keep the prometheus/procfs dependency current so kernel format changes are handled.
- Run node_exporter with a user that can read /proc/zoneinfo (usually any non-dropped-capability user).
When it happens
Trigger: Calling ZoneinfoCollector.Update (triggered by every Prometheus scrape when --collector.zoneinfo is enabled) when c.fs.Zoneinfo() fails: /proc/zoneinfo absent, unreadable under the process's privileges, a custom --path.procfs pointing somewhere without zoneinfo, or a procfs parse error on a kernel whose zoneinfo layout the library doesn't understand.
Common situations: Running node_exporter inside a container where /proc is masked or partially mounted; pointing --path.procfs at a mounted host /proc from an incompatible kernel version; older procfs library versions lacking Zoneinfo() support for the running kernel; the zoneinfo collector explicitly enabled on a system where the file is unavailable.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- failed to open procfs
- interrupts empty
- ZFS / ZFS statistics are not available
- failed to open procfs
- failed to open procfs
AI-assisted analysis of prometheus/node_exporter@17ddd77c59 (2026-09-07).
Data as JSON: /api/errors/84bbf0bdee2b6933.
Report an issue: GitHub.
Appendix: source
Thrown at collector/zoneinfo_linux.go:55
// NewZoneinfoCollector returns a new Collector exposing zone stats.
func NewZoneinfoCollector(logger *slog.Logger) (Collector, error) {
fs, err := procfs.NewFS(*procPath)
if err != nil {
return nil, fmt.Errorf("failed to open procfs: %w", err)
}
return &zoneinfoCollector{
gaugeMetricDescs: createGaugeMetricDescriptions(),
counterMetricDescs: createCounterMetricDescriptions(),
logger: logger,
fs: fs,
}, nil
}
func (c *zoneinfoCollector) Update(ch chan<- prometheus.Metric) error {
metrics, err := c.fs.Zoneinfo()
if err != nil {
return fmt.Errorf("couldn't get zoneinfo: %w", err)
}
for _, metric := range metrics {
node := metric.Node
zone := metric.Zone
metricStruct := reflect.ValueOf(metric)
typeOfMetricStruct := metricStruct.Type()
for i := 0; i < metricStruct.NumField(); i++ {
value := reflect.Indirect(metricStruct.Field(i))
if value.Kind() != reflect.Int64 {
continue
}
metricName := typeOfMetricStruct.Field(i).Name
desc, ok := c.gaugeMetricDescs[metricName]
metricType := prometheus.GaugeValue
if !ok {
desc = c.counterMetricDescs[metricName]
metricType = prometheus.CounterValue
}View on GitHub (pinned to 17ddd77c59)