open-webui/open-webui · critical · ValueError

DATABASE_PASSWORD is required when using sqlite+sqlcipher://

Error message

DATABASE_PASSWORD is required when using sqlite+sqlcipher:// URLs

What it means

SQLCipher branch of the sync engine setup in backend/open_webui/internal/db.py: when SQLALCHEMY_DATABASE_URL starts with sqlite+sqlcipher://, the code must execute "PRAGMA key = '<DATABASE_PASSWORD>'" to decrypt the database file. An unset, empty, or whitespace-only DATABASE_PASSWORD raises ValueError immediately, since opening an encrypted DB without a key is meaningless.

Source

Thrown at backend/open_webui/internal/db.py:244

        return url.replace('postgresql+psycopg2://', 'postgresql+psycopg://', 1)
    if url.startswith('postgresql://'):
        return url.replace('postgresql://', 'postgresql+psycopg://', 1)
    if url.startswith('postgres://'):
        return url.replace('postgres://', 'postgresql+psycopg://', 1)
    # For other dialects, return as-is and let SQLAlchemy handle it
    return url


# ============================================================
# SYNC ENGINE (used only for: startup migrations, config loading,
#              Alembic, peewee migration, health checks)
# ============================================================

# Handle SQLCipher URLs
if SQLALCHEMY_DATABASE_URL.startswith('sqlite+sqlcipher://'):
    database_password = os.environ.get('DATABASE_PASSWORD')
    if not database_password or database_password.strip() == '':
        raise ValueError('DATABASE_PASSWORD is required when using sqlite+sqlcipher:// URLs')

    # Extract database path from SQLCipher URL
    db_path = SQLALCHEMY_DATABASE_URL.replace('sqlite+sqlcipher://', '')

    # Create a custom creator function that uses sqlcipher3
    def create_sqlcipher_connection():
        import sqlcipher3

        conn = sqlcipher3.connect(db_path, check_same_thread=False)
        conn.execute(f"PRAGMA key = '{database_password}'")
        return conn

    # The dummy "sqlite://" URL would cause SQLAlchemy to auto-select
    # SingletonThreadPool, which non-deterministically closes in-use
    # connections when thread count exceeds pool_size, leading to segfaults
    # in the native sqlcipher3 C library. Use NullPool by default for safety,
    # or QueuePool if DATABASE_POOL_SIZE is explicitly configured.
    if isinstance(DATABASE_POOL_SIZE, int) and DATABASE_POOL_SIZE > 0:

View on GitHub (pinned to 01f4282f1f)

Solutions

  1. Set DATABASE_PASSWORD to the SQLCipher key: a non-empty string, e.g. DATABASE_PASSWORD=$(openssl rand -hex 32) stored securely.
  2. Verify it reaches the process (docker exec <container> printenv DATABASE_PASSWORD) and is not just set in the shell that built the image.
  3. Keep the same key for the life of the database file — losing it loses the data.

Example fix

# before
DATABASE_URL=sqlite+sqlcipher:///data/webui.db
# DATABASE_PASSWORD unset

# after
DATABASE_URL=sqlite+sqlcipher:///data/webui.db
DATABASE_PASSWORD=<strong-random-key>
Defensive patterns

Strategy: validation

Validate before calling

import os

if os.environ.get('DATABASE_URL', '').startswith('sqlite+sqlcipher://'):
    pw = os.environ.get('DATABASE_PASSWORD', '')
    assert pw and pw.strip(), 'DATABASE_PASSWORD must be a non-empty key for sqlcipher URLs'

Try / catch

try:
    import open_webui.internal.db  # noqa
except ValueError as e:
    raise SystemExit(f'Database init failed: {e}')  # missing key is fatal, do not retry

Prevention

When it happens

Trigger: DATABASE_URL=sqlite+sqlcipher:///data/webui.db with DATABASE_PASSWORD unset, empty (DATABASE_PASSWORD="" in .env counts as empty), or containing only whitespace.

Common situations: .env file defines the variable but with no value; secret injection failing so os.environ lacks it; whitespace/newline artifacts in the value; user expecting a prompt instead of hard failure.

Related errors


AI-assisted analysis of open-webui/open-webui@01f4282f1f (2026-08-14). Data as JSON: /api/errors/703104f35934d980. Report an issue: GitHub.