Operating Janus
Configuration, retention, health checks, and observability.
Configuration
Administration → Settings owns gateway configuration: General, Sign-in & provisioning, License & updates, Troubleshooting, and System status. Personal settings remain under the account menu, with Account, Security, Appearance, and Regional sections. Each card keeps its own save controls; switching workspace sections retains unsaved drafts in memory, not across reloads.
Configure and test OIDC or direct LDAP sign-in under Settings → Sign-in & provisioning; SCIM 2.0 provisioning is separate. Administration → People has Users, Groups, and Teams. People → Teams owns organization-wide creation, CSV import, membership roles, join-request decisions, group mappings, quota delegation, and deletion. Deleting a team switches API keys, Chat conversations and “Working under” selections that use it to personal context; their owners get a notification. Usage history keeps the team name. The top-level Team workspace is informational: it shows only your actual memberships, members, usage, and quotas, even for administrators. Administrator permissions alone do not make a team one of your memberships. Model access still requires Grants. CSV imports validate the complete upload and apply all rows or none.
Deleting a person disables the account and revokes their API tokens at once; their share links stop working. Their chats, files, memories, saved prompts, chat settings, share links and personal subscriptions are kept for 30 days — the list shows “Deleted — held until <date>”. Choose Restore to bring the person and everything back, or open the person and choose Delete content now (POST /api/v1/admin/users/{id}/purge) to delete it straight away. After 30 days it is deleted automatically. Usage history and the audit log are kept as before. If your directory deletes someone through SCIM and provisions them again within 30 days (same user name or email, or the same external ID), they get their account and content back.
Deployment settings come from environment variables; nothing is hardcoded. Gateway behavior that administrators change at runtime — file storage, additional sign-in providers and the directory, feature flags, Chat, troubleshooting — is set in the app and stored in the database. JANUS_ENCRYPTION_KEY is always required. An identity provider is optional: without one, people sign in with Janus accounts. If you set JANUS_OIDC_PROVIDER_URL you must also set JANUS_OIDC_CLIENT_ID and JANUS_OIDC_CLIENT_SECRET.
JANUS_DATABASE_URL selects the database: a postgres:// URL for production, or a sqlite:// URL. When it is unset the gateway defaults to embedded SQLite at /data/janus.db — single-node evaluation mode — and logs a prominent startup warning; Settings → System status shows which backend is in force.
Startup validates every required variable and dependency, and exits with a message naming what is missing rather than failing later under load.
Feature flags are the exception to environment-variable configuration: they are runtime database settings toggled on Admin → Settings → General, audit-logged, and applied without a restart. The spend_emphasis flag chooses whether dashboard graphs open on the Spend metric (on) or the Tokens metric (off, the shipped usage-emphasis default); viewers can still switch metrics per graph, and links with an explicit metric keep it.
JANUS_DATABASE_URL=postgres://janus:…@postgres:5432/janus
JANUS_ENCRYPTION_KEY=$(openssl rand -hex 32)
JANUS_OIDC_PROVIDER_URL=https://id.example.com/application/o/janus
JANUS_OIDC_CLIENT_ID=janus
JANUS_OIDC_CLIENT_SECRET=…
JANUS_BOOTSTRAP_ADMIN_EMAILS=alice@example.com,bob@example.com
JANUS_PUBLIC_URL=https://janus.example.com
JANUS_TRUSTED_PROXIES=10.0.0.0/8Linux server install
On a Linux server with systemd, the release installer sets Janus up as a service: sudo python3 install.py --admin-email you@example.com --hostname ai.example.com. It runs as the unprivileged janus user with HTTPS on port 443, redirects port 80 to HTTPS, creates a self-signed certificate on first install, and creates the first administrator before the gateway listens beyond loopback.
Configuration, including the encryption key, lives in /etc/janus/janus.env; back it up with the database in /var/lib/janus. Apply edits with sudo systemctl restart janus. SQLite is the default and suits a single server for a small team; pass --database-url postgres://… on first install for PostgreSQL.
Replace the certificate with sudo janus-ctl cert install --cert fullchain.pem --key privkey.pem, which checks that the key matches and that the certificate has not expired, or with sudo janus-ctl cert acme --domain ai.example.com for a Let’s Encrypt certificate that renews automatically (it needs a public DNS name and port 80 reachable from the internet). sudo janus-ctl status shows the service, readiness and certificate expiry; sudo janus-ctl upgrade --version V installs another release after backing up a SQLite database.
curl -fLO https://github.com/Torvanis/janus/releases/latest/download/install.py
sudo python3 install.py --admin-email you@example.com --hostname ai.example.com
sudo janus-ctl cert install --cert fullchain.pem --key privkey.pem
sudo janus-ctl statusHealth and readiness
GET /healthz reports liveness with no dependency checks. GET /readyz returns 503 unless the database and the environment-configured identity provider (if any) are reachable; admin-added providers and the directory are not checked — wire it to your readiness probe so a replica never receives traffic it cannot serve.
The identity-provider check tolerates transient faults: a single failed discovery probe (for example a connection refused while the provider restarts) is retried immediately and does not make the replica unready. Two consecutive failed probe rounds mark the provider unreachable, so a real outage flips /readyz to 503 within about 40 seconds, and a recovered provider clears it within about 5 seconds. Each failed probe is logged at WARN level with the discovery URL and the underlying error.
Retention
Usage events are kept for JANUS_USAGE_RETENTION_DAYS (365 by default) and audit entries for JANUS_AUDIT_RETENTION_DAYS (730). A purge job runs daily at JANUS_PURGE_JOB_TIME_UTC and reconciles quota ledgers afterwards. The same job deletes in-app notifications after 90 days, chats a person deleted 30 days after the delete, and the content of deleted people once their 30-day hold ends. Violations and Usage insights results are never kept longer than the request log (JANUS_USAGE_RETENTION_DAYS); within that, violations keep their own 30-day (90-day for prompt injection and content safety) limit and Usage insights its own setting.
The gateway never persists prompt or response bodies of API calls, including in debug logs — with one deliberate exception: troubleshooting mode, described below, which an administrator must switch on. Janus Chat is different by design: it keeps each person’s conversations and attachments, encrypted with the gateway key, until they are deleted. When someone deletes a chat it disappears at once and is removed for good, with its files, 30 days later.
Troubleshooting mode
When a client keeps failing and the request log’s metadata is not enough, Administration → Settings → Troubleshooting starts a capture session (24 hours by default, up to one week; No expiry is also offered, with a warning, and runs until you disable it). Starting a session needs a Business key. While it is active, requests matching its filter have their headers — Authorization, Cookie and API-key headers always redacted — and, optionally, their request and response bodies captured. Captured bodies are exactly what the client sent and received: they are taken before any Security Gateway redaction and regardless of a policy’s Keep prompt-injection request bodies setting, and they include messages from people covered by Child Safety. Leave Request bodies and Response bodies off under What to capture when you only need headers and metadata. The filter combines models, users or service tokens, groups, upstreams, error codes, HTTP statuses, success/failure and token-size thresholds on each direction with AND or OR; optional Require (every rule must match) and Exclude (any match keeps a request out) gates narrow it further. An empty filter captures everything and is warned about.
Every session declares a retention policy — maximum age, maximum number of captures, maximum total size, at least one required — enforced hourly and on demand with Run cleanup now. Bodies are stored in the database by default or, when JANUS_TROUBLESHOOT_DIR is set on the gateway, as files under that directory; either way they can be encrypted at rest with the gateway key, and the panel warns prominently when they are not. When File storage (below) is set to S3-compatible, a third choice, Object storage, writes the bodies to that bucket under troubleshoot/<date>/<capture>/, always encrypted by Janus before upload. Purge all captured data removes everything at once.
Captured rows in Admin → Requests gain a Download control (also in the detail drawer) serving a tar.gz with manifest.json, request/headers.json, request/body.*, response/headers.json and response/body.*. Export all streams every capture in one archive with a captures.jsonl index — one JSON document per request — and an Athena table definition in README.txt for bulk analysis or replay validation. Enabling, updating, disabling, purging and exporting are all audit-logged.
File storage
Files Janus keeps — chat attachments and, optionally, troubleshooting capture bodies — live in one admin-chosen place: Admin → Settings → General → File storage. Database (the default) keeps them in the Janus database and needs nothing else. S3-compatible sends them to a bucket on AWS S3, SeaweedFS, Ceph or any other S3-compatible service: enter the endpoint, bucket, region, path-style addressing (needed by most self-hosted servers), an optional key prefix, the access key ID and secret, and an optional CA bundle for a private CA. Test connection writes, reads back and deletes a probe object with the values on screen before you save; it reports the provider’s own error, such as SignatureDoesNotMatch for a wrong secret.
Every file is encrypted by Janus with the gateway key (AES-256-GCM) before it is stored, so neither the database nor the storage provider ever sees its contents. The secret access key is stored encrypted and never shown again; leave the field blank to keep it. Janus creates the bucket on first use when it does not exist. Single files are limited to JANUS_STORAGE_MAX_OBJECT_BYTES (25 MiB by default).
Switching backends does not move existing files: they stay where they are and remain readable, because every file records which backend holds it. Only new files go to the new backend.
A deployment can fix the choice instead with JANUS_STORAGE_MODE (database or s3) and JANUS_S3_ENDPOINT, JANUS_S3_BUCKET, JANUS_S3_REGION (default us-east-1), JANUS_S3_PATH_STYLE, JANUS_S3_PREFIX, JANUS_S3_ACCESS_KEY_ID, JANUS_S3_SECRET_ACCESS_KEY and JANUS_S3_CA_FILE. The card then shows those values as Set by the deployment and only offers Test connection. With the Helm chart, pass them under extraEnv, with the secret access key from a Secret.
Observability
GET /metrics exposes Prometheus metrics. Deliberately, no metric carries a user or token label: per-person analytics come from the usage-event table instead, which keeps active series bounded.
Logs are structured JSON on stdout. The Authorization header, API keys, and request bodies are excluded by construction.
Alert delivery is split in two on purpose. Alerts the gateway can observe itself (quota thresholds, upstream down) are sent by the Janus process using JANUS_SMTP_* and the alert rules configured in Admin → Alerts. Infrastructure alerts — gateway down, database replication lag over ten seconds — are evaluated by Prometheus (deploy/compose/prometheus/alert-rules.yaml) and must not depend on the gateway being healthy, so they are delivered by Alertmanager: enable the compose monitoring profile and set the ALERTMANAGER_* variables (SMTP for email to the admin, ALERTMANAGER_WEBHOOK_URL for a webhook). Running Prometheus without Alertmanager leaves those alerts visible in the Prometheus UI but delivered nowhere. Outside the reference compose stack, attach your own Alertmanager and point it at the same rules.
Alert rules (Administration → Safety & Security → Alerts) pick a trigger. Besides quota warnings and breaches, Upstream unreachable, System events and Child Safety, there are: Pool member taken out of rotation (a load-balanced alias stopped sending requests to one member after repeated failures; sent once per ejection); Personal subscription needs reconnecting (sent to the person whose subscription it is, when Janus can no longer renew their sign-in); License expiring or expired (weekly in the last 30 days, then daily in the grace period and after expiry); and Chat replies failing (at most once per model every 15 minutes).
Severity is the lowest severity this rule sends by email or webhook. Critical sends only critical alerts and Info sends everything. Recovered notices are always sent. The in-app inbox receives every alert regardless.
With several replicas, Upstream unreachable, Security classifier unavailable, pool ejection, license, Chat failure and Child Safety blocked notices are deduplicated in the database, so the cluster sends each once.