Environment Variables
Complete reference for all OpenPanel environment variables
This page documents all environment variables used by OpenPanel. Variables are organized by category. Most variables are optional and have sensible defaults.
For deployment-specific configuration, see the deployment guides.
After editing .env, run openpanel reload. It checks the file for mistakes first, then recreates only the services whose settings changed; your databases keep running. A plain docker compose restart is not enough: it restarts containers with their old environment.
Database & Storage
DATABASE_URL
Type: string
Required: Yes
Default: None
PostgreSQL connection string for the main database.
Example:
DATABASE_URL=postgres://user:password@localhost:5432/openpanel?schema=publicDATABASE_URL_DIRECT
Type: string
Required: No
Default: Same as DATABASE_URL
Direct PostgreSQL connection string (bypasses connection pooling). Used for migrations and administrative operations.
Example:
DATABASE_URL_DIRECT=postgres://user:password@localhost:5432/openpanel?schema=publicREDIS_URL
Type: string
Required: Yes
Default: redis://localhost:6379
Redis connection string for caching and queue management.
Example:
REDIS_URL=redis://localhost:6379
# With password
REDIS_URL=redis://:password@localhost:6379REDIS_CACHE_COMMAND_TIMEOUT_MS
Type: number
Required: No
Default: 5000
How long, in milliseconds, a Redis command used for caching, buffers and ingest may take before it fails. Commands still fail immediately while Redis is unreachable; this only bounds a connection that is up but slow. Too low a value turns brief network stalls into failed requests and duplicate events.
Example:
REDIS_CACHE_COMMAND_TIMEOUT_MS=5000CLICKHOUSE_URL
Type: string
Required: Yes
Default: http://localhost:8123/openpanel
ClickHouse HTTP connection URL for analytics data storage.
Example:
CLICKHOUSE_URL=http://localhost:8123/openpanelCLICKHOUSE_CLUSTER
Type: boolean
Required: No
Default: false
Enable ClickHouse cluster mode. Set to true or 1 if using a ClickHouse cluster.
Example:
CLICKHOUSE_CLUSTER=trueCLICKHOUSE_SETTINGS
Type: string (JSON)
Required: No
Default: {}
Additional ClickHouse settings as a JSON object.
Example:
CLICKHOUSE_SETTINGS='{"max_execution_time": 300}'CLICKHOUSE_SETTINGS_REMOVE_CONVERT_ANY_JOIN
Type: boolean
Required: No
Default: false
Remove convert_any_join from ClickHouse settings. Used for compatibility with certain ClickHouse versions. This needs to be set if you use any clickhouse version below 25!
Application URLs
API_URL
Type: string
Required: Yes
Default: None
Public API URL exposed to the browser. Used by the dashboard frontend and API service.
Example:
API_URL=https://analytics.example.com/apiAPI_URL_SSR
Type: string
Required: No
Default: Same as API_URL
Internal API URL used only for the dashboard's server-side rendering (SSR) requests. Set this when the dashboard server can't resolve or reach the public API_URL and needs an internal address instead โ for example a Docker Compose service name or a Kubernetes cluster-internal address.
The browser always keeps using the public API_URL; only requests made from the dashboard server during SSR use API_URL_SSR. If unset, SSR falls back to API_URL.
Example:
# Docker Compose: reach the API service directly over the internal network
API_URL_SSR=http://api:3000
# Kubernetes: cluster-internal service DNS
API_URL_SSR=http://openpanel-api.default.svc.cluster.local:3000This only affects the dashboard's server-side requests. It does not change the API service itself, CORS, or any browser-facing URLs โ those still use API_URL.
DASHBOARD_URL
Type: string
Required: Yes
Default: None
Public dashboard URL exposed to the browser. Used by the dashboard frontend and API service.
Example:
DASHBOARD_URL=https://analytics.example.comAPI_CORS_ORIGINS
Type: string (comma-separated)
Required: No
Default: None
Additional CORS origins allowed for API requests. Comma-separated list of origins.
Example:
API_CORS_ORIGINS=https://app.example.com,https://another-app.comAuthentication & Security
COOKIE_SECRET
Type: string
Required: Yes
Default: None
Secret key for encrypting session cookies. Generate a secure random string (32+ characters).
It also keys two HMACs: the cookie that proves a viewer entered a password-protected share's password, and the signature on unsubscribe links. Set the same value on the API and the worker โ the worker sends the emails, the API verifies the links. Without it, password-protected shares cannot be unlocked and unsubscribe links cannot be signed.
Example:
# Generate with: openssl rand -base64 32
COOKIE_SECRET=your-random-secret-hereUNSUBSCRIBE_SECRET
Type: string
Required: No
Default: falls back to COOKIE_SECRET
Keys the HMAC on unsubscribe links, if you want it separate from COOKIE_SECRET. One of the two must be set or sending an email that carries an unsubscribe link fails; there is no built-in default, because a known default makes every unsubscribe link forgeable.
Example:
UNSUBSCRIBE_SECRET=$(openssl rand -hex 32)Never use the default value in production! Always generate a unique secret.
ENCRYPTION_KEY
Type: string (64 hexadecimal characters)
Required: For Google Search Console, two-factor authentication (TOTP) and object-store exports
Default: None
AES-256 key used for everything OpenPanel encrypts at rest: Google Search Console tokens, users' 2FA secrets and S3/GCS export credentials. Must be exactly 32 bytes encoded as 64 hex characters, and must be identical on the API and worker services. The Docker Compose setup script generates one for you.
Example:
# Generate with: openssl rand -hex 32
ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefRotating this key makes every existing ciphertext unreadable. Search Console connections must be re-authorised, 2FA must be re-enrolled and export credentials re-entered. Back it up together with your database.
COOKIE_TLDS
Type: string (comma-separated)
Required: No
Default: None
Custom multi-part TLDs for cookie domain handling. Use this when deploying on domains with public suffixes that aren't recognized by default (e.g., .my.id, .web.id, .co.id).
Example:
# For domains like abc.my.id
COOKIE_TLDS=my.id
# Multiple TLDs
COOKIE_TLDS=my.id,web.id,co.idThis is required when using domain suffixes that are public suffixes (like .co.uk). Without this, the browser will reject authentication cookies. Common examples include Indonesian domains (.my.id, .web.id, .co.id).
CUSTOM_COOKIE_DOMAIN
Type: string
Required: No
Default: None
Override the automatic cookie domain detection and set a specific domain for authentication cookies. Useful when proxying the API through your main domain or when you need precise control over cookie scope.
Example:
# Set cookies only on the main domain
CUSTOM_COOKIE_DOMAIN=.example.com
# Set cookies on a specific subdomain
CUSTOM_COOKIE_DOMAIN=.app.example.comWhen set, this completely bypasses the automatic domain parsing logic. The cookie will always be set as secure. Include a leading dot (.) to allow the cookie to be shared across subdomains.
DEMO_USER_ID
Type: string
Required: No
Default: None
User ID for demo mode. When set, creates a demo session for testing.
Example:
DEMO_USER_ID=user_1234567890ALLOW_REGISTRATION
Type: boolean
Required: No
Default: false (after first user is created)
Allow new user registrations. Set to true to enable public registration.
Example:
ALLOW_REGISTRATION=trueRegistration is automatically disabled after the first user is created. Set this to true to re-enable it.
ALLOW_INVITATION
Type: boolean
Required: No
Default: true
Allow user invitations. Set to false to disable invitation functionality.
Example:
ALLOW_INVITATION=falseAI Features
The in-app AI chat supports OpenAI and Anthropic models. Set one or both provider keys on the API service โ the model picker in the chat UI automatically shows only the models whose provider has a key configured. If neither is set, the chat drawer still opens but shows setup instructions instead of suggestions.
Available models:
- OpenAI โ
GPT-4.1,GPT-4.1 mini,GPT-5.4 mini - Anthropic โ
Claude Haiku 4.5,Claude Sonnet 4.6,Claude Opus 4.6
The AI assistant is optional. Without OPENAI_API_KEY or ANTHROPIC_API_KEY set, every other OpenPanel feature continues to work normally.
OPENAI_API_KEY
Type: string
Required: No
Default: None
OpenAI API key. When set, the OpenAI models appear in the chat model picker.
Example:
OPENAI_API_KEY=sk-your-openai-api-key-hereOPENAI_BASE_URL
Type: string
Required: No
Default: OpenAI default API base URL
Override the OpenAI API base URL. Useful for proxies, gateways, Azure-compatible endpoints, or self-hosted OpenAI-compatible providers.
Example:
OPENAI_BASE_URL=https://your-openai-compatible-endpoint.example.com/v1OPENAI_PROJECT
Type: string
Required: No
Default: None
Optional OpenAI project identifier to send with requests.
Example:
OPENAI_PROJECT=proj_1234567890OPENAI_ORGANIZATION
Type: string
Required: No
Default: None
Optional OpenAI organization identifier to send with requests.
Example:
OPENAI_ORGANIZATION=org_1234567890ANTHROPIC_API_KEY
Type: string
Required: No
Default: None
Anthropic API key. When set, the Claude models appear in the chat model picker.
Example:
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-hereANTHROPIC_BASE_URL
Type: string
Required: No
Default: Anthropic default API base URL
Override the Anthropic API base URL. Useful for proxies, gateways, or Anthropic-compatible endpoints.
Example:
ANTHROPIC_BASE_URL=https://your-anthropic-endpoint.example.comANTHROPIC_TOKEN
Type: string
Required: No
Default: None
Optional auth token sent to the Anthropic provider in addition to the API key. Use this only if your Anthropic-compatible gateway requires it.
Example:
ANTHROPIC_TOKEN=your-auth-tokenANTHROPIC_VERSION
Type: string
Required: No
Default: Provider default
Override the Anthropic API version header sent with requests.
Example:
ANTHROPIC_VERSION=2023-06-01RESEND_API_KEY
Type: string
Required: No
Default: None
Resend API key for sending transactional emails (password resets, invitations, etc.).
Example:
RESEND_API_KEY=re_xxxxxxxxxxxxxGet your API key from resend.com. Make sure to verify your sender email domain.
EMAIL_SENDER
Type: string
Required: No
Default: hello@openpanel.dev
Email address used as the sender for transactional emails. Applies to both Resend and SMTP transports.
Example:
EMAIL_SENDER=noreply@yourdomain.comSMTP_HOST
Type: string
Required: No
Default: None
SMTP server hostname. When set, OpenPanel uses SMTP to send emails instead of Resend. Takes priority over RESEND_API_KEY if both are configured.
Example:
SMTP_HOST=smtp.example.comSMTP_PORT
Type: number
Required: No
Default: 587
SMTP server port.
Example:
SMTP_PORT=587SMTP_SECURE
Type: boolean
Required: No
Default: false
Use TLS for the SMTP connection. Set to true when using port 465.
Example:
SMTP_SECURE=trueSMTP_USER
Type: string
Required: No
Default: None
SMTP authentication username. Leave unset if your server does not require authentication.
Example:
SMTP_USER=smtp-user@example.comSMTP_PASS
Type: string
Required: No
Default: None
SMTP authentication password.
Example:
SMTP_PASS=your-smtp-passwordSet SMTP_HOST to enable SMTP. If both SMTP_HOST and RESEND_API_KEY are present, SMTP takes priority. If neither is set, emails are logged to the console (useful for development).
In case of using Resend, the sender email must be verified in your Resend account.
If SMTP_HOST is set and an SMTP send attempt fails, the system will not automatically fall back to RESEND_API_KEY โ the email will be silently dropped. To use Resend instead, unset SMTP_HOST.
OAuth & Integrations
GOOGLE_CLIENT_ID
Type: string
Required: No
Default: None
OAuth client ID from Google Cloud Console. Enables Sign in with Google and is also used by the Google Search Console integration. Set it on the API, and on the worker if you use Search Console.
Example:
GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnop.apps.googleusercontent.comGOOGLE_CLIENT_SECRET
Type: string
Required: No (required together with GOOGLE_CLIENT_ID)
Default: None
OAuth client secret matching GOOGLE_CLIENT_ID. Only the API and worker need it.
Example:
GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxGOOGLE_REDIRECT_URI
Type: string
Required: For Google sign-in
Default: None
Full callback URL for Google sign-in: ${API_URL}/oauth/google/callback. Must exactly match an authorized redirect URI on the Google OAuth client. It is not derived from API_URL, so set it explicitly. See Social login.
Example:
GOOGLE_REDIRECT_URI=https://analytics.example.com/api/oauth/google/callbackGSC_GOOGLE_REDIRECT_URI
Type: string
Required: For the Google Search Console integration
Default: None
Full callback URL for connecting Google Search Console: ${API_URL}/gsc/callback. Must be registered as an authorized redirect URI on the same Google OAuth client as GOOGLE_REDIRECT_URI. Only the API needs it. See Google Search Console.
Example:
GSC_GOOGLE_REDIRECT_URI=https://analytics.example.com/api/gsc/callbackGITHUB_CLIENT_ID
Type: string
Required: No
Default: None
Client ID of a GitHub OAuth App. Enables Sign in with GitHub. Only the API needs it.
Example:
GITHUB_CLIENT_ID=Iv1.xxxxxxxxxxxxxxxxGITHUB_CLIENT_SECRET
Type: string
Required: No (required together with GITHUB_CLIENT_ID)
Default: None
Client secret of the GitHub OAuth App. Only the API needs it.
Example:
GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGITHUB_REDIRECT_URI
Type: string
Required: For GitHub sign-in
Default: None
Full callback URL for GitHub sign-in: ${API_URL}/oauth/github/callback. Must exactly match the authorization callback URL of the GitHub OAuth App. It is not derived from API_URL, so set it explicitly.
Example:
GITHUB_REDIRECT_URI=https://analytics.example.com/api/oauth/github/callbackThe dashboard only shows the social login buttons and the Search Console settings when the API has both the client ID and the redirect URI for that feature. The dashboard itself needs none of these variables.
SLACK_CLIENT_ID
Type: string
Required: No
Default: None
Slack OAuth client ID for Slack integration.
Example:
SLACK_CLIENT_ID=1234567890.1234567890SLACK_CLIENT_SECRET
Type: string
Required: No
Default: None
Slack OAuth client secret for Slack integration.
Example:
SLACK_CLIENT_SECRET=your-slack-client-secretSLACK_OAUTH_REDIRECT_URL
Type: string
Required: No
Default: None
Slack OAuth redirect URL. Must match the redirect URI configured in your Slack app.
Example:
SLACK_OAUTH_REDIRECT_URL=https://analytics.example.com/api/integrations/slack/callbackSLACK_STATE_SECRET
Type: string
Required: No
Default: None
Secret for signing Slack OAuth state parameter.
Example:
SLACK_STATE_SECRET=your-state-secretSelf-hosting
SELF_HOSTED
Type: boolean
Required: No
Default: false
Enable self-hosted mode. Set to true or 1 to enable self-hosting features. Used by both the dashboard frontend and API service.
Example:
SELF_HOSTED=trueWorker & Queue
DISABLE_BULLBOARD
Type: boolean
Required: No
Default: false
Disable BullMQ board UI. Set to true or 1 to disable the queue monitoring dashboard.
Example:
DISABLE_BULLBOARD=trueADMIN_USERNAME / ADMIN_PASSWORD
Type: string
Required: No
Default: None
HTTP Basic credentials for operator-only surfaces. Today that is the queue dashboard at /bullboard, which can add, retry and clean jobs; it already requires a dashboard session, and these credentials narrow it from "any signed-up user" to "an operator". Set both or neither โ half a pair is a startup error. The browser prompts for them.
Example:
ADMIN_USERNAME=ops
ADMIN_PASSWORD=$(openssl rand -hex 16)DISABLE_WORKERS
Type: boolean
Required: No
Default: false
Disable all worker processes. Set to true or 1 to disable background job processing.
Example:
DISABLE_WORKERS=trueENABLED_QUEUES
Type: string (comma-separated)
Required: No
Default: All queues enabled
Comma-separated list of queue names to enable. Accepted queues: events, sessions, cron, notification, import, insights, gsc, cohortCompute. An unknown value (including the old events_kafka name, see below) fails startup with a non-zero exit rather than being silently ignored.
Example:
ENABLED_QUEUES=events,sessions,cronevents is the Kafka-based events consumer โ there is only one events transport now, so there is no separate events_kafka queue name. If you're upgrading from a version that used ENABLED_QUEUES=events_kafka, rename it to ENABLED_QUEUES=events; the old name is now rejected at boot. See the Event Streaming (Kafka) section for the full configuration. A common high-throughput layout is to run one worker pool with ENABLED_QUEUES=events and another pool with everything else.
CRON_CONCURRENCY
Type: number
Required: No
Default: 4
Number of scheduled jobs a worker runs at the same time: buffer flushes, the session reaper and the other cron jobs. Each buffer flush holds its own lock, so raising this lets different buffers flush in parallel without two workers flushing the same one. Raise it if one buffer's backlog keeps growing while the others drain.
Example:
CRON_CONCURRENCY=6GSC_CONCURRENCY
Type: number
Required: No
Default: 5
Number of Google Search Console sync/backfill jobs a worker runs in parallel. Only relevant when the integration is enabled; requires gsc (and cron) in ENABLED_QUEUES if that variable is set.
Example:
GSC_CONCURRENCY=2QUEUE_CLUSTER
Type: boolean
Required: No
Default: false
Enable Redis cluster mode for queues. When enabled, queue names are wrapped with {} for Redis cluster sharding.
Set it before the first start. Turning it on later renames every queue key, and jobs already queued under the old names are never processed.
Example:
QUEUE_CLUSTER=trueEvent Streaming (Kafka)
OpenPanel can optionally ingest events through a Kafka-compatible broker (Kafka itself, Redpanda, etc.) instead of the default Redis-backed events queue. This is useful at high throughput, where many consumer instances can process partitions in parallel.
Routing is all-or-nothing: if KAFKA_BROKERS is set, every event is produced to the Kafka topic; otherwise all events stay on the default Redis-backed events queue. Set ENABLED_QUEUES=events on the worker(s) that should consume from Kafka.
Connections are unauthenticated plaintext by default. For managed or production clusters, enable TLS with KAFKA_SSL and SASL authentication (plain, scram-sha-256 or scram-sha-512) with the KAFKA_SASL_* variables below.
The client speaks the Kafka wire protocol via kafkajs, so any Kafka-compatible broker works. self-hosting/docker-compose.template.yml ships an op-rp service running Redpanda โ a lightweight, single-binary broker โ by default, with KAFKA_BROKERS already pointed at it. You can instead point KAFKA_BROKERS at any Kafka cluster of your own.
KAFKA_BROKERS
Type: string (comma-separated)
Required: Yes (to enable the Kafka path)
Default: None
Comma-separated list of Kafka broker addresses. When set, all events are produced to Kafka; when unset, the Kafka producer/consumer is disabled and all events fall back to the default Redis-backed queue.
Example:
KAFKA_BROKERS=redpanda:9092
# Multiple brokers
KAFKA_BROKERS=kafka-1:9092,kafka-2:9092,kafka-3:9092KAFKA_SSL
Type: boolean
Required: No
Default: false (automatically true when SASL credentials are set)
Connect to the brokers over TLS. Set this to true for a TLS-only cluster without authentication. When KAFKA_SASL_USERNAME/KAFKA_SASL_PASSWORD are set, TLS is enabled automatically; set KAFKA_SSL=false explicitly to send SASL over plaintext (only sensible on a trusted internal network). Any value other than true/1/false/0 fails startup.
Example:
KAFKA_SSL=trueKAFKA_SSL_CA_PATH
Type: string
Required: No
Default: None (system trust store)
Path to a PEM-encoded CA certificate (or bundle) to trust in addition to the system trust store. Use this when your brokers present certificates signed by a private or self-signed CA. Requires TLS to be enabled; an unreadable file fails startup.
Example:
KAFKA_SSL_CA_PATH=/etc/openpanel/kafka-ca.pemKAFKA_SSL_REJECT_UNAUTHORIZED
Type: boolean
Required: No
Default: true
Whether to reject broker certificates that cannot be verified. Only set to false for local development against a self-signed broker; prefer KAFKA_SSL_CA_PATH in production. Requires TLS to be enabled.
Example:
KAFKA_SSL_REJECT_UNAUTHORIZED=falseKAFKA_SASL_USERNAME
Type: string
Required: No (required together with KAFKA_SASL_PASSWORD to enable SASL)
Default: None
SASL username. Setting a username without a password (or vice versa) is a configuration error and the process refuses to start. Credential values are never logged.
Example:
KAFKA_SASL_USERNAME=openpanelKAFKA_SASL_PASSWORD
Type: string
Required: No (required together with KAFKA_SASL_USERNAME to enable SASL)
Default: None
SASL password.
Example:
KAFKA_SASL_PASSWORD=your-secretKAFKA_SASL_MECHANISM
Type: string
Required: No
Default: scram-sha-512
SASL mechanism to authenticate with. One of plain, scram-sha-256 or scram-sha-512 (case-insensitive). Any other value is rejected at startup, before a Kafka client is created.
Example:
# TLS + SASL/SCRAM-SHA-512 (the default when credentials are set)
KAFKA_BROKERS=kafka-1.example.com:9093,kafka-2.example.com:9093
KAFKA_SASL_USERNAME=openpanel
KAFKA_SASL_PASSWORD=your-secret
# TLS + SASL/PLAIN with a private CA
KAFKA_BROKERS=kafka.internal:9093
KAFKA_SASL_MECHANISM=plain
KAFKA_SASL_USERNAME=openpanel
KAFKA_SASL_PASSWORD=your-secret
KAFKA_SSL_CA_PATH=/etc/openpanel/kafka-ca.pemThe producer runs in idempotent mode, so the SASL principal needs the IdempotentWrite cluster permission in addition to Write/Describe on the topic and Read on the consumer group. Managed Kafka providers usually grant this by default; on a locked-down cluster you may need to add it explicitly.
KAFKA_EVENTS_TOPIC
Type: string
Required: No
Default: events
Name of the Kafka topic used for incoming events.
Example:
KAFKA_EVENTS_TOPIC=openpanel-eventsKAFKA_EVENTS_TOPIC_PARTITIONS
Type: number
Required: No
Default: 24
Number of partitions the bundled Redpanda service creates the KAFKA_EVENTS_TOPIC topic with on first boot (matches the partition count OpenPanel Cloud runs). Only takes effect on an empty data volume โ it does not repartition a topic that already exists.
Example:
KAFKA_EVENTS_TOPIC_PARTITIONS=48Set this once, before the first openpanel up. The Kafka/Redpanda partitioner is murmur2(key) % numPartitions, so raising the partition count after the topic already has data reshuffles where every key lands โ it silently breaks per-key event ordering and the mutual exclusion the session buffer relies on for events produced before the change. To raise it later: stop producers, let consumers fully drain the backlog, delete the events topic, then restart so it's recreated with the new count.
KAFKA_CONSUMER_GROUP
Type: string
Required: No
Default: openpanel-events
Kafka consumer group ID used by the worker. All worker replicas with the same group ID share partition assignments cooperatively.
Changing it on an existing install starts a new group, which reads the events topic from the oldest message it still retains (48 hours on the bundled Redpanda). Expect duplicate events for that window.
Example:
KAFKA_CONSUMER_GROUP=openpanel-eventsKAFKA_CLIENT_ID
Type: string
Required: No
Default: openpanel
Kafka client ID used by both producer and consumer. Shows up in broker logs and metrics.
Example:
KAFKA_CLIENT_ID=openpanel-prodKAFKA_PARTITIONS_CONCURRENT
Type: number
Required: No
Default: 8
Maximum number of partitions a single consumer instance processes concurrently. Has no effect beyond the number of partitions actually assigned to this consumer.
Example:
KAFKA_PARTITIONS_CONCURRENT=12KAFKA_MIN_MESSAGES
Type: number
Required: No
Default: 1
Minimum number of messages the broker should accumulate before responding to a fetch request (each message is assumed to be ~1 KiB internally). Higher values create larger batches at the cost of slightly higher latency.
Example:
KAFKA_MIN_MESSAGES=16KAFKA_MAX_WAIT_MS
Type: number
Required: No
Default: 500
Maximum time in milliseconds the broker waits to fulfil KAFKA_MIN_MESSAGES before returning a fetch response. Caps end-to-end latency under low traffic.
Example:
KAFKA_MAX_WAIT_MS=200KAFKA_MAX_MESSAGES_PER_PARTITION
Type: number
Required: No
Default: 256
Maximum number of messages returned per partition per fetch request (each message is assumed to be ~1 KiB internally). Caps in-memory batch size during backlog recovery and bounds the worst-case time the consumer spends on a single batch.
Example:
KAFKA_MAX_MESSAGES_PER_PARTITION=512KAFKA_SESSION_TIMEOUT_MS
Type: number
Required: No
Default: 30000
Consumer group session timeout in milliseconds. The broker considers a consumer dead and rebalances its partitions if it does not heartbeat within this window.
Example:
KAFKA_SESSION_TIMEOUT_MS=45000KAFKA_HEARTBEAT_INTERVAL_MS
Type: number
Required: No
Default: 3000
How often, in milliseconds, the consumer sends background heartbeats to the broker. Should be well below KAFKA_SESSION_TIMEOUT_MS.
Example:
KAFKA_HEARTBEAT_INTERVAL_MS=3000KAFKA_REBALANCE_TIMEOUT_MS
Type: number
Required: No
Default: 60000
How long, in milliseconds, the consumer group waits for every member to rejoin during a rebalance. No consumer processes events while it waits, so a member that stopped without leaving the group stalls ingestion for up to this long. Raise it only if a single batch can take longer than this to finish.
Example:
KAFKA_REBALANCE_TIMEOUT_MS=60000Buffers
SESSION_BUFFER_BATCH_SIZE
Type: number
Required: No
Default: 10000
Session rows read from Redis per batch. Rows for the same session are squashed within a batch, so larger batches insert fewer rows.
Example:
SESSION_BUFFER_BATCH_SIZE=20000SESSION_BUFFER_CHUNK_SIZE
Type: number
Required: No
Default: Same as SESSION_BUFFER_BATCH_SIZE
Session rows per ClickHouse insert. By default each batch is one insert. A smaller value splits the batch into chunks inserted in parallel, but if one chunk fails the whole batch is retried and the chunks that already landed are written twice, which corrupts those sessions. Leave it unset unless a single insert is too large for your ClickHouse.
Example:
SESSION_BUFFER_CHUNK_SIZE=10000EVENT_BUFFER_BATCH_SIZE
Type: number
Required: No
Default: 4000
Batch size for event buffer operations.
Example:
EVENT_BUFFER_BATCH_SIZE=5000EVENT_BUFFER_CHUNK_SIZE
Type: number
Required: No
Default: 1000
Chunk size for event buffer operations.
Example:
EVENT_BUFFER_CHUNK_SIZE=2000PROFILE_BUFFER_BATCH_SIZE
Type: number
Required: No
Default: Buffer-specific default
Batch size for profile buffer operations.
Example:
PROFILE_BUFFER_BATCH_SIZE=5000PROFILE_BUFFER_CHUNK_SIZE
Type: number
Required: No
Default: Buffer-specific default
Chunk size for profile buffer operations.
Example:
PROFILE_BUFFER_CHUNK_SIZE=1000PROFILE_BUFFER_TTL_IN_SECONDS
Type: number
Required: No
Default: Buffer-specific default
Time-to-live in seconds for profile buffer entries.
Example:
PROFILE_BUFFER_TTL_IN_SECONDS=3600BUFFER_CH_INSERT_CONCURRENCY
Type: number
Required: No
Default: 5
How many ClickHouse inserts one buffer flush runs in parallel when it splits a batch into several chunks (*_BUFFER_CHUNK_SIZE rows each).
Example:
BUFFER_CH_INSERT_CONCURRENCY=8BOT_BUFFER_BATCH_SIZE
Type: number
Required: No
Default: Buffer-specific default
Batch size for bot detection buffer operations.
Example:
BOT_BUFFER_BATCH_SIZE=1000Analytics Behavior
Performance & Tuning
EVENT_LIST_MAX_LOOKBACK_DAYS
Type: number (whole days, positive integer)
Required: No
Default: 1825 (5 years)
Ceiling for the empty-result lookback on the event list. When a page's cursor window matches nothing, the window doubles and retries until it reaches this many days. With filters that no index can prune (e.g. a properties value) each retry is a full scan of its window, so large deployments can bound the worst case with a small, human-sized value. Invalid values (non-integer, zero, negative) keep the default.
Example:
EVENT_LIST_MAX_LOOKBACK_DAYS=7SESSION_LIST_MAX_LOOKBACK_DAYS
Type: number (whole days, positive integer)
Required: No
Default: 365
Same ceiling for the session list, which has its own default. Invalid values keep the default.
Example:
SESSION_LIST_MAX_LOOKBACK_DAYS=7COHORT_QUERY_MEMORY_LIMIT_BYTES
Type: number (bytes, positive integer)
Required: No
Default: unset (server defaults govern)
Hard memory cap applied to property-cohort queries, which aggregate every profile row for a project and are the one cohort query that can outgrow the server's headroom. When set without COHORT_QUERY_SPILL_BYTES, the spill threshold is derived as a third of this limit so the query spills to disk before it can be killed. Unset (together with the spill variable) applies no per-query settings at all. Invalid values are ignored.
Example:
COHORT_QUERY_MEMORY_LIMIT_BYTES=1400000000COHORT_QUERY_SPILL_BYTES
Type: number (bytes, positive integer)
Required: No
Default: unset (derived as COHORT_QUERY_MEMORY_LIMIT_BYTES / 3 when that is set)
Point past which a property-cohort GROUP BY spills to disk instead of growing in memory. Must sit below the memory limit โ a threshold at or above the kill limit means the query dies before it ever spills, so inverted pairs are re-derived. Invalid values are ignored.
Example:
COHORT_QUERY_SPILL_BYTES=314572800IMPORT_BATCH_SIZE
Type: number
Required: No
Default: 5000
Batch size for data import operations.
Example:
IMPORT_BATCH_SIZE=10000EVENT_PROPERTY_VALUE_AUTOCOMPLETE_LIMIT
Type: number (positive integer)
Required: No
Default: 500
Cap on distinct values returned per event property to the filter autocomplete. High-cardinality keys (ids, urls, tokens) can hold millions of distinct values; the most recent ones win. Invalid values keep the default.
Example:
EVENT_PROPERTY_VALUE_AUTOCOMPLETE_LIMIT=1000IP_HEADER_ORDER
Type: string (comma-separated)
Required: No
Default: See default order
Custom order of HTTP headers to check for client IP address. Useful when behind specific proxies or CDNs.
Example:
IP_HEADER_ORDER=cf-connecting-ip,x-real-ip,x-forwarded-forThe default order includes: openpanel-client-ip, cf-connecting-ip, true-client-ip, x-client-ip, x-forwarded-for, x-real-ip, and others. See the source code for the complete default list.
SHUTDOWN_FORCE_EXIT_MS
Type: number
Required: No
Default: 20000
How long, in milliseconds, a graceful shutdown may take before the process exits anyway with code 1. A shutdown stops consuming, writes buffered events to Redis and lets running scheduled jobs finish. Keep it below your orchestrator's kill timeout so the process can exit on its own.
Example:
SHUTDOWN_FORCE_EXIT_MS=30000API_PORT
Type: number
Required: No
Default: 3000
Port for the API service to listen on. Workers listen on the same port for their health checks and /metrics.
Example:
API_PORT=3000API_HOST
Type: string
Required: No
Default: 0.0.0.0 (production) / localhost (development)
Host address for the API service to bind to. Set to :: to enable IPv6 support (useful for platforms like Railway that use IPv6 for internal networking).
Example:
# Default IPv4 only
API_HOST=0.0.0.0
# IPv6 (dual-stack, accepts both IPv4 and IPv6)
API_HOST=::Use API_HOST=:: when deploying on platforms like Railway where private networking requires IPv6. The :: address enables dual-stack mode, accepting both IPv4 and IPv6 connections on most systems.
Logging
LOG_LEVEL
Type: string
Required: No
Default: info
Logging level. Options: error, warn, info, debug.
Example:
LOG_LEVEL=debugLOG_SILENT
Type: boolean
Required: No
Default: false
Disable all logging output. Set to true to silence logs.
Example:
LOG_SILENT=trueLOG_PREFIX
Type: string
Required: No
Default: None
Prefix for log messages. Useful for identifying logs from different services.
Example:
LOG_PREFIX=apiHYPERDX_API_KEY
Type: string
Required: No
Default: None
HyperDX API key for sending logs to HyperDX for monitoring and analysis.
Example:
HYPERDX_API_KEY=your-hyperdx-api-keyGeo
MAXMIND_LICENSE_KEY
Type: string
Required: No
Default: None
MaxMind GeoLite2 license key for downloading GeoIP databases.
Example:
MAXMIND_LICENSE_KEY=your-maxmind-license-keyGet your license key from MaxMind. Required for downloading GeoIP databases.
Quick Reference
Required Variables
For a basic self-hosted installation, these variables are required:
DATABASE_URL- PostgreSQL connectionREDIS_URL- Redis connectionCLICKHOUSE_URL- ClickHouse connectionAPI_URL- API endpoint URLDASHBOARD_URL- Dashboard URLCOOKIE_SECRET- Session encryption secret
Optional but Recommended
RESEND_API_KEYorSMTP_HOST- For email features (pick one)EMAIL_SENDER- Email sender addressOPENAI_API_KEYand/orANTHROPIC_API_KEY- For the in-app AI chat assistantENCRYPTION_KEY- Required for Google Search Console, 2FA and exportsGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_REDIRECT_URI- Sign in with GoogleGITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,GITHUB_REDIRECT_URI- Sign in with GitHubGSC_GOOGLE_REDIRECT_URI- Google Search Console integration