Skip to content

Multi-Tenancy Configuration

Apache Fineract is multi-tenant by design. Each tenant is an isolated environment with its own database, its own users, its own configuration, and its own data. A single Fineract instance serves all tenants simultaneously, routing each request to the correct database based on a header in the HTTP request.

Architecture

Two separate databases are involved.

Tenant store (fineract_tenants) - a small registry database that Fineract reads on startup and on every request. It holds one row per tenant describing where that tenant's database is located, what credentials to use, and what connection pool settings to apply. This is sometimes called the "hub" database.

Tenant databases - one database per tenant (e.g. fineract_default, fineract_acme, fineract_beta). Each contains the full Fineract schema: loans, clients, accounting, events, jobs, and all other domain tables. These databases are completely isolated - querying one tenant's database has no effect on any other.

                HTTP request

                    │  Fineract-Platform-TenantId: acme

         ┌─────────────────────┐
         │   fineract_tenants  │  ← tenant store (always one)
         │   tenants table     │
         │   row: identifier=acme
         │        schema=fineract_acme
         │        host=db.example.com
         └──────────┬──────────┘


         ┌─────────────────────┐
         │   fineract_acme     │  ← tenant database
         │   (full schema)     │
         └─────────────────────┘

Tenant databases do not have to be on the same server as the tenant store. Each tenant row in fineract_tenants can point to a different host, port, and set of credentials - making it possible to spread tenants across multiple database servers.

Tenant Resolution

Every API request must include the Fineract-Platform-TenantId header. Fineract reads this header, looks up the matching row in fineract_tenants.tenants, and opens a connection to that tenant's database for the duration of the request.

bash
curl -H "Fineract-Platform-TenantId: acme" \
     -H "Authorization: Basic $(echo -n 'mifos:password' | base64)" \
     https://localhost:8443/fineract-provider/api/v1/clients

If the header is missing or the identifier does not match any row in the tenant store, the request is rejected.

Environment Variables

Tenant Store Connection (HikariCP)

These variables configure the connection from Fineract to the fineract_tenants registry database.

VariableDefaultDescription
FINERACT_HIKARI_JDBC_URLjdbc:mariadb://localhost:3306/fineract_tenantsJDBC URL for the tenant store
FINERACT_HIKARI_USERNAMErootUsername for the tenant store
FINERACT_HIKARI_PASSWORDmysqlPassword for the tenant store
FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAMEorg.mariadb.jdbc.DriverJDBC driver class
FINERACT_HIKARI_MINIMUM_IDLE3Minimum idle connections in the pool
FINERACT_HIKARI_MAXIMUM_POOL_SIZE10Maximum connections in the pool
FINERACT_HIKARI_IDLE_TIMEOUT60000Milliseconds before an idle connection is closed
FINERACT_HIKARI_CONNECTION_TIMEOUT20000Milliseconds to wait for a connection
FINERACT_HIKARI_AUTO_COMMITtrueAuto-commit on the tenant store connection
FINERACT_HIKARI_TRANSACTION_ISOLATIONTRANSACTION_REPEATABLE_READTransaction isolation level

Default Tenant Provisioning

At startup, Liquibase runs against the tenant store and seeds a default tenant row if one does not already exist. These variables control what that default tenant looks like.

VariableDefaultDescription
FINERACT_DEFAULT_TENANTDB_IDENTIFIERdefaultThe tenant identifier used in Fineract-Platform-TenantId header
FINERACT_DEFAULT_TENANTDB_NAMEfineract_defaultThe database/schema name for the default tenant
FINERACT_DEFAULT_TENANTDB_DESCRIPTIONDefault Demo TenantHuman-readable label stored in the tenant row
FINERACT_DEFAULT_TENANTDB_HOSTNAMElocalhostHost of the default tenant's database
FINERACT_DEFAULT_TENANTDB_PORT3306Port of the default tenant's database
FINERACT_DEFAULT_TENANTDB_UIDrootUsername for the default tenant database
FINERACT_DEFAULT_TENANTDB_PWDmysqlPassword for the default tenant database
FINERACT_DEFAULT_TENANTDB_TIMEZONEAsia/KolkataDefault timezone for the tenant (IANA timezone name)
FINERACT_DEFAULT_TENANTDB_CONN_PARAMS(empty)Optional JDBC connection parameters appended to the tenant's connection URL
FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORDfineractMaster password used to encrypt the tenant's stored database password

Read-Only Replica (Optional)

Each tenant can optionally point to a read-only replica for read queries. These variables configure the replica for the default tenant.

VariableDefaultDescription
FINERACT_DEFAULT_TENANTDB_RO_HOSTNAME(empty)Read-only replica hostname
FINERACT_DEFAULT_TENANTDB_RO_PORT(empty)Read-only replica port
FINERACT_DEFAULT_TENANTDB_RO_UID(empty)Read-only replica username
FINERACT_DEFAULT_TENANTDB_RO_PWD(empty)Read-only replica password
FINERACT_DEFAULT_TENANTDB_RO_NAME(empty)Read-only replica database name
FINERACT_DEFAULT_TENANTDB_RO_CONN_PARAMS(empty)Additional JDBC connection parameters for the replica

When no read-only replica is configured, all queries go to the primary.

Per-Tenant Connection Pool

These variables set the HikariCP pool bounds for each tenant's database connection pool. They apply to all tenants unless overridden in the fineract_tenants.tenants row directly.

VariableDefaultDescription
FINERACT_CONFIG_MIN_POOL_SIZE-1Minimum pool size per tenant (-1 uses the HikariCP default)
FINERACT_CONFIG_MAX_POOL_SIZE-1Maximum pool size per tenant (-1 uses the HikariCP default)

Database Encryption

Fineract stores each tenant's database password encrypted in the tenant store. The encryption uses AES/CBC/PKCS5Padding by default. The master password used as the encryption key is set via FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD (for the default tenant) and FINERACT_DEFAULT_MASTER_PASSWORD globally.

When you insert a new tenant row into fineract_tenants.tenants directly, the password field should contain the plaintext password. Fineract will encrypt it on first use via TenantPasswordEncryptionTask. Set the master password consistently across all Fineract instances in a cluster.

Tenant Provisioning

How the Default Tenant Is Created

On first startup, Liquibase runs a migration against fineract_tenants that inserts the default tenant row using the FINERACT_DEFAULT_TENANTDB_* values above. It then runs all Fineract schema migrations against the default tenant's database, creating the full schema.

On every subsequent startup, TenantDatabaseUpgradeService checks each registered tenant and applies any pending Liquibase migrations to bring them up to the current schema version. This happens for all tenants in the registry, not just the default one - so adding a tenant and restarting is enough to provision its schema.

Adding a Tenant Manually (SQL)

Fineract has no built-in REST API for creating tenants. To add a new tenant:

Step 1 - Create the tenant database:

sql
CREATE DATABASE fineract_acme CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON fineract_acme.* TO 'fineract_user'@'%';

Step 2 - Insert a row into the tenant store:

sql
INSERT INTO fineract_tenants.tenants (
  identifier,
  name,
  schema_name,
  timezone_id,
  schema_server,
  schema_server_port,
  schema_username,
  schema_password,
  auto_update,
  pool_initial_size,
  pool_max_active,
  pool_min_idle,
  pool_max_idle
) VALUES (
  'acme',
  'Acme MFI',
  'fineract_acme',
  'Africa/Nairobi',
  'db.example.com',
  '3306',
  'fineract_user',
  'plaintext_password',  -- encrypted by Fineract on first access
  1,
  5,
  40,
  10,
  20
);

Step 3 - Restart Fineract (or rely on TenantDatabaseUpgradeService if it is configured to run automatically). Fineract detects the new row and runs Liquibase migrations against fineract_acme, provisioning the full schema.

Step 4 - Verify the new tenant:

bash
curl -H "Fineract-Platform-TenantId: acme" \
     -H "Authorization: Basic $(echo -n 'mifos:password' | base64)" \
     https://localhost:8443/fineract-provider/api/v1/offices

The tenants.auto_update = 1 flag tells Fineract to run migrations for this tenant on startup. Setting it to 0 disables automatic schema upgrades for that tenant.

Finecko Hub

Managing tenants via raw SQL and server restarts is error-prone and slow at scale. Finecko provides a Hub - a web UI and REST API layer that wraps the tenant provisioning process end to end.

With the Finecko Hub you can:

  • Create a new tenant in seconds by filling in a form or calling a single API endpoint - no direct database access required
  • View all registered tenants, their database locations, timezone settings, and schema versions from a single dashboard
  • Enable or disable a tenant without touching the database
  • Trigger schema migrations for individual tenants on demand, without restarting the full application
  • Monitor connection pool health and active session counts per tenant
  • Set tenant-level configuration such as rounding mode and connection parameters through the UI

The Hub exposes a clean REST API so tenant lifecycle management can be embedded in your own provisioning automation or customer onboarding workflows.

For details on the Finecko Hub, contact the Finecko team.

Docker Compose Example (Two Tenants)

yaml
services:
  db:
    image: mariadb:10.11
    environment:
      MARIADB_ROOT_PASSWORD: secret
      MARIADB_DATABASE: fineract_tenants
    volumes:
      - db-data:/var/lib/mysql

  fineract:
    image: apache/fineract:latest
    environment:
      # Tenant store connection
      FINERACT_HIKARI_JDBC_URL: "jdbc:mariadb://db:3306/fineract_tenants"
      FINERACT_HIKARI_USERNAME: "root"
      FINERACT_HIKARI_PASSWORD: "secret"

      # Default tenant
      FINERACT_DEFAULT_TENANTDB_HOSTNAME: "db"
      FINERACT_DEFAULT_TENANTDB_PORT: "3306"
      FINERACT_DEFAULT_TENANTDB_UID: "root"
      FINERACT_DEFAULT_TENANTDB_PWD: "secret"
      FINERACT_DEFAULT_TENANTDB_IDENTIFIER: "default"
      FINERACT_DEFAULT_TENANTDB_NAME: "fineract_default"
      FINERACT_DEFAULT_TENANTDB_TIMEZONE: "UTC"
      FINERACT_DEFAULT_TENANTDB_MASTER_PASSWORD: "changeme-masterkey"
    depends_on:
      - db

volumes:
  db-data:

After startup, insert a second tenant row into fineract_tenants.tenants pointing to a second database (which can be on the same or a different server), then restart Fineract to trigger schema provisioning.

Per-Tenant Timezone

Each tenant row in fineract_tenants.tenants has a timezone_id column. This controls how Fineract interprets dates for that tenant's business logic - repayment schedules, COB runs, report date ranges, and interest calculations all use this timezone.

Use IANA timezone names (e.g. Africa/Nairobi, Asia/Dhaka, America/New_York, UTC). The default is Asia/Kolkata from the seed row, which should be changed for deployments outside India.

Timezone is set when the tenant row is inserted. Changing it after data exists can cause date calculation inconsistencies - coordinate any timezone changes with a maintenance window.

Tenant Isolation

Each tenant's data is fully isolated at the database level. There is no shared application-level state between tenants. A bug or misconfiguration in one tenant's data cannot directly affect another tenant's data.

What is shared across all tenants in a single Fineract instance:

  • JVM process and memory
  • Thread pools
  • Kafka and JMS producer connections (events are tagged with tenantId in the MessageV1 envelope)
  • Liquibase schema version history (each tenant's schema versioning is tracked in its own database)

For complete infrastructure isolation (separate JVM per tenant), deploy separate Fineract instances pointed at separate tenant store rows, or use the Finecko Hub's multi-instance management capabilities.

Authentication Per Tenant

Fineract users are stored in each tenant's database (m_appuser table). There is no cross-tenant user - an account in tenant acme cannot authenticate requests to tenant beta.

Each tenant starts with the default admin credentials (mifos / password). These should be changed immediately after provisioning.

Liquibase Migration Threading

Fineract runs Liquibase migrations for all registered tenants at startup. This is deliberately single-threaded (tenant-upgrade-task-executor-core-pool-size=1) to avoid Liquibase locking issues. If you have a large number of tenants, startup time will be proportional to the number of tenants with pending migrations.

The queue capacity for pending migration tasks is controlled by:

bash
FINERACT_TENANT_UPGRADE_TASK_EXECUTOR_QUEUE_CAPACITY=100

Increase this if you have more than 100 tenants to migrate at startup.