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.
- OpenWA installed and able to start — see Installation.
- Shell access to the machine (or container) running OpenWA, to edit
.envor 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:
| Boundary | Variable | Options | Default |
|---|---|---|---|
| Database | DATABASE_TYPE | sqlite, postgres | sqlite |
| Storage | STORAGE_TYPE | local, s3 | local |
| Cache | REDIS_ENABLED | true, false | false |
| WhatsApp engine | ENGINE_TYPE | whatsapp-web.js, baileys | whatsapp-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
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.
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
| Variable | Default | Description |
|---|---|---|
NODE_ENV | production | Runtime mode; production enforces the security checks noted above |
API_PORT | 2785 | Port the API and dashboard listen on |
LOG_LEVEL | info | One of error, warn, info, debug |
AUTO_START_SESSIONS | false | Auto-start previously authenticated sessions on boot |
DOMAIN | localhost | Domain used in the startup banner and external links |
CORS_ORIGINS | * | Comma-separated allowed origins; the * wildcard is refused in production |
Adapters
| Variable | Default | Description |
|---|---|---|
ENGINE_TYPE | unset | whatsapp-web.js or baileys; dashboard chooses when unset |
SESSION_DATA_PATH | ./data/sessions | Where whatsapp-web.js session auth data is stored |
BAILEYS_AUTH_DIR | ./data/baileys | Where Baileys session auth data is stored |
DATABASE_TYPE | sqlite | sqlite or postgres |
DATABASE_HOST | localhost | PostgreSQL host (ignored for SQLite) |
DATABASE_PORT | 5432 | PostgreSQL port |
DATABASE_NAME | openwa | PostgreSQL database name |
DATABASE_USERNAME | openwa | PostgreSQL user |
DATABASE_PASSWORD | (empty) | Required for PostgreSQL; no default shipped |
DATABASE_SSL | false | Enable TLS for managed PostgreSQL |
STORAGE_TYPE | local | local or s3 |
STORAGE_LOCAL_PATH | ./data/media | Local media directory |
S3_ENDPOINT | http://localhost:9000 | S3 / MinIO endpoint |
S3_BUCKET | openwa | S3 / MinIO bucket |
S3_REGION | us-east-1 | S3 region |
S3_ACCESS_KEY | (empty) | S3 / MinIO access key; required for s3 |
S3_SECRET_KEY | (empty) | S3 / MinIO secret key; required for s3 |
REDIS_ENABLED | false | Enable Redis cache/queue |
REDIS_HOST | localhost | Redis host |
REDIS_PORT | 6379 | Redis port |
Security, webhooks, and rate limiting
| Variable | Default | Description |
|---|---|---|
API_MASTER_KEY | (empty) | Your own master API key; see API key authentication |
API_KEY_PEPPER | unset | Server-side pepper for HMAC key hashing; recommended in production |
WEBHOOK_TIMEOUT | 10000 | Webhook delivery timeout in milliseconds |
WEBHOOK_MAX_RETRIES | 3 | Webhook retry attempts |
WEBHOOK_RETRY_DELAY | 5000 | Delay between webhook retries in milliseconds |
WEBHOOK_SSRF_PROTECT | true | Block webhook deliveries to internal/reserved addresses |
RATE_LIMIT_MEDIUM_LIMIT | 100 | Max requests per window (the enforced tier) |
RATE_LIMIT_MEDIUM_TTL | 60000 | Rate-limit window in milliseconds |
ENABLE_SWAGGER | true in .env.example | Serve 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_LIMIT | 25mb | Max 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
| Problem | Cause | Fix |
|---|---|---|
| Server exits at boot with a credentials error | DATABASE_TYPE=postgres or STORAGE_TYPE=s3 with an empty or placeholder secret under NODE_ENV=production | Set a strong, unique DATABASE_PASSWORD or S3_ACCESS_KEY/S3_SECRET_KEY |
Server rejects ENGINE_TYPE at boot | Value is not whatsapp-web.js or baileys | Use one of the two valid values, or leave it unset to choose from the dashboard |
| Browser blocks dashboard requests in production | CORS_ORIGINS=* is refused in production | Set explicit origins, e.g. CORS_ORIGINS=https://dashboard.yourdomain.com |
| Cache appears to do nothing | REDIS_ENABLED=false, or Redis is unreachable | Caching is optional and fails open; enable Redis or accept source-of-truth reads |
401 Unauthorized on every request | Missing or wrong X-API-Key | Send the key from the startup banner, or the one you set via API_MASTER_KEY |
| Config change had no effect | Values load only at boot | Restart OpenWA; adapters cannot be hot-swapped |
Next steps
- Connect a session — authenticate a WhatsApp account and confirm it is online.
- Authentication — API key roles, scoping, and management.
- Deployment — production hardening and reverse-proxy setup.