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

  1. Verify /proc/zoneinfo exists and is readable by the node_exporter user (ls -l /proc/zoneinfo; cat /proc/zoneinfo).
  2. If running in a container, mount the host's /proc correctly (e.g. -v /proc:/host/proc:ro) and set --path.procfs=/host/proc.
  3. Check the collector is only enabled on Linux with kernel support; disable it otherwise (--collector.zoneinfo is disabled by default).
  4. 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

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


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)