Server Configuration Reference - The gingee.json File

The gingee.json file is the master configuration file for the entire Gingee server instance. It resides in the root of your project and controls server behavior, caching policies, logging, and security settings that apply to all applications running on the platform.

Here is a comprehensive breakdown of all available properties.

{
  "server": {
    "http": { "enabled": true, "port": 7070 },
    "https": { 
      "enabled": false, 
      "port": 7443,
      "key_file": "./settings/ssl/key.pem",
      "cert_file": "./settings/ssl/cert.pem"
    }
  },
  "web_root": "./web",
  "default_app": "glade",
  "cache": {
    "provider": "memory",
    "prefix": "gingee:",
    "redis": {
      "host": "127.0.0.1",
      "port": 6379,
      "password": null,
      "db": 0
    }
  },
  "email": {
    "type": "console"
  },
  "ai": {
    "type": "mock"
  },
  "scheduler": {
    "enabled": false,
    "timezone": "UTC",
    "coordination": {
      "driver": "none",
      "strategy": "tick"
    },
    "redis": {
      "host": "127.0.0.1",
      "port": 6379,
      "password": null,
      "key_prefix": "gingee:scheduler:"
    }
  },
  "limits": {
    "request_timeout_ms": 30000,
    "request_timeout_stream_ms": 300000,
    "stream_idle_timeout_ms": 60000,
    "outbound_timeout_ms": 15000,
    "max_concurrent_requests": 100,
    "max_concurrent_requests_per_app": 25,
    "max_concurrent_outbound": 50
  },
  "egress": {
    "mode": "protected",
    "https_only": false,
    "dns_check": true,
    "max_redirects": 3,
    "allow_hosts": [],
    "allow_cidrs": []
  },
  "secrets": {
    "load_dotenv": false,
    "required": true,
    "file_roots": ["./settings/secrets", "/run/secrets"]
  },
  "metrics": {
    "enabled": true,
    "path": "/metrics",
    "allow_from": ["127.0.0.1", "::1", "::ffff:127.0.0.1"],
    "bearer_token": null
  },
  "audit": {
    "enabled": true,
    "path": "./logs/audit.jsonl"
  },
  "isolation": {
    "mode": "off",
    "default": "inprocess",
    "apps": [],
    "groups": {},
    "auto_restart": true,
    "restart_max": 10
  },
  "websockets": {
    "enabled": true,
    "max_connections": 10000,
    "max_connections_per_app": 2000,
    "max_message_bytes": 65536,
    "idle_timeout_ms": 300000,
    "heartbeat_ms": 30000,
    "default_path": "/ws"
  },
  "queue": {
    "enabled": true,
    "driver": "memory",
    "concurrency": 5,
    "default_attempts": 3,
    "default_backoff_ms": 1000,
    "jobs_dir": "jobs",
    "redis": {
      "url": null,
      "host": "127.0.0.1",
      "port": 6379,
      "key_prefix": "gingee:queue:"
    }
  },
  "max_body_size": "10mb",
  "content_encoding": { "enabled": true },
  "logging": {
    "level": "info",
    "rotation": {
      "period_days": 7,
      "max_size_mb": 50
    }
  },
  "box": {
    "allowed_modules": []
  },
  "privileged_apps": []
}

server

An object that configures the HTTP and HTTPS servers.

web_root

cache

"cache": {
  "provider": "redis",
  "prefix": "gingee:",
  "redis": {
    "url": "env:REDIS_URL"
  }
}

Or without a URL:

"cache": {
  "provider": "redis",
  "prefix": "gingee:",
  "redis": {
    "host": "127.0.0.1",
    "port": 6379,
    "password": null,
    "db": 0
  }
}

email

ai

scheduler

Key Default Meaning
driver "none" "none" (ops designates one scheduler node) or "redis" (distributed coordination). Same idea as queue.driver / cache.provider.
strategy "tick" tick: per app+job+fire-slot lock (recommended HA). leader: one global leader lease; only the leader runs any schedule.
lock_ttl_ms 300000 Lock / leader lease TTL (ms). Leader renews at ~ttl/3.
slot_granularity_ms 10000 Tick slot bucket when planned fire time is unavailable (absorbs small clock skew).
node_id hostname:pid Identity stored in Redis lock values.
Key Default Meaning
url null Redis URL (e.g. env:REDIS_URL). When set, used instead of host/port.
host / port / password / db 127.0.0.1 / 6379 / null / 0 Classic connection fields.
key_prefix "gingee:scheduler:" Namespace for lock keys (use a dedicated prefix; do not share gingee:queue:).
"scheduler": {
  "enabled": true,
  "timezone": "UTC",
  "coordination": {
    "driver": "redis",
    "strategy": "tick",
    "lock_ttl_ms": 300000
  },
  "redis": {
    "url": "env:REDIS_URL",
    "key_prefix": "gingee:scheduler:"
  }
}

Behavior notes:

limits

Key Default Meaning
request_timeout_ms 30000 Wall-clock budget for a non-streaming server script (starts when the script runs). On expiry: 504 JSON and request abort signal.
request_timeout_stream_ms 300000 Hard cap after $g.response.startStream() (e.g. AI SSE).
stream_idle_timeout_ms 60000 If no write / writeSSE for this long while streaming, the stream is ended (504 / error SSE).
outbound_timeout_ms 15000 Default httpclient axios timeout when the app omits options.timeout (also a ceiling for explicit timeouts). Clamped to remaining request budget when not streaming.
max_concurrent_requests 100 Max in-flight server scripts process-wide (static files are not counted). Over limit → 503 TOO_MANY_REQUESTS.
max_concurrent_requests_per_app 25 Max in-flight scripts per app. Over limit → 503.
max_concurrent_outbound 50 Max concurrent httpclient calls process-wide. Over limit → status 503 from httpclient.
headers_timeout_ms 60000 Node HTTP server.headersTimeout.
request_timeout_server_ms 120000 Node HTTP server.requestTimeout (whole connection).
keep_alive_timeout_ms 5000 Node HTTP keep-alive.

Notes:

egress

Key Default Meaning
mode "protected" "protected" — block private/loopback/link-local/metadata, allow public internet. "allowlist" — only allow_hosts / allow_cidrs. "off" — no checks (local dev only).
https_only false When true, reject http: URLs.
dns_check true In protected mode, resolve hostnames and deny if any address is blocked.
max_redirects 3 Max HTTP redirects; each hop is re-validated.
block_private / block_loopback / block_link_local / block_metadata true Class blocks used in protected mode. Metadata hostnames/IPs are force-blocked in protected and allowlist.
allow_hosts [] Exact host or *.example.com patterns (exceptions / allowlist entries).
allow_cidrs [] CIDR exceptions (e.g. "10.0.0.0/8") for intentional private access.
deny_hosts / deny_cidrs [] Extra denials.

Examples:

"egress": { "mode": "protected", "allow_cidrs": ["10.0.1.0/24"] }
"egress": { "mode": "off" }

Denied httpclient calls return 403 with code: "EGRESS_DENIED". Scheduler URL jobs fail registration/run with a clear log line.

secrets

Key Default Meaning
load_dotenv false When true, load project-root .env into process.env for keys not already set (local Joy).
required true Missing env: / file: targets throw at load time (fail closed).
file_roots ["./settings/secrets", "/run/secrets"] Absolute or project-relative directories allowed for file: secrets. Paths outside these roots are rejected.

Reference syntax (any string config value, including nested fields):

Form Example
Env "jwt_secret": "env:GINGEE_MYAPP_JWT_SECRET"
File "password": "file:./settings/secrets/myapp_db_password"
Object "api_key": { "$secret": "env:SENDGRID_KEY", "required": true }

Literal values still work (dev): "jwt_secret": "dev-only-secret".

Examples of fields that commonly use refs: jwt_secret, db[].password, email.api_key, ai.api_key, cache.redis.password.

metrics

Key Default Meaning
enabled true When false, the metrics path is not served.
path "/metrics" HTTP path for scrapes (must start with /).
allow_from ["127.0.0.1", "::1", "::ffff:127.0.0.1"] Socket remote addresses allowed to scrape. Empty array = allow all (not recommended). Uses the TCP peer address only—X-Forwarded-For is not trusted.
bearer_token null If set (literal or env: / file: secret ref), require Authorization: Bearer <token>.

Series (high level): HTTP request counts/durations (by app, kind, status class), concurrency reject counters, egress deny reasons, scheduler job run outcomes, WebSocket upgrade results / open connection gauges, in-flight gauges, process memory, app/job counts.

Scrape example (local):

curl -s http://127.0.0.1:7070/metrics

audit

Key Default Meaning
enabled true When false, no audit file is written.
path "./logs/audit.jsonl" Absolute or project-relative path to the audit log file. Parent directories are created if needed.

Each line is one JSON object, for example:

{"ts":"2026-07-18T12:00:00.000Z","event":"permission.set","actor":"glade","app":"myapp","details":{"previous":["fs"],"granted":["fs","db"]}}
Field Meaning
event Stable name: permission.set, app.install, app.upgrade, app.reload, app.delete, app.rollback, app.register
actor Privileged app that performed the action when available; otherwise system
app Target application name
details Event-specific payload (previous/granted permissions, versions, etc.)

isolation

Key Default Meaning
mode "off" "off" = never use workers. "process" = allow workers per policy below.
default "inprocess" When mode is "process", apps without an explicit flag use "inprocess" or "process".
apps [] App folder names that each get a solo worker (app:<name>) when mode is "process".
groups {} Map of group id → app name list; members share one worker (group:<id>). Membership alone isolates them—no need to also list them in apps.
worker_ready_timeout_ms 15000 Max wait for a worker to become ready after fork.
request_timeout_ms 120000 Max wait for a worker script (buffered or stream) to finish.
auto_restart true Restart workers after unexpected exit (not after intentional stop/reload).
restart_max 10 Max automatic restarts before staying down until next request/reload.
restart_delay_ms 500 Base backoff delay (doubles each attempt).
restart_backoff_max_ms 30000 Cap on backoff delay.
restart_stable_ms 60000 After this long ready without crash, restart counter resets.
worker_limits see below V8 / OS resource caps applied to each isolation worker process.

isolation.worker_limits

Applied when a worker is forked (isolation.mode: "process"). All fields default to null (no forced cap).

Key Default Platform Meaning
max_old_space_mb null All V8 old-space heap cap (--max-old-space-size). When the heap hits the limit the worker dies and auto-restart may bring it back.
max_semi_space_mb null All V8 young-generation size (--max-semi-space-size).
uv_threadpool_size null All Sets UV_THREADPOOL_SIZE in the worker env.
priority null All "low" | "normal" | "high" — os.setPriority after spawn (may require privileges for "high" on Unix).
max_rss_mb null Linux Best-effort address-space ceiling via prlimit --as if installed. Ignored on Windows (log warning); use Docker/Job Objects at the orchestrator for hard RSS caps.
"isolation": {
  "mode": "process",
  "apps": ["untrusted-app"],
  "worker_limits": {
    "max_old_space_mb": 512,
    "priority": "low",
    "max_rss_mb": 768
  }
}

Honesty: These are worker process limits, not hostile multi-tenant hard isolation. Full cgroups v2 / Windows Job Objects remain the orchestrator’s job for production multi-tenant. max_old_space_mb is the portable, always-on V8 cap.

Per-app (app.json): "isolation": "process" or "isolation": "inprocess".

How apps are selected (when mode is "process"):

Source Effect
app.json "isolation": "process" Solo worker unless the app is also in a group
isolation.apps Same as solo opt-in by name
isolation.groups Shared worker for all listed members that are installed
default: "process" Every non-privileged app isolated (use carefully)
privileged_apps (e.g. Glade) Always stay in-process

If an app appears in both apps and a group, the group wins (one shared worker).

Runtime rules:

"isolation": {
  "mode": "process",
  "default": "inprocess",
  "apps": ["untrusted-app"],
  "groups": {
    "tenant-a": ["app-one", "app-two"]
  },
  "auto_restart": true,
  "restart_max": 10,
  "restart_delay_ms": 500,
  "restart_backoff_max_ms": 30000,
  "restart_stable_ms": 60000
}

In this example: untrusted-app → worker app:untrusted-app; app-one and app-two (if installed) → shared worker group:tenant-a; all other apps stay on the master.

websockets

Key Default Meaning
enabled true Global kill switch. When false, no upgrades are accepted.
max_connections 10000 Max open sockets server-wide.
max_connections_per_app 2000 Max open sockets per app.
max_message_bytes 65536 Max inbound message size (also ws maxPayload).
idle_timeout_ms 300000 Close sockets idle longer than this (activity = message or pong).
heartbeat_ms 30000 Server ping interval; also drives idle checks.
default_path "/ws" Used when an app omits websockets.path. Full URL is /{appName}{path}.
fanout see below Multi-node room/app broadcast (optional).
redis see below Connection for fan-out when fanout.driver is "redis" (same fields as queue.redis).

websockets.fanout (multi-node)

Without fan-out, require('websockets').toRoom / toApp only reach sockets on this process. With Redis pub/sub, every Gingee master delivers to its local members of the room.

Key Default Meaning
driver "none" "none" (single-node) or "redis" (pub/sub fan-out).
node_id hostname:pid Origin id so a node ignores its own publishes.

websockets.redis

Same connection shape as queue.redis / scheduler.redis / cache.redis: url or host/port/password/db, plus key_prefix (default "gingee:ws:"). Channel: {key_prefix}broadcast.

"websockets": {
  "enabled": true,
  "fanout": {
    "driver": "redis"
  },
  "redis": {
    "url": "env:REDIS_URL",
    "key_prefix": "gingee:ws:"
  }
}

Behavior: local delivery always runs first; Redis publish is best-effort (if Redis is down, other nodes miss the message — this node still serves its sockets). Apps need no API changes.

Per-app (app.json):

"websockets": {
  "enabled": true,
  "path": "/ws",
  "handler": "realtime/handler.js",
  "auth": "realtime/auth.js",
  "allowed_origins": ["https://app.example.com"]
}
Field Required Meaning
enabled no Set false to disable; presence of handler is enough to enable when permission is granted
path no Path under the app (default server default_path). Client connects to ws(s)://host/{appName}{path}
handler yes Box-relative script exporting async function (socket, ctx)
auth no Box-relative script run on upgrade; return false / { ok: false } to reject
allowed_origins no If set, Origin must match exactly

Multi-tenant apps: rooms are app-global. Prefix with require('websockets').tenantRoom(tenantId, name) → t:{tenantId}:{name}.

Reload / delete: app reload re-binds the handler and closes that app’s sockets.

Sample app: web/ginchat/ — multi-tenant room chat + HTTP announce (POST /ginchat/api/announce). Open /ginchat/ after granting the websockets permission and restarting/reloading.

Metrics: gingee_websocket_upgrades_total, gingee_websocket_connections_opened_total / _closed_total, gauges gingee_websocket_connections and gingee_websocket_connections_per_app, fan-out gingee_websocket_fanout_publish_total / _receive_total.

queue

Key Default Meaning
enabled true When false, enqueue and processing are off.
driver "memory" "memory" or "redis".
concurrency 5 Max jobs running at once on this node.
default_attempts 3 Retries after handler failure (exponential backoff).
default_backoff_ms 1000 Base delay between retries.
jobs_dir "jobs" Default folder under box/ for job scripts.
redis see defaults url or host/port/password/db/key_prefix when driver is redis.
"queue": {
  "enabled": true,
  "driver": "redis",
  "concurrency": 10,
  "redis": { "url": "env:REDIS_URL", "key_prefix": "gingee:queue:" }
}

App (app.json optional):

"queue": {
  "jobs": {
    "send-welcome": { "script": "jobs/send_welcome.js" }
  }
}

Handler example (box/jobs/echo.js):

module.exports = async function () {
  await gingee(async ($g) => {
    const { payload, attempt, id } = $g.queue;
    // do work…
  });
};

From a server script:

const queue = require('queue');
await queue.add('echo', { hello: true }, { delayMs: 0, attempts: 3 });

CRON → queue (multi-node friendly): schedule target "type": "queue", "job": "nightly" enqueues instead of running the heavy work inline. App needs both scheduler and queue permissions; server needs scheduler.enabled and queue.enabled.

Metrics: gingee_queue_jobs_enqueued_total, _completed_total, _failed_total, _retried_total, gingee_queue_dlq_total / _retry_total / _discard_total, histogram gingee_queue_job_duration_seconds.

Admin (Glade): top menu Queue / DLQ — Live jobs (running/waiting on this node + pending/delayed in the driver; optional auto-refresh) and DLQ (retry/discard). App filter (3+ letters). APIs: getQueueStats / listQueueLiveJobs / listQueueDlq / retryQueueDlqJob / discardQueueDlqJob (/glade/api/queue-*). Memory live+DLQ is process-local; Redis pending/delayed/DLQ are shared.

Optional npm feature packages

Gingee keeps a core set of required dependencies (engine, SQLite, zip, auth crypto, etc.) and marks specialized packages as optionalDependencies in package.json:

Feature Packages
Image processing (require('image')) sharp
PostgreSQL / MySQL / MSSQL / Oracle pg, mysql2, mssql, oracledb
Charts / canvas barcodes / dashboard chartjs-node-canvas, canvas
PDF pdfmake
SendGrid email @sendgrid/mail
Gemini AI @google/generative-ai

Install behavior (npm):

Using a feature without its package throws FEATURE_NOT_INSTALLED with the package name. SQLite (better-sqlite3), email type: "console", and AI type: "mock" do not require optionals. Image ops need sharp installed (or a full/default install that includes optionals).

max_body_size

content_encoding

logging

An object that configures the server's logger.

On disk:

Stream Path Notes
Server {project}/logs/gingee-YYYY-MM-DD.log JSON lines; includes engine events and app logs forwarded from each app logger
App {web_root}/{app}/box/logs/app-YYYY-MM-DD.log JSON lines with "app"; app-only

Glade: top menu Logs — tail/view server or app files (default last 100 lines; path-jailed). See Glade Admin.

box (Sandbox Configuration)

"box": {
  "allowed_modules": [],
  "allow_code_generation": false
}

default_app

privileged_apps


Enabling HTTPS for Local Development

To run and test your Gingee server with a valid SSL certificate on localhost (i.e., get the green padlock in your browser), you cannot use a simple self-signed certificate, as browsers do not trust them. The correct method is to create your own local Certificate Authority (CA) and use it to sign a certificate for localhost.

Prerequisites: You must have the openssl command-line tool installed. It is available by default on Linux and macOS. For Windows, it is included with Git Bash.

Step 1: Create Your Local Certificate Authority

First, we create a private key and a root certificate for our new local CA. Run these commands from your project root.

  1. Generate the CA's private key:
    openssl genrsa -out ./settings/ssl/localCA.key 2048
    
  2. Generate the CA's root certificate. You will be prompted for details like country and organization; you can enter any information you like.
    openssl req -x509 -new -nodes -key ./settings/ssl/localCA.key -sha256 -days 1024 -out ./settings/ssl/localCA.pem
    

Step 2: Add the CA to Your System's Trust Store

This is the critical step where you tell your operating system to trust your new local CA.

Step 3: Create and Sign the Server Certificate

Now, create the key.pem and cert.pem files that Gingee will use, and sign them with your trusted local CA.

  1. Generate the server's private key:
    openssl genrsa -out ./settings/ssl/key.pem 2048
    
  2. Create a Certificate Signing Request (CSR). Important: When prompted for the "Common Name (CN)," you must enter localhost.
    openssl req -new -key ./settings/ssl/key.pem -out ./settings/ssl/server.csr
    
  3. Sign the server certificate with your local CA:
    openssl x509 -req -in ./settings/ssl/server.csr -CA ./settings/ssl/localCA.pem -CAkey ./settings/ssl/localCA.key -CAcreateserial -out ./settings/ssl/cert.pem -days 500 -sha256
    

Step 4: Update gingee.json and Run

Enable the HTTPS server in your configuration. Since we used the default file paths, you don't need to add the key_file or cert_file properties.

{
  "server": {
    "http": { "enabled": false },
    "https": { "enabled": true, "port": 7443 }
  }
}

Now, start your server (npm start). You can navigate to https://localhost:7443 and your browser will show a secure connection with no warnings.