immich-app/immich · error

Failed to decode redis options

Error message

Failed to decode redis options

What it means

REDIS_URL may carry ioredis options as a base64-encoded JSON blob prefixed with 'ioredis://'. When that suffix is present, getEnv decodes slice(10) from base64 and JSON.parses it; any decode/parse failure (invalid base64, or base64 that is not valid JSON) is wrapped in this error.

Solutions

  1. Regenerate the payload: node -e 'console.log("ioredis://"+Buffer.from(JSON.stringify({host:"redis"})).toString("base64"))' and use the output as REDIS_URL
  2. Verify the suffix decodes: node -e 'JSON.parse(Buffer.from(process.argv[1].slice(10),"base64"))' "$REDIS_URL"
  3. If you just want a plain connection, drop the ioredis:// scheme and set REDIS_URL=redis://redis:6379 (or REDIS_HOSTNAME etc.) instead
  4. Check the compose/env layer isn't mangling the value (no stray quotes, spaces, or line breaks)

Example fix

// before
REDIS_URL=ioredis://redis:6379
// after
REDIS_URL=redis://redis:6379
# or properly encoded:
REDIS_URL=ioredis://eyJob3N0IjoicmVkaXMifQ==
Defensive patterns

Strategy: validation

Validate before calling

const url = process.env.REDIS_URL;
if (url?.startsWith('ioredis://')) {
  try {
    JSON.parse(Buffer.from(url.slice(10), 'base64').toString());
  } catch (e) {
    throw new Error(`REDIS_URL is not valid base64-encoded JSON: ${e}`);
  }
}

Type guard

const isEncodedRedisOptions = (v: string | undefined): boolean =>
  !!v?.startsWith('ioredis://') && (() => { try { JSON.parse(Buffer.from(v.slice(10), 'base64').toString()); return true; } catch { return false; } })();

Try / catch

try { start(); } catch (e) { if ((e as Error).message === 'Failed to decode redis options') { console.error('REDIS_URL must be redis://... or ioredis:// + base64(JSON). Re-encode:', e.cause); process.exit(1); } throw e; }

Prevention

When it happens

Trigger: REDIS_URL starts with 'ioredis://' but the remainder is not valid base64 (spaces, URL-encoding, truncated string) or decodes to bytes that are not valid JSON (e.g. plain 'redis://host' content pasted after the prefix).

Common situations: Users copying an example REDIS_URL and replacing the JSON payload but leaving it unencoded; YAML/compose interpolating quotes or newlines into the value; a sentinel/template string left unfinished; manually base64-encoding with padding stripped incorrectly or double-encoding.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/869a18eb23e7e619. Report an issue: GitHub.

Appendix: source

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

    geodata: join(buildFolder, 'geodata'),
    web: join(buildFolder, 'www'),
  };

  let redisConfig = {
    host: dto.REDIS_HOSTNAME || 'redis',
    port: dto.REDIS_PORT || 6379,
    db: dto.REDIS_DBINDEX || 0,
    username: dto.REDIS_USERNAME || undefined,
    password: dto.REDIS_PASSWORD || undefined,
    path: dto.REDIS_SOCKET || undefined,
  };

  const redisUrl = dto.REDIS_URL;
  if (redisUrl && redisUrl.startsWith('ioredis://')) {
    try {
      redisConfig = JSON.parse(Buffer.from(redisUrl.slice(10), 'base64').toString());
    } catch (error) {
      throw new Error('Failed to decode redis options', { cause: error });
    }
  }

  const includedTelemetries =
    dto.IMMICH_TELEMETRY_INCLUDE === 'all'
      ? new Set(Object.values(ImmichTelemetry))
      : asSet<ImmichTelemetry>(dto.IMMICH_TELEMETRY_INCLUDE, []);

  const excludedTelemetries = asSet<ImmichTelemetry>(dto.IMMICH_TELEMETRY_EXCLUDE, []);
  const telemetries = setDifference(includedTelemetries, excludedTelemetries);
  for (const telemetry of telemetries) {
    if (!TELEMETRY_TYPES.has(telemetry)) {
      throw new Error(`Invalid telemetry found: ${telemetry}`);
    }
  }

  const databaseConnection: DatabaseConnectionParams = dto.DB_URL
    ? { connectionType: 'url', url: dto.DB_URL }

View on GitHub (pinned to e55ac299a4)