Skip to main content
Version: v0.7.6

Configure OpenWA with environment variables

Set up OpenWA's database, storage, cache, and WhatsApp engine, then point it at the backends you actually run. Every setting is an environment variable: the defaults run with zero configuration (SQLite + local filesystem + no cache), so you change only what you need.

Prerequisites
  • OpenWA installed and able to start — see Installation.
  • Shell access to the machine (or container) running OpenWA, to edit .env or pass env vars.

How configuration is loaded

OpenWA reads configuration from environment variables, with .env.example as the single source of truth for the full list. Copy it and edit your copy:

cp .env.example .env

The server validates the file at boot and refuses to start on an invalid value (for example, an unknown ENGINE_TYPE, or empty credentials a selected backend requires). Changing a value takes effect on the next restart — there is no hot reload, and adapters cannot be swapped without a restart.

In Docker, the same variables are passed through docker-compose.yml. Names and meanings are identical; only the delivery mechanism differs.

Select your pluggable adapters

OpenWA's infrastructure backends are swappable by configuration alone — no code changes. Four boundaries are independently selectable:

BoundaryVariableOptionsDefault
DatabaseDATABASE_TYPEsqlite, postgressqlite
StorageSTORAGE_TYPElocal, s3local
CacheREDIS_ENABLEDtrue, falsefalse
WhatsApp engineENGINE_TYPEwhatsapp-web.js, baileyswhatsapp-web.js

The main database (API keys and audit logs) is always SQLite and is not configurable — only the data connection above is pluggable. See Database design terminology for the dual-database split.

Database — SQLite or PostgreSQL

SQLite is the zero-config default, stored as a file under ./data. It suits a personal bot or a handful of sessions but has a single writer. For higher write concurrency or many sessions, switch to PostgreSQL:

DATABASE_TYPE=postgres
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=openwa
DATABASE_USERNAME=openwa
DATABASE_PASSWORD=your-strong-password

For managed PostgreSQL (Supabase, Heroku, Render, Railway), enable TLS:

DATABASE_SSL=true
# Only if the provider uses a self-signed certificate:
# DATABASE_SSL_REJECT_UNAUTHORIZED=false
warning

With DATABASE_TYPE=postgres, a production build (NODE_ENV=production) refuses to start if DATABASE_PASSWORD is empty or a known placeholder. Set a strong, unique value.

Storage — local filesystem or S3/MinIO

Media files default to the local filesystem under ./data/media. To use S3 — or any S3-compatible service such as MinIO — set:

STORAGE_TYPE=s3
S3_ENDPOINT=http://localhost:9000 # point at MinIO, or your S3 endpoint
S3_BUCKET=openwa
S3_REGION=us-east-1
S3_ACCESS_KEY=your-access-key
S3_SECRET_KEY=your-secret-key

MinIO is not a separate type — it is the s3 backend. The client always uses path-style addressing, which MinIO requires and AWS S3 accepts. As with PostgreSQL, a production build refuses to start with empty or placeholder S3 credentials.

Cache — Redis (optional)

Caching is off by default, and there is no in-process cache: "no cache configured" means no cache. The cache layer fails open — when Redis is disabled or unreachable, reads return nothing and writes are skipped, so the app keeps serving from its source of truth (the database and engine). Caching is a pure optimization, never the source of truth.

Enable Redis with:

REDIS_ENABLED=true
REDIS_HOST=localhost
REDIS_PORT=6379
# REDIS_PASSWORD=your-redis-password

Engine — whatsapp-web.js or Baileys

OpenWA ships two WhatsApp engines. whatsapp-web.js (the default) drives a headless Chromium and is heavier (hundreds of MB per session). baileys is browser-free, far lighter, and supports phone-number pairing in addition to QR. Pin one explicitly:

ENGINE_TYPE=baileys

If ENGINE_TYPE is left unset, the active engine is chosen in the dashboard (Infrastructure → Engine), defaulting to whatsapp-web.js. A value set in the environment always wins over the dashboard selection.

note

Switching the engine does not migrate an existing authenticated session — each engine stores its own credentials, so a session connected under one engine must scan a fresh QR after you switch. Connect a session in Connect a session.

Key environment variables

These names are taken verbatim from .env.example. For the full list, including media size limits, observability, and MCP, read the comments in .env.example itself.

Core

VariableDefaultDescription
NODE_ENVproductionRuntime mode; production enforces the security checks noted above
API_PORT2785Port the API and dashboard listen on
LOG_LEVELinfoOne of error, warn, info, debug
AUTO_START_SESSIONSfalseAuto-start previously authenticated sessions on boot
DOMAINlocalhostDomain used in the startup banner and external links
CORS_ORIGINS*Comma-separated allowed origins; the * wildcard is refused in production

Adapters

VariableDefaultDescription
ENGINE_TYPEunsetwhatsapp-web.js or baileys; dashboard chooses when unset
SESSION_DATA_PATH./data/sessionsWhere whatsapp-web.js session auth data is stored
BAILEYS_AUTH_DIR./data/baileysWhere Baileys session auth data is stored
DATABASE_TYPEsqlitesqlite or postgres
DATABASE_HOSTlocalhostPostgreSQL host (ignored for SQLite)
DATABASE_PORT5432PostgreSQL port
DATABASE_NAMEopenwaPostgreSQL database name
DATABASE_USERNAMEopenwaPostgreSQL user
DATABASE_PASSWORD(empty)Required for PostgreSQL; no default shipped
DATABASE_SSLfalseEnable TLS for managed PostgreSQL
STORAGE_TYPElocallocal or s3
STORAGE_LOCAL_PATH./data/mediaLocal media directory
S3_ENDPOINThttp://localhost:9000S3 / MinIO endpoint
S3_BUCKETopenwaS3 / MinIO bucket
S3_REGIONus-east-1S3 region
S3_ACCESS_KEY(empty)S3 / MinIO access key; required for s3
S3_SECRET_KEY(empty)S3 / MinIO secret key; required for s3
REDIS_ENABLEDfalseEnable Redis cache/queue
REDIS_HOSTlocalhostRedis host
REDIS_PORT6379Redis port

Security, webhooks, and rate limiting

VariableDefaultDescription
API_MASTER_KEY(empty)Your own master API key; see API key authentication
API_KEY_PEPPERunsetServer-side pepper for HMAC key hashing; recommended in production
WEBHOOK_TIMEOUT10000Webhook delivery timeout in milliseconds
WEBHOOK_MAX_RETRIES3Webhook retry attempts
WEBHOOK_RETRY_DELAY5000Delay between webhook retries in milliseconds
WEBHOOK_SSRF_PROTECTtrueBlock webhook deliveries to internal/reserved addresses
RATE_LIMIT_MEDIUM_LIMIT100Max requests per window (the enforced tier)
RATE_LIMIT_MEDIUM_TTL60000Rate-limit window in milliseconds
ENABLE_SWAGGERtrue in .env.exampleServe API docs at /api/docs. When unset, the effective default is on outside production and off under NODE_ENV=production — set ENABLE_SWAGGER=true to serve it in production
BODY_SIZE_LIMIT25mbMax request body size (base64 media rides in the JSON body)

Verify your configuration

After editing .env, restart OpenWA and confirm the API is up. The base URL in local development is http://localhost:2785/api (the /api global prefix; in production it sits behind your domain and TLS).

Start with GET /api/health. This endpoint is public — it needs no API key, so you can run it before you have located your key:

curl http://localhost:2785/api/health

A healthy instance returns 200 OK with three fields:

{
"status": "ok",
"timestamp": "2026-06-26T12:00:00.000Z",
"version": "0.7.6"
}

Once that succeeds, confirm your API key works against an authenticated endpoint. GET /api/sessions lists your sessions and requires the X-API-Key header:

curl http://localhost:2785/api/sessions \
-H "X-API-Key: YOUR_API_KEY"

Replace YOUR_API_KEY with the key OpenWA printed in its startup banner (or one you set via API_MASTER_KEY). A 200 OK confirms the key is accepted; a 401 Unauthorized means it is missing or wrong. See API key authentication below.

API key authentication

Every API route requires a key in the X-API-Key header. Keys carry a role — admin, operator, or viewer — and can be scoped to specific sessions or IP ranges.

On first boot, OpenWA generates a random admin key and prints it in the startup banner (it is also written to data/.api-key). To supply your own instead, set:

API_MASTER_KEY=your-long-random-secret

For production, also set API_KEY_PEPPER to switch key hashing from SHA-256 to HMAC-SHA256. Changing the pepper invalidates all existing key hashes, so set it before issuing keys and re-issue any keys created earlier.

See Authentication for roles, scoping, and key lifecycle.

Webhooks

OpenWA delivers session and message events to your endpoints as HTTP POSTs, optionally signed with an HMAC secret so you can verify authenticity. Tune delivery with WEBHOOK_TIMEOUT, WEBHOOK_MAX_RETRIES, and WEBHOOK_RETRY_DELAY. Outbound SSRF protection is on by default (WEBHOOK_SSRF_PROTECT): webhook URLs that resolve to loopback, private, or link-local ranges are refused at registration and at delivery.

See Webhooks for the event catalog, payload shapes, and signature verification.

Troubleshooting

ProblemCauseFix
Server exits at boot with a credentials errorDATABASE_TYPE=postgres or STORAGE_TYPE=s3 with an empty or placeholder secret under NODE_ENV=productionSet a strong, unique DATABASE_PASSWORD or S3_ACCESS_KEY/S3_SECRET_KEY
Server rejects ENGINE_TYPE at bootValue is not whatsapp-web.js or baileysUse one of the two valid values, or leave it unset to choose from the dashboard
Browser blocks dashboard requests in productionCORS_ORIGINS=* is refused in productionSet explicit origins, e.g. CORS_ORIGINS=https://dashboard.yourdomain.com
Cache appears to do nothingREDIS_ENABLED=false, or Redis is unreachableCaching is optional and fails open; enable Redis or accept source-of-truth reads
401 Unauthorized on every requestMissing or wrong X-API-KeySend the key from the startup banner, or the one you set via API_MASTER_KEY
Config change had no effectValues load only at bootRestart OpenWA; adapters cannot be hot-swapped

Next steps