immich-app/immich · error

Failed to read helmet file

Error message

Failed to read helmet file: ${helmetFile}

What it means

This error is thrown when the server cannot read the helmet.json file (helmet/CSP security headers config). resolveHelmetFile resolves IMMICH_HELMET_FILE to a path (defaulting to <buildDir>/helmet.json when set to 'true'), then reads and JSON.parses it synchronously; if readFileSync or JSON.parse fails, the path resolution error is wrapped and rethrown with the offending path in the message.

Solutions

  1. Verify the file exists and is readable at the resolved path: ls -l <resolved-path> and check JSON validity with node -e 'JSON.parse(require("fs").readFileSync(process.argv[1]))' <path>
  2. If IMMICH_HELMET_FILE=true, ensure helmet.json is present at <build folder>/helmet.json (check your build/Dockerfile copies it)
  3. Point IMMICH_HELMET_FILE at an explicit absolute path to a valid helmet.json instead of relying on the default resolution
  4. Restore a minimal valid helmet.json (e.g. {}) if the file was corrupted

Example fix

// before
docker run immich-server  # IMMICH_HELMET_FILE=true, helmet.json missing in image
// after
# in Dockerfile
copy ./server/helmet.json /build/helmet.json
# or explicitly
e docker run -e IMMICH_HELMET_FILE=/config/helmet.json -v ./helmet.json:/config/helmet.json ...
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, readFileSync } from 'node:fs';
const helmetFile = process.env.IMMICH_HELMET_FILE === 'true'
  ? new URL('../../helmet.json', import.meta.url).pathname
  : process.env.IMMICH_HELMET_FILE;
if (helmetFile && !existsSync(helmetFile)) throw new Error(`helmet file missing: ${helmetFile}`);
if (helmetFile) JSON.parse(readFileSync(helmetFile, 'utf8')); // throws early with a clear message

Type guard

const helmetFileReadable = (p: string): boolean => { try { statSync(p).isFile(); return true; } catch { return false; } };

Try / catch

try { startServer(); } catch (e) { if ((e as Error).message.startsWith('Failed to read helmet file')) { console.error(`Check IMMICH_HELMET_FILE (${process.env.IMMICH_HELMET_FILE}) exists, is a readable file, and contains valid JSON`, e.cause); process.exit(1); } throw e; }

Prevention

When it happens

Trigger: process.env.IMMICH_HELMET_FILE is set to a path that does not exist, is unreadable due to permissions, is a directory, or contains invalid JSON; or helmetFile is 'true' but the resolved join(import.meta.dirname,'..','..','helmet.json') file is absent from the deployment (e.g. trimmed container image or running from source outside the expected layout).

Common situations: Docker images built without helmet.json copied into /build; running the server via tsx/node from a directory that breaks the relative ../.. resolution; mounting a custom helmet.json with a wrong container path; invalid JSON hand-edited into the file.

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 immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/ec514a21c1b0af2a. Report an issue: GitHub.

Appendix: source

Thrown at server/src/repositories/config.repository.ts:165

const TELEMETRY_TYPES = new Set(Object.values(ImmichTelemetry));

const asSet = <T>(value: string | undefined, defaults: T[]) => {
  const values = (value || '').replaceAll(/\s/g, '').split(',').filter(Boolean);
  return new Set(values.length === 0 ? defaults : (values as T[]));
};

const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined) => {
  // default is off
  if (!helmetFile || helmetFile === 'false') {
    return;
  }

  helmetFile = helmetFile === 'true' ? join(import.meta.dirname, '..', '..', 'helmet.json') : helmetFile;

  try {
    return JSON.parse(readFileSync(helmetFile).toString()) as HelmetOptions;
  } catch (error) {
    throw new Error(`Failed to read helmet file: ${helmetFile}`, { cause: error });
  }
};

const getEnv = (): EnvData => {
  const parseResult = EnvSchema.safeParse(process.env);
  if (!parseResult.success) {
    const messages = ['Invalid environment variables: '];
    for (const issue of parseResult.error.issues) {
      const path = issue.path.join('.');
      messages.push(`  - [${path}] ${issue.message}`);
    }
    throw new Error(messages.join('\n'));
  }
  const dto = parseResult.data;

  const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
  const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
  const workers = [...setDifference(includedWorkers, excludedWorkers)];

View on GitHub (pinned to e55ac299a4)