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
- 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>
- If IMMICH_HELMET_FILE=true, ensure helmet.json is present at <build folder>/helmet.json (check your build/Dockerfile copies it)
- Point IMMICH_HELMET_FILE at an explicit absolute path to a valid helmet.json instead of relying on the default resolution
- 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
- Always copy helmet.json into the build folder in Dockerfiles
- Use an absolute IMMICH_HELMET_FILE path in deployments rather than the 'true' default
- Validate custom helmet.json with a JSON linter before mounting it
- Add a healthcheck/startup check that stats the helmet file
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)