Skip to content

Configuration

qnop is configured in two places, and knowing which is which saves a lot of time:

  • Environment variables — infrastructure. Where the database is, where the bucket is, which secrets to use. Changing them needs a restart.
  • Administration → Settings — everything an operator tunes while the system runs: SMTP, upload limits, self-registration, retention. These are not environment variables.

Every environment variable is prefixed QNOP_.

These have no usable default. Each ships as CHANGE_ME, which is rejected at startup — the server refuses to boot rather than run insecurely.

VariablePurposeRequirement
QNOP_AUTH_JWT_SECRETsigns access tokens≥ 32 characters
QNOP_AUTH_ENCRYPTION_KEYencrypts secrets held at rest (e.g. OIDC client secrets)≥ 32 characters
QNOP_AUTH_ENCRYPTION_SALTkey-derivation salthex-encoded, ≥ 16 characters
Terminal window
QNOP_AUTH_JWT_SECRET="$(openssl rand -base64 48)"
QNOP_AUTH_ENCRYPTION_KEY="$(openssl rand -base64 48)"
QNOP_AUTH_ENCRYPTION_SALT="$(openssl rand -hex 32)"

Note the salt uses -hex, not -base64.

VariableDefaultPurpose
QNOP_DB_HOSTlocalhostPostgres host
QNOP_DB_PORT5432Postgres port
QNOP_DB_NAMEqnopdatabase name
QNOP_DB_USERNAMEqnopuser
QNOP_DB_PASSWORDqnoppassword

The defaults are development values — set all of them in production. The standard SPRING_DATASOURCE_* variables override the URL assembled from these if you need full control.

VariableDefaultPurpose
QNOP_S3_ENDPOINT(empty)endpoint URL — leave empty for real AWS S3
QNOP_S3_BUCKETqnop-documentsbucket name
QNOP_S3_ACCESS_KEYqnopaccess key
QNOP_S3_SECRET_KEYqnop_secretsecret key
QNOP_S3_REGIONus-east-1region, or the placeholder your vendor requires
QNOP_S3_PATH_STYLE_ACCESStruetrue for MinIO; set false for AWS S3
QNOP_S3_AUTO_CREATE_BUCKETfalsecreate the bucket on first start
QNOP_S3_REAPER_GRACE_PERIODPT1Hgrace before a staged, unreferenced object is swept
QNOP_S3_REAPER_CRON0 30 3 * * *when that sweep runs

The defaults are tuned for the bundled MinIO. Pointing at AWS S3 means clearing the endpoint and flipping QNOP_S3_PATH_STYLE_ACCESS to false.

VariableDefaultPurpose
QNOP_HTTP_FORWARD_HEADERS_STRATEGYnoneset framework behind a trusted reverse proxy
QNOP_CORS_ALLOWED_ORIGINShttp://localhost:5173comma-separated exact origins; not needed for the embedded UI
QNOP_HTTP_SHUTDOWN_GRACE30sgraceful-shutdown drain
QNOP_HTTP_CONNECTION_TIMEOUT20sconnection timeout
QNOP_HTTP_KEEP_ALIVE_TIMEOUT20skeep-alive reap
QNOP_HTTP_REQUEST_TIMEOUT30sasync dispatch cap

Outbound timeouts — used for OIDC discovery, JWKS fetches and SMTP — are QNOP_HTTP_CLIENT_OUTBOUND_CONNECT_TIMEOUT (5s), QNOP_HTTP_CLIENT_OUTBOUND_READ_TIMEOUT (15s), and the QNOP_HTTP_CLIENT_SMTP_* trio (10s connect, 30s read, 30s write).

VariableDefaultPurpose
QNOP_UPLOAD_MULTIPART_LIMIT_MB55servlet-level multipart ceiling
VariableDefaultPurpose
QNOP_AUTH_OIDC_FRONTEND_BASE_URL(empty)where to return after OIDC login; empty = same origin
QNOP_AUTH_OIDC_ALLOW_PRIVATE_DISCOVERY_URISfalseSSRF guard — see below
QNOP_AUTH_RATE_LIMIT_TRUSTED_PROXY_CIDRS(empty)which hops may set the client IP

OIDC providers themselves are not configured by environment variable — they are managed at runtime under Administration → OIDC providers. See Authentication.

DOCX uploads are converted to PDF by a headless LibreOffice, which the official image ships. The same converter renders annotation exports.

VariableDefaultPurpose
QNOP_OFFICE_BINARYsofficethe LibreOffice executable
QNOP_OFFICE_TIMEOUT60show long one conversion may run
QNOP_OFFICE_MAX_CONCURRENT2conversions running at once
QNOP_OFFICE_MAX_WAIT30show long a conversion waits for a free slot

A conversion that finds every slot busy for longer than QNOP_OFFICE_MAX_WAIT is refused: an export answers 503 with Retry-After, and an ingest job simply retries later. Without LibreOffice — a custom image, say — PDF keeps working and DOCX is not offered.

The tracking provider itself is configured in the admin area. Two decisions sit in the environment because they must not be reachable from the web UI:

VariableDefaultPurpose
QNOP_TRACKING_ALLOW_PRIVATE_HOSTfalselet the tracking proxy reach a private or loopback address
QNOP_TRACKING_RATE_LIMIT_MAX_ATTEMPTS120request budget of the tracking proxy
QNOP_TRACKING_RATE_LIMIT_WINDOW_SECONDS60the window those attempts are counted in

Enable QNOP_TRACKING_ALLOW_PRIVATE_HOST only for a self-hosted analytics server on your internal network — the same switch would let the server call cloud metadata addresses.

These exist for operators who run qnop for someone else — a hosted plan, in practice. An administrator inside the instance can neither see nor change them, which is the point. The defaults change nothing: every limit is 0 (unlimited) and every capability is on, so a self-hosted deployment needs none of this.

VariableBounds
QNOP_LIMITS_MAX_USERSuser accounts
QNOP_LIMITS_MAX_TEAMSteams
QNOP_LIMITS_MAX_TEAM_MEMBERSmembers per team
QNOP_LIMITS_MAX_ACTIVE_REVIEWSreviews in draft, in review or changes requested

A limit stops the next record, never an existing one: lowering it below current usage leaves everything in place.

The capability switches, all true by default, are QNOP_FEATURES_OIDC, QNOP_FEATURES_ANNOTATION_EXPORT, QNOP_FEATURES_CUSTOM_BRANDING, QNOP_FEATURES_SCHEDULER_MANUAL_RUN, QNOP_FEATURES_SCHEDULER_JOB_SETTINGS, QNOP_FEATURES_SMTP_CONFIGURATION, QNOP_FEATURES_EMAIL_TEMPLATES, QNOP_FEATURES_USAGE_TRACKING, QNOP_FEATURES_UPLOAD_CONSTRAINTS, QNOP_FEATURES_SELF_REGISTRATION and QNOP_FEATURES_DEPLOYMENT_CONFIGURATION. Switching one off closes the door rather than hiding the handle — with QNOP_FEATURES_OIDC=false, for example, the OAuth2 endpoints are not mounted at all. Most of the others make a group of admin settings read-only or unavailable; the application.yml in the repository documents each one.

VariableDefaultPurpose
QNOP_ADMIN_PASSWORD(unset)fixed initial admin password

Unset, qnop generates a one-time password on first start and forces a change at first login. Set, it uses yours and skips the forced change.

These live in Administration → Settings and change without a restart. The list matters because operators regularly go looking for them in the environment:

  • General — application name, base URL, default language and timezone.
  • Uploads — document_max_file_size_mb (default 25), attachment_max_file_size_mb (default 10).
  • SMTP — enabled, host, port, credentials, encryption, from-address and from-name. Mail is configured here, not in the environment.
  • Authentication — self-registration and its default role, password reset and its token lifetime.
  • Notifications — whether review e-mails are sent, and how long in-app notifications are kept (notifications.retain_days).
  • Usage tracking — the analytics provider, host, site id, consent and opt-outs, including whether the client IP is forwarded to it.

Each person additionally chooses, in their own profile, their interface language and how review activity reaches them by e-mail: as it happens, once a day as a morning digest (the default), or not at all. Mentions arrive straight away either way. Mail is written in the recipient’s language, falling back to the application’s default language and then to English; the shipped templates cover English, German, Spanish, French and Brazilian Portuguese, and each can be edited per language under Administration → Email.

  • Review — free reattach, finalize-with-open-annotations, and the archive and purge retention windows.

A number of further variables exist and bind by naming convention, but are not part of the documented contract and can change without notice: token lifetimes (QNOP_AUTH_ACCESS_TOKEN_TTL, QNOP_AUTH_REFRESH_TOKEN_TTL), the re-anchoring similarity thresholds (QNOP_REANCHORING_*), the per-endpoint rate-limit budgets (QNOP_AUTH_RATE_LIMIT_*), and the maintenance cron expressions (QNOP_S3_ORPHAN_REAPER_CRON, QNOP_REVIEW_ARCHIVE_CRON, QNOP_REVIEW_PURGE_CRON). Reach for them only with a specific reason.