Configuration
The Proxima Console backend is configured entirely through environment variables, following the Twelve-Factor App methodology. No configuration is stored in code.
Environment Variables
| Variable | Default | Required | Description |
|---|---|---|---|
PROXIMA_DB_URL | — | Yes | PostgreSQL connection string (e.g., postgres://user:pass@host:5432/proxima?sslmode=disable) |
PROXIMA_DB_MAX_OPEN_CONNS | 25 | No | Maximum number of open database connections |
PROXIMA_DB_MAX_IDLE_CONNS | 5 | No | Maximum number of idle database connections |
PROXIMA_DB_CONN_MAX_LIFETIME | 300 | No | Maximum connection lifetime in seconds |
PROXIMA_NATS_URL | nats://localhost:4222 | No | NATS server URL |
PROXIMA_NATS_CREDS_FILE | — | Yes (JWT mode) | Path to backend NATS credentials file |
PROXIMA_NATS_SYS_CREDS_FILE | — | Yes (JWT mode) | Path to sys-admin credentials file (required for JWT revocation) |
PROXIMA_NATS_CA_FILE | — | Yes (JWT mode) | Path to NATS TLS CA certificate |
PROXIMA_SERVER_PORT | 8080 | No | HTTP server listen port |
PROXIMA_CORS_ORIGINS | http://localhost:5173 | No | Comma-separated list of allowed CORS origins |
PROXIMA_LOG_LEVEL | info | No | Log level (debug, info, warn, error) |
PROXIMA_INSTALL_TOKEN | — | No | Shared install token for agent enrollment (development only) |
PROXIMA_AGENT_STALE_TIMEOUT_SECONDS | 300 | No | Seconds without a heartbeat before an agent is marked offline |
PROXIMA_AGENT_STALE_CHECK_SECONDS | 60 | No | Interval (seconds) between stale-agent detection sweeps |
PROXIMA_API_RATE_LIMIT | 1000 | No | Max API requests per minute per IP on /api/v1/ routes |
PROXIMA_AUTH_RATE_LIMIT | 5 | No | Max public auth requests per minute per IP (login, refresh, forgot-password) |
PROXIMA_CREDENTIAL_RATE_LIMIT | 10 | No | Max agent credential requests per minute per IP (challenge, exchange, renew) |
PROXIMA_JWT_SECRET | — | Yes | JWT signing secret. Must be at least 32 characters — the check is unconditional in Load(), so a shorter value makes the backend refuse to start. Generate with openssl rand -base64 48. |
PROXIMA_JWT_ACCESS_TTL | 900 | No | Access token TTL in seconds (15 minutes). Must be 60–3600; out of range is a startup failure, not a clamp. |
PROXIMA_JWT_REFRESH_TTL | 604800 | No | Refresh token TTL in seconds (7 days). Must be 300–2592000; out of range is a startup failure. |
PROXIMA_VALKEY_URL | — | No | Valkey (Redis-compatible) URL for auth caches |
PROXIMA_MFA_ENCRYPTION_KEY | — | No | Hex-encoded 32-byte AES-256 key for TOTP secret encryption |
PROXIMA_ADMIN_EMAIL | — | No | Email for the super-admin user seeded at startup |
PROXIMA_ADMIN_PASSWORD | — | No | Password for the super-admin user (12+ characters) |
PROXIMA_EMAIL_PROVIDER | smtp | No | Email delivery provider (smtp or resend) |
PROXIMA_EMAIL_FROM | [email protected] | No | Sender email address |
PROXIMA_FRONTEND_URL | https://app-console.prxm.uz | No | Frontend URL for email links |
PROXIMA_OTEL_ENABLED | false | No | Enable OpenTelemetry tracing |
PROXIMA_OTEL_EXPORTER_ENDPOINT | localhost:4317 | No | OTLP gRPC exporter endpoint |
PROXIMA_OTEL_SERVICE_NAME | proxima-backend | No | Service name reported to the tracing backend |
PROXIMA_OTEL_TRACES_SAMPLER | always_on | No | Trace sampler strategy (always_on, traceidratio) |
PROXIMA_OTEL_TRACES_RATIO | 1.0 | No | Sampling ratio (0.0–1.0), used when sampler is traceidratio |
PROXIMA_ENABLE_DEBUG_ENDPOINTS | true | No | Enable /swagger and /metrics endpoints (set false in production) |
PROXIMA_GOOGLE_CLIENT_ID | — | No | Google OAuth 2.0 client ID (enables Google SSO when set) |
PROXIMA_GOOGLE_CLIENT_SECRET | — | No | Google OAuth 2.0 client secret |
PROXIMA_GOOGLE_ALLOWED_DOMAIN | proximaops.io | No | Restrict Google SSO to this email domain |
PROXIMA_BACKEND_URL | http://localhost:8080 | No | Backend public URL (used for OIDC redirect URIs) |
PROXIMA_EMAIL_FROM_NAME | Proxima Console | No | Sender display name in outgoing emails |
PROXIMA_RESEND_API_KEY | — | No | Resend API key (required when EMAIL_PROVIDER=resend) |
PROXIMA_SMTP_HOST | — | No | SMTP server hostname (required when EMAIL_PROVIDER=smtp) |
PROXIMA_SMTP_PORT | 587 | No | SMTP server port |
PROXIMA_SMTP_USERNAME | — | No | SMTP authentication username |
PROXIMA_SMTP_PASSWORD | — | No | SMTP authentication password |
PROXIMA_JIRA_BASE_URL | — | No | Atlassian instance URL for feedback widget (e.g. https://yourorg.atlassian.net) |
PROXIMA_JIRA_EMAIL | — | No | Service account email for Jira API Basic Auth |
PROXIMA_JIRA_API_TOKEN | — | No | Jira API token for Basic Auth |
PROXIMA_JIRA_PROJECT_KEY | — | No | Target Jira project key for feedback issues |
PROXIMA_JIRA_ISSUE_TYPE | Task | No | Issue type name for feedback issues |
Config Loading Order
Configuration values are resolved in the following order, with earlier sources taking precedence:
- Environment variables — highest priority, always wins
- Code defaults — hardcoded fallback values in the application
The backend has no YAML config layer. Its config package loads from environment variables only;
gopkg.in/yaml.v3 appears in go.mod as an indirect dependency, not as a config source. (The agent
has no config file either — see Agent Configuration.)
Example .env File
For local development, you can use a .env file with docker run --env-file or source it in your shell:
# Required
PROXIMA_DB_URL=postgres://proxima:secretpass@localhost:5432/proxima?sslmode=disable
# Optional overrides
PROXIMA_NATS_URL=nats://localhost:4222
PROXIMA_SERVER_PORT=8080
PROXIMA_LOG_LEVEL=debug
PROXIMA_NATS_CREDS_FILE=infra/nats/creds/backend.creds
PROXIMA_NATS_SYS_CREDS_FILE=infra/nats/creds/sys-admin.creds
PROXIMA_NATS_CA_FILE=infra/nats/certs/ca.pem
# CORS (frontend dev server)
PROXIMA_CORS_ORIGINS=http://localhost:5173
# JWT Auth
# Must be >= 32 chars or the backend refuses to start: openssl rand -base64 48
PROXIMA_JWT_SECRET=CHANGE_ME_use_openssl_rand_base64_48_at_least_32_chars
PROXIMA_VALKEY_URL=redis://localhost:6379
# MFA (generate: openssl rand -hex 32)
PROXIMA_MFA_ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
# Admin seeding (first run only)
PROXIMA_ADMIN_EMAIL=[email protected]
PROXIMA_ADMIN_PASSWORD=change-me-on-first-login
# Email
PROXIMA_EMAIL_PROVIDER=smtp
PROXIMA_SMTP_HOST=localhost
PROXIMA_SMTP_PORT=1025
PROXIMA_FRONTEND_URL=http://localhost:5173
# Rate limits (requests per minute per IP)
PROXIMA_API_RATE_LIMIT=1000
PROXIMA_AUTH_RATE_LIMIT=5
PROXIMA_CREDENTIAL_RATE_LIMIT=10
# Stale agent detection
PROXIMA_AGENT_STALE_TIMEOUT_SECONDS=300
PROXIMA_AGENT_STALE_CHECK_SECONDS=60
# Google SSO (optional)
# PROXIMA_GOOGLE_CLIENT_ID=your-google-client-id
# PROXIMA_GOOGLE_CLIENT_SECRET=your-google-client-secret
# PROXIMA_GOOGLE_ALLOWED_DOMAIN=yourcompany.com
# Jira feedback integration (optional)
# PROXIMA_JIRA_BASE_URL=https://yourorg.atlassian.net
# [email protected]
# PROXIMA_JIRA_API_TOKEN=your-api-token
# PROXIMA_JIRA_PROJECT_KEY=PDD
# Debug endpoints (disable in production)
# PROXIMA_ENABLE_DEBUG_ENDPOINTS=false
# OpenTelemetry (disabled by default)
PROXIMA_OTEL_ENABLED=false
PROXIMA_OTEL_EXPORTER_ENDPOINT=localhost:4317
PROXIMA_OTEL_SERVICE_NAME=proxima-backend
Database Connection
The PROXIMA_DB_URL must be a valid PostgreSQL connection string. The backend uses connection pooling via sqlx with configurable pool sizes:
- Max open connections controls the total number of connections the pool can open to the database. Set this based on your PostgreSQL
max_connectionssetting and the number of backend instances. - Max idle connections controls how many connections remain open when not in use. A higher value reduces connection setup latency at the cost of holding more database resources.
- Connection max lifetime prevents connections from going stale. The default of 300 seconds (5 minutes) is suitable for most deployments.
NATS Connection
The backend connects to NATS for agent data ingestion via JetStream. NATS being unavailable at
startup is not fatal and not a 503. The transport falls back to RetryOnFailedConnect, logs
nats connection pending; retrying in background at Info, and the process starts and serves.
/readyz reports 200 degraded in that state — only a PostgreSQL failure produces 503 not_ready.
Header support is re-detected on the first successful reconnect.
Stale Agent Detection
The backend runs a StaleDetectorWorker that periodically checks for agents that have stopped sending heartbeats. If a host's last_seen_at timestamp is older than the configured timeout, its agent_status is set to offline.
PROXIMA_AGENT_STALE_TIMEOUT_SECONDS(default300) — How long to wait (in seconds) after the last heartbeat before considering an agent stale. The default of 5 minutes allows for 10 missed heartbeats (at the default 30-second heartbeat interval).PROXIMA_AGENT_STALE_CHECK_SECONDS(default60) — How often the worker runs its sweep. A shorter interval means faster detection but more frequent queries.
Per-Client Feature Flags
Each client can have features individually enabled or disabled. Super admins can toggle flags via the "Features" card on the client detail page.
Available feature flags:
| Flag | Description | Default |
|---|---|---|
cmdb | Asset management and CMDB | on |
terminal | Terminal access | on |
service_desk | Jira ITSM integration | on |
monitoring | System monitoring and metrics | off |
compliance | Healthcheck and compliance scoring | off |
changes | Change detection | off |
alerts | Alert management | off |
runbooks | Runbook execution | off |
logs | Log collection and search | off |
ai_chat | AI Chat with Claude | off |
oncall_enabled | Paging. Route matching, escalation arming, the escalation timer worker, the notification chain. Off = this client's alerts page nobody | off |
ai_triage | AI investigation. Auto-triage, the post-incident memory draft, remediation proposals, reactive /engage in a bound Telegram chat. Off = alerts still page exactly as before, and nothing is investigated automatically | off |
ai_triage_manual | Investigation mode. Off = automatic; on = the correlation worker skips auto-triage and an operator triggers each investigation on demand. Never affects paging | off |
remediation | L1 remediation proposals. Requires ai_triage as well — the worker re-checks both, fail-closed | off |
project_hours_visible | Show a client its own project tracked-hours-vs-limit | off |
change_notes_in_triage | Include recent proxima-wiki change notes in AI investigations of this client's alerts. The client's own users can read investigations, so they may see this content; the Service Desk change-note surfaces stay staff-only regardless | off |
resolve_check | Corroborate a source's resolve against independent evidence before writing it. Defaults on — see Resolution Corroboration before turning it off | on |
l1_agent | Retired. Replaced by oncall_enabled + ai_triage; still read as a fallback for both (see below) | off |
l1_manual_triage | Retired. Replaced by ai_triage_manual; still read as a fallback for it | off |
Flags resolve via GET /api/v1/auth/me in the client_features response field. The frontend useFeatures() hook and FeatureGuard component gate access automatically.
The retired l1_agent, and why it is still here
l1_agent was a single boolean gating two unrelated products. It read as an AI switch and was
labelled "L1 Agent" in the UI, but it also decided whether anyone got paged — so turning the AI
off turned the pager off, and the only trace was a no-page reason named l1_disabled. It is now
two flags that each state their own consequence:
oncall_enabledoff → nobody is paged. Alerts still ingest, correlate and enrich; the route matcher is never reached.ai_triageoff → alerts page exactly as before, and nothing is auto-investigated.
Neither gates the other. A client may page without investigating, or investigate without paging — two states that were unreachable while one boolean stood for both.
domain.ResolveFeatures carries a legacy alias: a client whose stored features still holds
only {"l1_agent": true} resolves both new flags from it, so the split changed no client's
behaviour. Migration 000230 backfills the stored JSONB and deliberately does not delete the
legacy keys — that is what lets a rollback to the previous binary still resolve correctly. A
later release removes the alias and the legacy keys together; until then, do not add a reader of
l1_agent or l1_manual_triage.
incident_grouping_active (a paging knob, read straight out of the features JSONB in SQL rather
than through ResolveFeatures) and the per-client LLM budget overrides are stored alongside these
flags and edited in the same Features card, but they are not resolved feature flags and do not
appear in client_features.
Logging
The backend uses Go's slog package to emit structured JSON logs to stdout. The log level is controlled by PROXIMA_LOG_LEVEL:
| Level | What is logged |
|---|---|
debug | Detailed diagnostic information, request/response bodies |
info | Normal operational events (startup, requests, shutdown) |
warn | Recoverable issues (connection retries, deprecated usage) |
error | Failures that need attention (query errors, NATS disconnects) |
All log entries include a request_id field for end-to-end request tracing.