prometheus/node_exporter · error
failed to open textfile data file
Error message
failed to open textfile data file %q: %w
What it means
textFileCollector.processFile opens each file in the textfile directory with os.Open; any open failure (missing file, permission denied, path is a directory) is wrapped as this error. node_exporter's textfile collector reads Prometheus exposition-format files dropped by other processes and merges them into the scrape.
Solutions
- Check file permissions: ensure the user node_exporter runs as can read the files (chmod 644, correct group)
- Make writers atomic: write to a temp file and rename() into place instead of deleting/recreating during scrape
- Ensure the textfile directory (--collector.textfile.directory) contains only regular readable files, not subdirectories
- If the wrapped cause is permission denied, adjust SELinux/AppArmor or directory ownership
Example fix
// before (writer): non-atomic delete during scrape rm /var/lib/node_exporter/textfile/foo.prom && write foo.prom // after (writer): atomic replace tmp=$(mktemp /var/lib/node_exporter/textfile/foo.prom.XXXXXX) write "$tmp" && chmod 644 "$tmp" && mv "$tmp" /var/lib/node_exporter/textfile/foo.prom
Defensive patterns
Strategy: validation
Validate before calling
files, _ := os.ReadDir(textfileDir)
for _, f := range files {
if !f.Type().IsRegular() { continue }
if fh, err := os.Open(filepath.Join(textfileDir, f.Name())); err != nil {
log.Warn("unreadable textfile", "name", f.Name(), "err", err)
} else { fh.Close() }
} Try / catch
if err := coll.Update(ch); err != nil {
log.Warn("textfile scrape issue", "err", err) // surfaced via node_exporter textfile scrape error metric
} Prevention
- Write .prom files atomically (temp + rename)
- Set directory/file ownership so the exporter user can read them
- Keep only regular files in the textfile directory — no subdirs or symlinks
- Run as a dedicated user with a managed textfile directory
When it happens
Trigger: A file listed/visible in the textfile directory disappears between readdir and open (writer removing files); the file is a directory; the exporter user lacks read permission; stale symlink targets.
Common situations: Cron jobs that `mv` or delete .prom files while a scrape is in flight; files written with root-only permissions while node_exporter runs as the prometheus user; misconfigured directory containing subdirectories; broken symlinks left by deploy scripts.
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- failed to stat
- failed to open sysfs
- failed to open procfs
- failed to open /proc/self
- failed to open procfs
AI-assisted analysis of prometheus/node_exporter@17ddd77c59 (2026-09-07).
Data as JSON: /api/errors/2973f75250e3c0b4.
Report an issue: GitHub.
Appendix: source
Thrown at collector/textfile.go:292
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
"node_textfile_scrape_error",
"1 if there was an error opening or reading a file, 0 otherwise",
nil, nil,
),
prometheus.GaugeValue, errVal,
)
return nil
}
// processFile processes a single file, returning its modification time on success.
func (c *textFileCollector) processFile(dir, name string) (*time.Time, map[string]*dto.MetricFamily, error) {
path := filepath.Join(dir, name)
f, err := os.Open(path)
if err != nil {
return nil, nil, fmt.Errorf("failed to open textfile data file %q: %w", path, err)
}
defer f.Close()
parser := expfmt.NewTextParser(model.UTF8Validation)
families, err := parser.TextToMetricFamilies(f)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse textfile data from %q: %w", path, err)
}
if hasTimestamps(families) {
return nil, nil, fmt.Errorf("textfile %q contains unsupported client-side timestamps, skipping entire file", path)
}
// Only stat the file once it has been parsed and validated, so that
// a failure does not appear fresh.
stat, err := f.Stat()
if err != nil {
return nil, families, fmt.Errorf("failed to stat %q: %w", path, err)View on GitHub (pinned to 17ddd77c59)