Skip to content

Configuration Reference

All configuration is via environment variables. In production, set DJANGO_SETTINGS_MODULE=config.settings_production.

Required

Variable Description
SECRET_KEY Django secret key. Generate with python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())".
DATABASE_URL PostgreSQL connection URL, e.g. postgres://user:pass@host:5432/dbname. Alternatively set individual DJANGO_DATABASE_* variables below.
REDIS_URL Redis connection URL, e.g. redis://host:6379.
DJANGO_ALLOWED_HOSTS Comma-separated list of hostnames the app will serve, e.g. yourdomain.com,www.yourdomain.com.
DJANGO_SETTINGS_MODULE Must be config.settings_production for production.

Database (alternative to DATABASE_URL)

Variable Default Description
DJANGO_DATABASE_NAME Database name
DJANGO_DATABASE_USER Database user
DJANGO_DATABASE_PASSWORD Database password
DJANGO_DATABASE_HOST localhost Database host
DJANGO_DATABASE_PORT 5432 Database port

Connection behaviour

These apply whether the connection comes from DATABASE_URL or the variables above.

Variable Default Description
DJANGO_DATABASE_USE_POOL True Use a psycopg connection pool. When disabled, DJANGO_DATABASE_CONN_MAX_AGE applies instead
DJANGO_DATABASE_POOL_MIN_SIZE 2 Connection pool minimum size
DJANGO_DATABASE_POOL_MAX_SIZE 35 Connection pool maximum size
DJANGO_DATABASE_POOL_TIMEOUT 10 Connection pool timeout (seconds)
DJANGO_DATABASE_CONN_MAX_AGE 0 Persistent connection lifetime, in seconds. Ignored when the pool is enabled
DJANGO_DATABASE_SSLMODE require (prefer when DEBUG) psycopg sslmode. AWS RDS Proxy requires TLS
DJANGO_DISABLE_SERVER_SIDE_CURSORS False Set to True to stop Django using server-side cursors for QuerySet.iterator(). Behind a connection proxy in transaction-pooling mode (e.g. AWS RDS Proxy) these are declared WITH HOLD and pin the session to a backend connection. Disabling them costs memory: each iterator() call then buffers its whole result set client-side

Redis (alternative to REDIS_URL)

Variable Description
REDIS_HOST Redis hostname
REDIS_PORT Redis port
REDIS_USE_TLS Set to True to enable TLS (e.g. for managed Redis with TLS)

Security

Variable Default Description
CRYPTOGRAPHY_KEY SECRET_KEY Encryption key for sensitive fields (API keys, credentials). Set explicitly in production.
CRYPTOGRAPHY_SALT Additional salt for field encryption.
CSRF_TRUSTED_ORIGINS [] Comma-separated list of trusted origins for CSRF, e.g. https://yourdomain.com. Required when behind a reverse proxy.
DJANGO_SECURE_SSL_REDIRECT True Redirect HTTP to HTTPS. Set to False if TLS is terminated upstream and you want to disable the redirect in Django.
OIDC_RSA_PRIVATE_KEY RSA private key (PEM format) for the built-in OAuth2/OIDC provider. Required to register an OAuth2 application at all: the registration form signs every application with RS256, which django-oauth-toolkit rejects when this is unset. Setting it also enables OIDC, signs ID tokens, and adds the openid/profile scopes.
OAUTH_PKCE_REQUIRED True Require PKCE for OAuth2 flows.
HEALTH_CHECK_TOKENS [] Comma-separated tokens for the /status health check endpoint.

Rate Limiting

Variable Default Description
RATE_LIMIT_ENFORCE False When False, over-limit requests are served and logged as rate_limit.would_block (sampled after the first crossing, not every request); when True, they receive HTTP 429. The channels scope does not answer to this switch: it counts and logs in both states and never returns 429.
RATE_LIMIT_API 2000/5m Request limit for the api scope, format count/window with s/m/h units. Fails open: if the limiter's cache is unreachable, requests are served.
RATE_LIMIT_ADMIN_API 100/5m Request limit for the admin_api scope (the /admin/api/* autocomplete and provider-reporting endpoints). Keyed by authenticated user, then the provider-reporting token, then client IP, so anonymous traffic cannot spend a staff member's allowance. Set RATE_LIMIT_TRUSTED_PROXY_COUNT before enforcing, or all anonymous callers behind a proxy share one bucket. Fails open: if the limiter's cache is unreachable, requests are served.
RATE_LIMIT_CHAT_API 300/5m Request limit for the chat_api scope (the /api/chat/* endpoints; the embedded chat widget is the primary caller, but non-widget clients use these endpoints too). Session starts are keyed per widget channel, except public link channels, which are keyed per visitor IP; after start, per session. Set RATE_LIMIT_TRUSTED_PROXY_COUNT before enforcing behind a proxy, or those legacy callers all share one bucket. Sits apart from RATE_LIMIT_API so one busy conversation cannot spend the team's interactive API allowance. Fails open: if the limiter's cache is unreachable, requests are served.
RATE_LIMIT_PUBLIC_CHAT 100/5m Request limit for the public_chat scope. Applies to the legacy public chat pages and to the public link page /c/<token>/, keyed per visitor IP. Set RATE_LIMIT_TRUSTED_PROXY_COUNT before enforcing behind a proxy, or every visitor starting a conversation shares one bucket. The poll that runs while a reply is being composed is excluded from the scope, so a slow answer does not spend a conversation's allowance. Over-limit requests receive the site's error page rather than a JSON body, since these views are reached in a browser. Fails open: if the limiter's cache is unreachable, requests are served.
RATE_LIMIT_CHANNELS 3000/5m Request limit for the channels scope (inbound channel deliveries: Telegram, Twilio, Meta Cloud API, Turn, SureAdhere, CommCare Connect, Slack). Keyed per chatbot channel: each delivery is counted inside its view, once it has resolved to a channel and passed the provider's signature check, so a delivery that resolves to no channel is not counted and no caller can spend another tenant's allowance. A Meta payload carrying several phone numbers is counted once per number, against each one's own channel. On Slack this covers the messages the bot answers (mentions, DMs and replies in an existing thread); other channel traffic resolves no chatbot channel and is not counted. This scope counts but never refuses, in both RATE_LIMIT_ENFORCE states: an over-limit delivery is logged as rate_limit.would_block and still processed, since refusing it would discard a participant's message rather than delay it. Traffic that never resolves to a channel is outside this scope entirely, and is bounded by the WAF rather than here. Fails open: if the limiter's cache is unreachable, deliveries are served.
RATE_LIMIT_CREDENTIALS 100/5m Request limit for the credentials scope (the OAuth client-credential endpoints at /o/token/, /o/revoke_token/ and /o/introspect/, API requests whose key or bearer token is rejected, and the CommCare Connect key exchange at /api/commcare_connect/generate_key, which issues an outbound request to CommCare Connect before it knows whether the caller's token is valid). Keyed by client IP, because a caller failing authentication has no identity to key on. Set RATE_LIMIT_TRUSTED_PROXY_COUNT before enforcing behind a proxy, or every caller shares one bucket. The one scope that fails closed: where the others serve the request when the limiter's cache is unreachable, this one refuses it once enforcement is on, so that a counter nobody can read does not become a way to brute force credentials unobserved. Successful API requests are counted under RATE_LIMIT_API instead, so a working integration is never charged to this scope.
RATE_LIMIT_TRUSTED_PROXY_COUNT 0 Number of trusted reverse proxies; required for correct client IPs behind a proxy or tunnel before enabling any IP-keyed scope.

Per-IP keying reads the client address through RATE_LIMIT_TRUSTED_PROXY_COUNT; behind a proxy or load balancer set it, or every visitor shares one bucket.

The public link page and its API access are pinned to the hostname of the Django Site row (Site.objects.get_current().domain). If that domain is not the deployed host, public links 404 and their chat starts are refused with 403. The value is cached per process, so a change needs a restart.

Email

One of the following email backends must be configured. Set DJANGO_EMAIL_BACKEND to choose:

Mailgun (default)

DJANGO_EMAIL_BACKEND=anymail.backends.mailgun.EmailBackend
MAILGUN_API_KEY=your-mailgun-api-key
MAILGUN_SENDER_DOMAIN=mail.yourdomain.com

Amazon SES

DJANGO_EMAIL_BACKEND=anymail.backends.amazon_ses.EmailBackend
# Omit these if using IAM roles:
AWS_SES_ACCESS_KEY=
AWS_SES_SECRET_KEY=
AWS_SES_REGION=us-east-1

Other settings

Variable Default Description
ACCOUNT_EMAIL_VERIFICATION mandatory Set to none to disable email verification (not recommended for production).
DJANGO_SERVER_EMAIL noreply@dimagi.com From address for error emails.
DJANGO_DEFAULT_FROM_EMAIL noreply@dimagi.com From address for user-facing emails.

File Storage (S3)

Without S3, user-uploaded files are stored on the local filesystem. This is only suitable for single-instance deployments. For multi-instance or Heroku/container deployments, use S3.

Variable Description
USE_S3_STORAGE Set to True to enable S3 for media storage.
AWS_ACCESS_KEY_ID AWS access key (omit if using IAM roles).
AWS_SECRET_ACCESS_KEY AWS secret key (omit if using IAM roles).
AWS_S3_REGION S3 region, e.g. us-east-1. May be left blank for S3-compatible providers (a dummy region is used for signing).
AWS_PUBLIC_STORAGE_BUCKET_NAME Bucket for public user uploads (e.g. profile images).
AWS_PRIVATE_STORAGE_BUCKET_NAME Bucket for private user uploads.
WHATSAPP_S3_AUDIO_BUCKET Bucket for WhatsApp voice message audio files.
AWS_S3_ENDPOINT_URL Optional. Endpoint for S3-compatible storage, e.g. https://minio.example.com. Omit for AWS S3.
AWS_S3_ADDRESSING_STYLE Optional. path (MinIO, IP endpoints), virtual (most cloud providers), or unset (auto). Must match your endpoint.
AWS_S3_CUSTOM_DOMAIN Optional. Public-media URL prefix as a browser sees it. Include the bucket for path-style (host/bucket); host-only for virtual-host/CDN. Omit for the AWS default.

S3-compatible providers

The settings above also work with any S3-compatible service (MinIO, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean Spaces, etc.). Leave the three AWS_S3_* overrides blank for plain AWS. These apply to both media storage and the WhatsApp audio bucket.

MinIO (path-style addressing — note the bucket is included in AWS_S3_CUSTOM_DOMAIN):

USE_S3_STORAGE=True
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_S3_ENDPOINT_URL=http://minio:9000
AWS_S3_ADDRESSING_STYLE=path
AWS_S3_CUSTOM_DOMAIN=minio.example.com/public-bucket
AWS_PUBLIC_STORAGE_BUCKET_NAME=public-bucket
AWS_PRIVATE_STORAGE_BUCKET_NAME=private-bucket
WHATSAPP_S3_AUDIO_BUCKET=whatsapp-audio

Cloudflare R2 (virtual-host addressing):

USE_S3_STORAGE=True
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_S3_ENDPOINT_URL=https://<accountid>.r2.cloudflarestorage.com
AWS_S3_ADDRESSING_STYLE=virtual
AWS_S3_CUSTOM_DOMAIN=pub.<accountid>.r2.cloudflarestorage.com
AWS_PUBLIC_STORAGE_BUCKET_NAME=public-bucket
AWS_PRIVATE_STORAGE_BUCKET_NAME=private-bucket

Notes:

  • Private file downloads are served via presigned URLs generated against AWS_S3_ENDPOINT_URL, so they work automatically with any provider.
  • For path-style endpoints (MinIO), AWS_S3_CUSTOM_DOMAIN must include the bucket segment; for virtual-host, it must not.

Integrations

Slack

Required only if you want users to connect Slack channels to their chatbots.

Variable Description
SLACK_CLIENT_ID Slack app client ID
SLACK_CLIENT_SECRET Slack app client secret
SLACK_SIGNING_SECRET Slack app signing secret
SLACK_BOT_NAME Display name for the Slack bot

Telegram

Variable Description
TELEGRAM_SECRET_TOKEN Optional. Secret token for verifying inbound webhook authenticity via the X-Telegram-Bot-Api-Secret-Token header. When configured, OCS registers it with Telegram's setWebhook API and rejects requests without matching tokens.

Observability

Variable Description
SENTRY_DSN Sentry DSN for error tracking.
SENTRY_ENVIRONMENT Sentry environment tag, e.g. production.
ENABLE_JSON_LOGGING Set to True for structured JSON log output (recommended for log aggregation).

Task Badger (optional)

Task Badger provides visibility into Celery task execution.

Variable Description
TASKBADGER_ORG Task Badger organisation slug
TASKBADGER_PROJECT Task Badger project slug
TASKBADGER_API_KEY Task Badger API key

Analytics

Variable Description
GOOGLE_ANALYTICS_ID Google Analytics measurement ID
Variable Description
TERMS_URL URL to your Terms of Service page (shown in the UI)
PRIVACY_POLICY_URL URL to your Privacy Policy page (shown in the UI)

Zero Trust Access (optional)

Required only when using Cloudflare Tunnel as the Zero Trust access layer. Not needed for standard reverse-proxy deployments.

Variable Description
CLOUDFLARE_TUNNEL_TOKEN Tunnel token from the Cloudflare Zero Trust dashboard. See Cloudflare Tunnel setup for how to obtain this.

System Agent

The System Agent is an internal AI assistant used for certain platform features. Configure the models it can use:

Variable Description
SYSTEM_AGENT_MODELS_HIGH Model(s) for complex tasks, e.g. openai:gpt-4o. Use comma-separated values for fallback.
SYSTEM_AGENT_MODELS_LOW Model(s) for simple tasks, e.g. openai:gpt-4o-mini.
SYSTEM_AGENT_API_KEYS Provider API keys as provider=key pairs, e.g. openai=sk-...,anthropic=sk-ant-....