Skip to main content
ekso.json is the single source of truth for install-time configuration. The bundle ships with a complete ekso.json pre-configured for the install path you picked — most customers never edit it. This page documents every field, what it does, and when you’d touch it. ekso.json is intentionally minimal: just the cryptographic keys the app needs to boot before the database exists, plus the database connection string. Everything else — mail provider, storage backend, S3 credentials, SMTP password, AI keys, license activation — lives in the database and is configured via the Settings UI after first-run, with sensitive values encrypted at rest using the Secrets.EncryptionKey field below.

File layout

ekso.json lives next to docker-compose.yml in the bundle. Compose mounts it into both the app and worker containers. Edit, then docker compose restart to pick up changes. The bundle ships two sibling folders alongside ekso.json: ./data/ (database files and attachments) and ./backups/ (automatic daily snapshots). Those are not configured here — they’re properties of the docker-compose.yml. See Data management for layout, retention, and restore details.

Sections

SectionPurpose
SecretsInstall-lifetime cryptographic keys: JWT signing + admin-settings encryption
DatabasePostgres or SQL Server connection strings
That’s it. There are no Misc, Smtp, or Storage sections in ekso.json — those settings now live in the admin Settings UI. See Configure storage and email for the post-install walkthrough.

Secrets

"Secrets": {
    "JwtKey":        "<32-char random string — pre-baked per download>",
    "EncryptionKey": "<32-char random string — pre-baked per download>"
}
FieldDescription
Secrets.JwtKeySymmetric secret used to sign user session JWTs. Pre-baked unique-per-download. Minimum 32 bytes of entropy.
Secrets.EncryptionKeyAES-GCM key for encrypting admin-managed settings (S3 credentials, SMTP password, Resend API key, mailbox/ticketing creds). Pre-baked unique-per-download. Either a 32-character ASCII string OR base64-encoded 32 bytes (44 chars with = padding).
Rotating Secrets.JwtKey signs every active user out — every session token in the wild was signed with the old key and will no longer validate. Plan a rotation window and brief your users.Rotating Secrets.EncryptionKey makes every encrypted admin setting unreadable. After a rotation, re-enter S3 credentials, SMTP password, Resend API key, mailbox creds, etc. via the Settings UI. Plan accordingly.
The session-JWT issuer and audience are install-wide constants (both "Ekso"). They are not configurable.

Generating new keys

You don’t normally generate these yourself — every download from ekso.app ships an ekso.json with both fields pre-populated, and the Render Blueprint mints them via generateValue: true. The recipes below are for the rare cases that genuinely need a fresh key: a planned rotation, an ekso.local.json for dev, or a custom orchestrator that bypasses the standard install flows. Secrets.JwtKey — any string with at least 32 bytes of entropy. The Dotcom seeder uses 48 random bytes encoded as base64url:
# macOS / Linux — 64-char base64url, no padding
openssl rand -base64 48 | tr '+/' '-_' | tr -d '='
# Windows PowerShell — same shape
[Convert]::ToBase64String((1..48 | ForEach-Object { Get-Random -Maximum 256 })).TrimEnd('=').Replace('+','-').Replace('/','_')
Secrets.EncryptionKey — 32 random bytes, base64-encoded. The cipher accepts either form (32-char ASCII or base64-of-32-bytes); the base64 form is canonical and avoids the “did you mean 32 chars or 32 bytes” trap:
# macOS / Linux — 44-char base64 with one `=` padding char
openssl rand -base64 32
# Windows PowerShell
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
# Cross-platform Python
import secrets, base64
print(base64.b64encode(secrets.token_bytes(32)).decode())
The EksoCipher constructor refuses to boot on a missing or wrong-length EncryptionKey with a clear error — your Backend logs will tell you immediately if you’ve pasted the wrong value.

Database

"Database": {
    "Provider": "Postgres",
    "TransactConnection": "Host=...",
    "MartConnection": "Host=..."
}
FieldDescription
Database.Provider"Postgres" (recommended) or "SqlServer". Determines which dialect, driver, and Dapper type-handlers Ekso wires up against the connection strings below.
Database.TransactConnectionOLTP connection string — the live-work database. pgvector required for AI features when using Postgres; SQL Server 2025 required for vector search when using SQL Server.
Database.MartConnectionReporting/analytics connection. Can point to the same instance as Transact for small installs; separate it onto a read replica for heavy reporting.
Connection strings use the dialect implied by Provider. Use Postgres key=value syntax (Host=...;Port=...;Database=...) when Provider = "Postgres"; use SQL Server syntax (Server=...;Database=...;User Id=...) when Provider = "SqlServer".Query timeout (300s), max pool size (100), and Postgres auto-prepare (off, PgBouncer-safe) are hardcoded. They were never customised per install, so they’re no longer config knobs.
Ekso emits the schema for whichever Provider you pick on first boot. Don’t switch providers on a populated install — back up, point at a fresh database, restore.

Override mechanisms

Outside Docker (Backend dev, custom orchestrators), .NET configuration binding accepts these alternatives, in increasing precedence:
  1. ekso.json — committed default
  2. ekso.local.json — same directory, gitignored, for developer overrides
  3. Environment variables — section separator is __, e.g. Secrets__JwtKey, Database__TransactConnection
The last value wins. Env vars are useful for dev shells and CI runners; ekso.local.json for persistent dev overrides; ekso.json for the canonical install config. The Render Blueprint sets every field via env vars (Secrets via generateValue: true, Database via fromDatabase) — there’s no ekso.json on Render at all.

Settings not in ekso.json

Everything operational lives in the database and is configured via the Settings UI after first run, with sensitive values encrypted at rest using Secrets.EncryptionKey:
  • Outbound mail — Resend or SMTP credentials. See Configure storage and email for the walkthrough.
  • Storage backend — Local filesystem or S3-compatible bucket (AWS S3, Cloudflare R2, Backblaze B2, MinIO). Same guide.
  • AI providers and keys — OpenAI, Azure OpenAI, Anthropic. Per-tenant. See Settings → AI.
  • License activation — pasted from your welcome email during the first-run wizard; rotates with the licence.
  • Authentication providers (Entra SSO, password policies, 2FA) — see Authentication.
  • Mailbox / ticketing config, rule webhooks, integrations — see the per-feature pages under Core concepts.
This split is deliberate: install-time config is the minimum to boot before the database exists. Everything else lives in the database, edited via the UI, scoped per tenant where applicable.