Skip to content

Configuration

All configuration is managed through environment variables defined in .env. Copy .env.example to .env and adjust values for your environment.

Application Environment

VariableDefaultDescription
ENVIRONMENTdevelopmentSet to production to enforce JWT validation, strict CORS, the RLS startup checks, and to disable the dev email outbox

Database

VariableDefaultDescription
DATABASE_URLpostgresql+asyncpg://nis2:nis2secret@postgres:5432/nis2Async database connection string (used by FastAPI)
DATABASE_URL_SYNCpostgresql://nis2:nis2secret@postgres:5432/nis2Sync connection string (used by Alembic migrations)
POSTGRES_USERnis2PostgreSQL user
POSTGRES_PASSWORDnis2secretPostgreSQL password
POSTGRES_DBnis2PostgreSQL database name
MIGRATION_DATABASE_URL(falls back to DATABASE_URL)Privileged identity used ONLY for Alembic and the boot-time RLS setup. DATABASE_URL must point at a NOSUPERUSER NOBYPASSRLS role or every RLS policy is decorative — the API asserts this at startup and refuses to serve otherwise
MIGRATION_DATABASE_URL_SYNC(falls back to DATABASE_URL_SYNC)Sync variant of the above
NIS2_APP_PASSWORDPassword for the nis2_app runtime role, created on first volume init by infra/docker/initdb/01-create-app-role.sh
DB_POOL_SIZE10Connections per pool, per gunicorn worker — gunicorn preforks, so each worker builds its own
DB_MAX_OVERFLOW5Overflow per worker. Total demand is workers × (pool + overflow); keep it below the server's max_connections (100 in the stock image)

Redis

VariableDefaultDescription
REDIS_URLredis://redis:6379/0Redis connection for caching and sessions

Authentication (JWT)

VariableDefaultDescription
JWT_SECRET(change in production)Secret key for signing JWT tokens. Generate with openssl rand -hex 32
JWT_ALGORITHMHS256JWT signing algorithm
ACCESS_TOKEN_EXPIRE_MINUTES30Access token lifetime in minutes
REFRESH_TOKEN_EXPIRE_DAYS7Refresh token lifetime in days

RS256 key generation

bash
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

Set JWT_PRIVATE_KEY and JWT_PUBLIC_KEY to the contents of those files (newlines escaped as \n, or use multi-line block syntax in .env). The public key is then served at GET /.well-known/jwks.json for third parties to verify tokens with.

Encryption at rest

VariableDefaultDescription
DATA_ENCRYPTION_KEYAES-GCM key for TOTP seeds, notification-channel credentials and leaked-secret evidence. At least 32 characters; the API refuses to boot without strong key material
DATA_ENCRYPTION_KEY_PREVIOUS(empty)The key being rotated away from. Set during a rotation only — see Secrets rotation; decryption tries the current key first and falls back to this one

Observability

VariableDefaultDescription
LOG_LEVELINFOVerbosity for the API and the worker. Every record carries the request id, which also returns in the X-Request-Id response header

GET /metrics exposes Prometheus series — request rate, errors and latency by route template, plus database pool utilisation. It is unauthenticated and mounted outside /api/v1, because Prometheus scrapes it over the compose network by service name; block it at the edge if the API is exposed directly. The bundled prometheus.yml already scrapes it.

GET /api/v1/health reports the running version and the commit the image was built from.

Password reset (B05)

The forgot/reset flow needs a public URL to put in the email link and an SMTP relay (or the dev outbox) to deliver it. In make dev and the e2e suite, leaving SMTP_HOST empty activates the in-memory dev outbox — the email is logged at INFO and captured for GET /api/v1/auth/debug/last-email (mounted only when ENVIRONMENT != "production"). Production with SMTP_HOST empty refuses to deliver: the route turns the RuntimeError into a 5xx rather than silently dropping the email.

VariableDefaultDescription
PUBLIC_URLhttp://localhost:8077Base URL the reset link points to. The user clicks ${PUBLIC_URL}/reset-password?token=…
RESET_TOKEN_TTL_MINUTES30Lifetime of the reset token. Tokens are single-use; once consumed (used_at non-null) they're rejected even within the TTL
SMTP_HOST`` (dev outbox)SMTP relay hostname. Leave empty in dev / e2e — the email is captured in-process instead
SMTP_PORT587SMTP relay port
SMTP_USER``SMTP auth username (omit if your relay doesn't require auth)
SMTP_PASSWORD``SMTP auth password
SMTP_FROMnoreply@nis2.localFrom: header on outgoing emails
SMTP_STARTTLStrueIssue STARTTLS after EHLO (the common case for ports 25 / 587)
SMTP_SSLfalseWrap the entire connection in TLS (port 465 style). Mutually exclusive with SMTP_STARTTLS

Reports

Generated reports (PDF / HTML / Markdown / JSON / CSV / JUnit XML) live under /tmp/nis2-reports/ on the Celery worker, shared with the API container via the reports-data Docker named volume. A daily Celery beat task (cleanup-old-reports) sweeps this directory of files older than the TTL — without it, the disk grows unbounded as users generate reports.

VariableDefaultDescription
REPORT_TTL_DAYS30Days to keep generated report files before the daily cleanup task deletes them. Long enough for a compliance team to download last week's report after a holiday; short enough that a deploy generating 100s of scans/day doesn't fill the disk in weeks. The cleanup task always runs at the schedule's wall-clock cadence regardless of this value (it just changes the cutoff age).

Celery

VariableDefaultDescription
CELERY_BROKER_URLredis://redis:6379/1Celery message broker
CELERY_RESULT_BACKENDredis://redis:6379/2Celery result backend

Frontend (Next.js)

VariableDefaultDescription
NEXT_PUBLIC_API_URLhttp://localhost:8000Public API URL (client-side)

NEXTAUTH_URL, NEXTAUTH_SECRET and API_URL were documented here and are not read by anything: next-auth is not a dependency of packages/web. If your .env still carries them they are inert.

Production (Caddy)

VariableDefaultDescription
DOMAINnis2.yourdomain.comDomain for Caddy auto-HTTPS. Set this for production deployments

Scanner Defaults

Scanner behavior is configured per scan via the API when creating a scan or schedule. Organization settings store defaults that new scans inherit. Key defaults in the scan creation endpoint:

  • Timeout: 10 seconds per check (scan_timeout)
  • Concurrency: 20 parallel tasks (concurrency)
  • Max hosts: 0 (unlimited) -- configurable limit on targets per scan (max_hosts)
  • Features: Individual check categories (dns_checks, web_checks, port_scan, whois_checks) can be toggled per scan. Organization settings store the defaults that new scans inherit.

Organization Settings

Organization-level settings are managed through the dashboard under Settings:

  • Organization name and metadata
  • Default scan configuration (features, concurrency, timeout)
  • Team member management (invite, role assignment)
  • API key management
  • Notification channel preferences

Row-Level Security

VariableDefaultDescription
RLS_SUPERUSER_OKSet to 1 to suppress the startup error when the database role is SUPERUSER or BYPASSRLS. Not recommended for production — provision a non-superuser app role instead