Mintplex-Labs/anything-llm · critical · Error

Cannot create JWT as JWT_SECRET is unset.

Error message

Cannot create JWT as JWT_SECRET is unset.

What it means

Thrown by makeJWT() when process.env.JWT_SECRET is falsy. The function signs JWTs for sessions and API tokens; without a secret, signing is impossible and would silently produce an insecure token, so it refuses. AnythingLLM expects JWT_SECRET to be set in multi-user mode; in single-user mode it may also be required for system features.

Source

Thrown at server/utils/http/index.js:27

function reqBody(request) {
  return typeof request.body === "string"
    ? JSON.parse(request.body)
    : request.body;
}

function queryParams(request) {
  return request.query;
}

/**
 * Creates a JWT with the given info and expiry
 * @param {object} info - The info to include in the JWT
 * @param {string} expiry - The expiry time for the JWT (default: 30 days)
 * @returns {string} The JWT
 */
function makeJWT(info = {}, expiry = "30d") {
  if (!process.env.JWT_SECRET)
    throw new Error("Cannot create JWT as JWT_SECRET is unset.");
  return JWT.sign(info, process.env.JWT_SECRET, { expiresIn: expiry });
}

/**
 * Gets the user from the session
 * Note: Only valid for multi-user mode
 * as single-user mode with password is not a "user"
 * @param {import("express").Request} request - The request object
 * @param {import("express").Response} response - The response object
 * @returns {Promise<import("@prisma/client").users | null>} The user
 */
async function userFromSession(request, response = null) {
  if (!!response && !!response.locals?.user) {
    return response.locals.user;
  }

  const auth = request.header("Authorization");
  const token = auth ? auth.split(" ")[1] : null;

View on GitHub (pinned to 526360e320)

Solutions

  1. Set JWT_SECRET in .env to a long random string (e.g. `openssl rand -hex 32`).
  2. For Docker, pass -e JWT_SECRET=... or mount the env file.
  3. Add a startup check that fails fast with a clear message if JWT_SECRET is missing.
  4. Re-issue existing sessions/tokens after rotating the secret, since old tokens become invalid.

Example fix

// before
// .env has no JWT_SECRET -> makeJWT throws on first login

// after
// .env
JWT_SECRET=9f2c...long-random-hex...

// server bootstrap
if (!process.env.JWT_SECRET && MULTI_USER_MODE)
  throw new Error('JWT_SECRET is required in multi-user mode');
Defensive patterns

Strategy: validation

Validate before calling

if (!process.env.JWT_SECRET || process.env.JWT_SECRET.length < 16)
  throw new Error('JWT_SECRET must be set to a strong random string');

Type guard

function hasJwtSecret(): boolean {
  return typeof process.env.JWT_SECRET === 'string' && process.env.JWT_SECRET.length > 0;
}

Try / catch

try {
  const token = makeJWT(info, expiry);
} catch (e) {
  if (e.message.includes('JWT_SECRET is unset'))
    return res.status(503).json({ error: 'Server auth not configured' });
  throw e;
}

Prevention

When it happens

Trigger: Booting the server without JWT_SECRET in .env; a Docker container missing the env var; running in an environment where the .env file wasn't loaded (wrong NODE_ENV path). Any login, session creation, or API-key issuance path that calls makeJWT.

Common situations: Fresh install that skipped the env bootstrap; a deployment that rotated secrets and forgot to redeploy; CI running without the secret; switching NODE_ENV so a different .env file is loaded.

Related errors


AI-assisted analysis of Mintplex-Labs/anything-llm@526360e320 (2026-08-13). Data as JSON: /api/errors/ccbb992e69ab2d04. Report an issue: GitHub.