✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

EcosystemIntermediate5 min read

"SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string" in Strapi

September 9, 2026
"SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string" in Strapi

Strapi refuses to start and throws this from deep inside node_modules:

node_modules/pg/lib/sasl.js:24
  throw new Error('SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string')

The wording sends people hunting for SASL configuration, authentication mechanisms, and PostgreSQL pg_hba.conf settings. None of that is the problem.

What the error actually means

PostgreSQL 10+ uses SCRAM-SHA-256 for password authentication. When the pg Node driver starts that handshake, it needs to hash the password, and hashing requires a string. The driver checks the type first and throws this error if it gets anything else.

In practice, "anything else" is almost always undefined.

So the real message is: Strapi passed undefined as the database password. The password is missing, not wrong. A wrong password produces a different error (password authentication failed for user "...").

This is a type check failing before any network traffic reaches your database.

Confirm it in ten seconds

Add a temporary log line at the top of config/database:

export default ({ env }) => {
  console.log('DB password type:', typeof env('DATABASE_PASSWORD'));
  console.log('DB password set:', Boolean(env('DATABASE_PASSWORD')));

  return {
    connection: {
      client: 'postgres',
      connection: {
        host: env('DATABASE_HOST', '127.0.0.1'),
        port: env.int('DATABASE_PORT', 5432),
        database: env('DATABASE_NAME', 'strapi'),
        user: env('DATABASE_USERNAME', 'strapi'),
        password: env('DATABASE_PASSWORD'),
        ssl: env.bool('DATABASE_SSL', false),
      },
    },
  };
};

If that prints undefined / false, you have confirmed the diagnosis. Remove the log lines before committing, and never log the value itself, only its type.

The causes, roughly in order of frequency

1. DATABASE_PASSWORD is not set in the environment

The most common case. Your config/database reads env('DATABASE_PASSWORD') with no fallback, and the variable does not exist.

Check that .env exists at the project root, sits next to package.json, and contains the key:

DATABASE_CLIENT=postgres
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_NAME=strapi
DATABASE_USERNAME=strapi
DATABASE_PASSWORD=your-actual-password

Common variations on this mistake:

  • The file is named .env.local, env, or .env.txt. Strapi loads .env.
  • The file is in a subdirectory. Strapi loads it from the project root only.
  • You are running the process from a different working directory, so the relative path to .env does not resolve.

2. The Postgres role genuinely has no password

Fresh local installs of PostgreSQL often create a postgres superuser with no password set, relying on peer authentication for local socket connections. Strapi connects over TCP, which requires a password.

sudo -u postgres psql
\password postgres

Then set the same value in your .env.

3. A deployed environment where the variable was never added

Everything works locally and the deploy fails. The .env file is (correctly) gitignored, and nobody added the variables to the platform.

  • Docker Compose: env_file: .env or an explicit environment: block. A .env next to docker-compose.yml populates interpolation in the compose file, which is not the same as being present inside the container.
  • Dockerfile: .dockerignore almost certainly excludes .env, which is correct. Inject the values at runtime.
  • Kubernetes: the Secret exists but is not referenced by envFrom or env in the pod spec.
  • PaaS (Render, Railway, Fly, Heroku): the variable was set in one environment and not the other.

4. The connection string overrides your individual fields

If DATABASE_URL is set, and your config uses connectionString, it overrides every other connection.connection property. A DATABASE_URL that is malformed or has no password section produces exactly this error while your correctly-set DATABASE_PASSWORD sits there being ignored.

postgresql://user:password@host:5432/dbname

Check for a missing :password, or special characters that need percent-encoding: @%40, #%23, /%2F, :%3A.

5. env.int() or another typed helper on the password

password: env.int('DATABASE_PASSWORD'),   // wrong
password: env('DATABASE_PASSWORD'),       // right

A numeric password read through env.int() becomes a Number, and the driver rejects it for exactly the reason the error states. Rare, but it produces the most confusing version of this bug, because the variable is set.

6. An environment-specific config file that shadows the base one

config/env/production/database.ts completely replaces config/database.ts when NODE_ENV=production. If the production variant was written with different variable names, such as DB_PASSWORD instead of DATABASE_PASSWORD, you get an app that works in development and fails on deploy.

Check whether the directory exists:

ls -la config/env/

Making the failure legible next time

Two habits keep this from costing an afternoon.

Fail fast with a clear message. Validate required variables at the top of the config:

export default ({ env }) => {
  const password = env('DATABASE_PASSWORD');

  if (typeof password !== 'string' || password.length === 0) {
    throw new Error(
      'DATABASE_PASSWORD is missing or empty. Set it in .env (local) or in your platform environment (deployed).'
    );
  }

  return {
    connection: {
      client: 'postgres',
      connection: {
        host: env('DATABASE_HOST', '127.0.0.1'),
        port: env.int('DATABASE_PORT', 5432),
        database: env('DATABASE_NAME', 'strapi'),
        user: env('DATABASE_USERNAME', 'strapi'),
        password,
        ssl: env.bool('DATABASE_SSL', false),
      },
    },
  };
};

Keep a committed .env.example listing every key with placeholder values, so a new environment has a checklist rather than an archaeology exercise.

ErrorMeaning
client password must be a stringPassword is undefined or not a string
password authentication failed for user "x"Password is present but wrong
role "x" does not existUsername is wrong or the role was never created
database "x" does not existDatabase name is wrong or was never created
ECONNREFUSEDNothing is listening on that host and port
Knex: Timeout acquiring a connectionUsually a firewall dropping packets; see the pool timeout guide
Cannot destructure property 'client' of 'db.config.connection'The database config file itself did not load

Further reading

Theodore Kelechukwu OnyejiakuDevRel and Community | Software Developer | Technical Writer

Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.

"Cannot destructure property 'client' of 'db.config.connection'" in Strapi
EcosystemIntermediate·6 min read

"Cannot destructure property 'client' of 'db.config.connection'" in Strapi

This error means Strapi loaded no database configuration at all. The file is missing from the build, excluded from the container, or never compiled.

·September 9, 2026
"Cannot send secure cookie over unencrypted connection" in Strapi
EcosystemAdvanced·6 min read

"Cannot send secure cookie over unencrypted connection" in Strapi

Your site is on HTTPS but Strapi thinks the request came over HTTP. The fix is one config key, proxy.koa, plus the proxy headers that make it work.

·September 9, 2026
"Strapi is in production mode" when you are running development mode
EcosystemBeginner·5 min read

"Strapi is in production mode" When you are Running Development Mode

The Content-Type Builder is disabled and Strapi claims production, but your terminal says development. The rule Strapi actually applies, and the fix.

·September 9, 2026