redis/node-redis · error · TypeError

tls socket option is set to

Error message

tls socket option is set to ${options.socket.tls} which is mismatch with protocol or the URL ${options.url} passed

What it means

When both url and socket.tls are supplied to createClient, the explicit tls flag must agree with the URL scheme: rediss:// implies tls true, redis:// implies tls false. A mismatch is rejected to prevent silently opening an insecure connection the user thought was encrypted (or vice-versa).

Solutions

  1. Drop the redundant socket.tls and let the URL drive it.
  2. Make the URL and the tls flag agree (use rediss:// with tls:true, or redis:// with tls:false).

Example fix

// before
createClient({ url: 'redis://host:6379', socket: { tls: true } });

// after
createClient({ url: 'rediss://host:6379', socket: { tls: true } });
// or
createClient({ url: 'rediss://host:6379' });
Defensive patterns

Strategy: validation

Validate before calling

function resolveRedisUrl(options) {
  const wantsTls = Boolean(options.socket?.tls);
  const urlIsTls = options.url?.startsWith('rediss://');
  const urlIsPlain = options.url?.startsWith('redis://');
  if (options.url && options.socket?.tls !== undefined && (
    (wantsTls && urlIsPlain) || (!wantsTls && urlIsTls)
  )) {
    throw new TypeError(`tls flag ${wantsTls} conflicts with URL scheme ${options.url}`);
  }
  return options;
}

Prevention

When it happens

Trigger: createClient({ url: 'redis://host:6379', socket: { tls: true } }) or createClient({ url: 'rediss://host:6379', socket: { tls: false } }).

Common situations: Copy-pasting url and tls options from different examples; switching scheme without removing the explicit flag; running behind a TLS-terminating proxy where the user toggles tls but not the scheme.

Understand the failure class

Related errors


AI-assisted analysis of redis/node-redis@90fd0652bc (2026-08-11). Data as JSON: /api/errors/74b26fbee5675206. Report an issue: GitHub.

Appendix: source

Thrown at packages/client/lib/client/index.ts:457

    };
  }

  static create<
    M extends RedisModules = {},
    F extends RedisFunctions = {},
    S extends RedisScripts = {},
    RESP extends RespVersions = 3,
    TYPE_MAPPING extends TypeMapping = {}
  >(this: void, options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>) {
    return RedisClient.factory(options)(options);
  }

  static parseOptions<O extends AnyRedisClientOptions>(options: O): O {
    if (options?.url) {
      const parsed = RedisClient.parseURL(options.url);
      if (options.socket) {
        if (options.socket.tls !== undefined && options.socket.tls !== parsed.socket.tls) {
          throw new TypeError(`tls socket option is set to ${options.socket.tls} which is mismatch with protocol or the URL ${options.url} passed`)
        }
        parsed.socket = Object.assign(options.socket, parsed.socket);
      }

      Object.assign(options, parsed);
    }
    return options;
  }

  static parseURL(url: string): AnyRedisClientOptions & {
    socket: Exclude<AnyRedisClientOptions['socket'], undefined> & {
      tls: boolean
    }
  } {
    // unix:// URIs use a non-special scheme; WHATWG URL refuses to parse an
    // authority (e.g. `user:pass@`) without a host, so handle it separately.
    if (url.startsWith('unix:')) {
      return RedisClient.#parseUnixURL(url);

View on GitHub (pinned to 90fd0652bc)