TheDuffman85/crowdsec-web-ui

A self-hosted dashboard for CrowdSec: investigate alerts, manage decisions, monitor runtime metrics, and send notifications from one responsive UI.

TypeScript

699

218 commits

updated Sep 25, 2026

See the code

See what people are saying

SourceMessageScoreDate

Feature overlap across your homelab tools (r/selfhosted)

I'm quite happy with [my home lab](https://github.com/Yann39/self-hosted-n100), after refining it for months (mainly swapping out tools until finding the best match), everything is running smoothly, securely, and backed up. So now that everything's working fine, I've been thinking hard to come up…

29

Sep 27, 2026

README

CrowdSec Web UI Logo

GitHub Workflow Status Trivy Scan GitHub License GitHub last commit Latest Container

CrowdSec Web UI

A self-hosted dashboard for CrowdSec: investigate alerts, manage decisions, monitor runtime metrics, and send notifications from one responsive UI.

React Vite Tailwind CSS Node.js Docker

Features

AreaHighlights
DashboardAlert and active-decision totals, attack map, drilldowns, top lists, shared quick filters, and simulation counts
AlertsSearchable alert history, persistent count-aware quick filters, CrowdSec alert contexts, IP/AS/location details, event metadata, simulation labels, and configurable columns
DecisionsActive and expired decisions, persistent count-aware quick filters, duplicate hiding, manual bans, custom durations, reasons, and cleanup actions
Multi-instanceSeveral CrowdSec LAPIs, per-instance views, and a Combined scope for Dashboard, Alerts, and Decisions
MetricsOptional Prometheus views for LAPI activity, bouncers, AppSec, parsers, latency, parsing time, and whitelists
NotificationsAlert, decision, CVE, availability, and update rules delivered through Email, Gotify, MQTT, ntfy, or Webhooks
SecurityInitial administrator setup, password and TOTP login, passkeys, OIDC SSO, group roles, and instance-wide read-only mode
LocalizationArabic, Chinese, English, French, German, Hindi, Japanese, Portuguese, Russian, and Spanish
ExperienceUnified search, server-synced saved and recent filters, dark/light themes, and responsive layouts

Screenshots

Dashboard Runtime Metrics

Combined multi-instance alerts Alerts

Alert details with CrowdSec context Search Syntax

Quick filters applied to alerts Saved and recently used filters

Decisions Add Decision

Notification Center Notification Rule

Settings

Quick Start

You need a running CrowdSec LAPI. Connect the Web UI using either watcher password authentication or agent mTLS.

1. Register the Web UI

Watcher password

openssl rand -hex 32
docker exec crowdsec cscli machines add crowdsec-web-ui --password 'replace-with-generated-password' -f /dev/null

# For local installations
sudo cscli machines add crowdsec-web-ui --password 'replace-with-generated-password' -f /dev/null

Replace replace-with-generated-password with the value printed by openssl.

[!IMPORTANT] Keep -f /dev/null. It registers the machine without overwriting the CrowdSec container's existing credentials file.

Agent mTLS

Configure LAPI TLS authentication and create a client certificate/key pair using the CrowdSec TLS authentication guide.

2. Start with Docker Compose

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec_web_ui
    ports:
      - "3000:3000"
    # For local CrowdSec instances
    # extra_hosts:
      # - "host.docker.internal:host-gateway"
    environment:
      CONFIG_INSTANCE_LAPI_URL: http://crowdsec:8080
      # For local CrowdSec instances
      # CONFIG_INSTANCE_LAPI_URL: http://host.docker.internal:8080
      CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCE_LAPI_AUTH_PASSWORD: your-crowdsec-password
    volumes:
      - ./data:/app/data
    restart: unless-stopped

A ready-to-use docker-compose.yml is included. Add the generated password, make sure the Web UI can reach CrowdSec on the same Docker network, then start it.

docker compose up -d

Open http://localhost:3000 and create the initial administrator account.

Docker Run Alternative

docker pull ghcr.io/theduffman85/crowdsec-web-ui:latest
mkdir -p data
docker run -d \
  --name crowdsec_web_ui \
  -p 3000:3000 \
  -v $(pwd)/data:/app/data \
  -e CONFIG_INSTANCE_LAPI_URL=http://crowdsec:8080 \
  -e CONFIG_INSTANCE_LAPI_AUTH_USERNAME=crowdsec-web-ui \
  -e CONFIG_INSTANCE_LAPI_AUTH_PASSWORD=your-crowdsec-password \
  --network your_crowdsec_network \
  ghcr.io/theduffman85/crowdsec-web-ui:latest

Current images use Node.js and do not have the former Bun/AVX-specific x64 limitation.

mTLS Compose Alternative

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec_web_ui
    ports:
      - "3000:3000"
    environment:
      CONFIG_INSTANCE_LAPI_URL: https://crowdsec:8080
      CONFIG_INSTANCE_LAPI_AUTH_TYPE: mtls
      CONFIG_INSTANCE_LAPI_AUTH_CERT_FILE: /certs/agent.pem
      CONFIG_INSTANCE_LAPI_AUTH_KEY_FILE: /certs/agent-key.pem
      # CONFIG_INSTANCE_LAPI_TLS_CA_FILE: /certs/ca.pem
    volumes:
      - ./data:/app/data
      - /path/on/host/agent.pem:/certs/agent.pem:ro
      - /path/on/host/agent-key.pem:/certs/agent-key.pem:ro
      # - /path/on/host/ca.pem:/certs/ca.pem:ro
    restart: unless-stopped

Adjust the URL and certificate paths. Enable CONFIG_INSTANCE_LAPI_TLS_CA_FILE and its volume when LAPI uses a private CA.

[!CAUTION] Use HTTPS and a hardened reverse proxy for public deployments. Built-in authentication protects the UI and API, but TLS terminates outside the application. OIDC integrations include Authentik, Authelia, and Keycloak. Migrated installations that predate authentication remain unauthenticated until explicitly enabled.

Architecture

ComponentImplementation
ClientReact, Vite, and Tailwind CSS; builds to dist/client
ServerNode.js and Hono; builds to dist/server
StorageSQLite via better-sqlite3 under /app/data
CrowdSecWatcher password or agent mTLS; delta refreshes and chunked historical synchronization
ContainerRuns as the non-root node user

Configuration

Configuration files

EnvironmentDefault path
Docker/app/data/config.yaml
Local./data/config.yaml

Use CONFIG_FILE only to select another existing file. config.example.yaml contains the complete commented YAML reference.

Configuration lifecycle

StageBehavior
First startCreates the missing default file. Supplied values become active YAML; defaults and optional examples remain comments. Generated mappings use block rows and the documented order.
Later startsTreats the file as user-managed. CONFIG_* values override it in memory without rewriting it; generated explanations and defaults are not refreshed.
Persistent overridesCONFIG_PERSIST_OVERRIDES: "true" writes validated merged values while preserving comments where possible. Removing a persisted non-secret override leaves its last value in YAML.
PrecedenceApplies section variables, then field variables, then indexed array variables. Removing a non-persisted override reveals the file value.
LoggingRecords applied paths and before/after values. Credentials are redacted; secret references show only their environment name or file path.
ReloadingRequires a restart after configuration changes or secret rotation.

Environment overrides

  • Values are parsed as YAML and validated.
  • Arrays use zero-based contiguous indexes: CONFIG_AUTH_OIDC_ADMIN_GROUPS_0, CONFIG_INSTANCES_0_ID, CONFIG_INSTANCES_0_METRICS_0_URL.
  • Whole sections accept YAML through CONFIG_SERVER, CONFIG_STORAGE, CONFIG_UI, CONFIG_AUTH, CONFIG_NOTIFICATIONS, CONFIG_AUDIT, CONFIG_UPDATES, CONFIG_CROWDSEC, or CONFIG_INSTANCES.
  • CONFIG_INSTANCE_* addresses instance 0: CONFIG_INSTANCE_NAME equals CONFIG_INSTANCES_0_NAME. Metrics index 0 may also be omitted: CONFIG_INSTANCES_0_METRICS_URL equals CONFIG_INSTANCES_0_METRICS_0_URL, and CONFIG_INSTANCE_METRICS_URL applies both shorthands. Do not set equivalent forms together.
  • Secrets accept a direct string or exactly one env: NAME / file: PATH reference. Secret overrides also accept _FILE.
  • Initial direct secret overrides are stored as environment references, never plaintext. Persisted secret references still require their environment variable.

Server, storage, UI, and updates

YAML fieldDefaultPurposeEnvironment override
server.port3000HTTP listen port.CONFIG_SERVER_PORT
server.basePath""Optional URL prefix such as /crowdsec; no trailing slash.CONFIG_SERVER_BASE_PATH
storage.dataDir/app/dataSQLite database and persistent application state.CONFIG_STORAGE_DATA_DIR
storage.geonamesDir/app/geonames in Docker; ./geonames locallyLocal GeoNames snapshot used for location labels.CONFIG_STORAGE_GEONAMES_DIR
storage.walEnabledtrueEnables SQLite write-ahead logging. Set to false for filesystems that do not support WAL.CONFIG_STORAGE_WAL_ENABLED
storage.incrementalVacuumEnabledtrueUses bounded incremental vacuum while idle on compatible databases. Existing databases are not migrated automatically.CONFIG_STORAGE_INCREMENTAL_VACUUM_ENABLED
storage.journalSizeLimit128MiBRetained WAL size after checkpoints. This does not cap transactions; use unlimited to disable trimming. Ignored when WAL is disabled.CONFIG_STORAGE_JOURNAL_SIZE_LIMIT
ui.timeZonebrowserBrowser timezone or an IANA zone such as Europe/Berlin or UTC.CONFIG_UI_TIME_ZONE
ui.timeFormatbrowserClock format: browser, 12h, or 24h.CONFIG_UI_TIME_FORMAT
ui.dateFormatbrowserBrowser date format or a custom pattern such as dd/mm/yyyy, mm/dd/yyyy, or yyyy-MM-dd.CONFIG_UI_DATE_FORMAT
ui.readOnlyfalseHides management actions and rejects mutating API operations.CONFIG_UI_READ_ONLY
updates.enabledtrue in packaged imagesEnables the built-in update check.CONFIG_UPDATES_ENABLED

Date patterns require one day, month, and year token in any order: d/dd (day), m/mm (numeric month), mmm/mmmm (localized month name), and yy/yyyy (year). Punctuation and spaces are literal; wrap words in single quotes, for example d 'of' mmmm yyyy. Date patterns affect full dates and timestamps; compact chart labels keep their own display format. Set ui.timeFormat separately for a 12 or 24 hour clock.

Authentication

YAML fieldDefaultPurposeEnvironment override
auth.enabledautoEnables authentication; auto enables new databases while preserving migrated database state.CONFIG_AUTH_ENABLED
auth.sessionSecretGenerated and storedSigns sessions and encrypts saved authentication settings.CONFIG_AUTH_SESSION_SECRET or CONFIG_AUTH_SESSION_SECRET_FILE
auth.totpSecretsessionSecretEncrypts stored per-account TOTP seeds.CONFIG_AUTH_TOTP_SECRET or CONFIG_AUTH_TOTP_SECRET_FILE
auth.totpSeedUnsetOptional base32 fallback TOTP seed for the password user; minimum 26 characters.CONFIG_AUTH_TOTP_SEED or CONFIG_AUTH_TOTP_SEED_FILE
auth.oidc.issuerUrlUnsetOIDC provider issuer URL.CONFIG_AUTH_OIDC_ISSUER_URL
auth.oidc.clientIdUnsetOIDC client identifier.CONFIG_AUTH_OIDC_CLIENT_ID
auth.oidc.clientSecretUnsetOIDC client secret.CONFIG_AUTH_OIDC_CLIENT_SECRET or CONFIG_AUTH_OIDC_CLIENT_SECRET_FILE
auth.oidc.scopeopenid profile emailRequested OIDC scopes; must include openid.CONFIG_AUTH_OIDC_SCOPE
auth.oidc.groupsClaimgroupsClaim containing role-mapping groups.CONFIG_AUTH_OIDC_GROUPS_CLAIM
auth.oidc.adminGroups[]Groups granted administrator access.CONFIG_AUTH_OIDC_ADMIN_GROUPS or CONFIG_AUTH_OIDC_ADMIN_GROUPS_<INDEX>
auth.oidc.readOnlyGroups[]Groups granted read-only access.CONFIG_AUTH_OIDC_READ_ONLY_GROUPS or CONFIG_AUTH_OIDC_READ_ONLY_GROUPS_<INDEX>
auth.oidc.unmatchedRoledenyRole for unmatched OIDC users: deny, admin, or read-only.CONFIG_AUTH_OIDC_UNMATCHED_ROLE

Notifications

YAML fieldDefaultPurposeEnvironment override
notifications.secretKeyGenerated and storedEncrypts saved notification credentials.CONFIG_NOTIFICATIONS_SECRET_KEY or CONFIG_NOTIFICATIONS_SECRET_KEY_FILE
notifications.allowPrivateAddressestrueAllows private, loopback, and link-local notification destinations.CONFIG_NOTIFICATIONS_ALLOW_PRIVATE_ADDRESSES
notifications.debugPayloadsfalseLogs truncated rendered payloads after failed notification delivery.CONFIG_NOTIFICATIONS_DEBUG_PAYLOADS

Audit log

User actions that change CrowdSec state (adding decisions, deleting decisions and alerts, and per-IP cleanup) are recorded as single [audit] lines in the application log. Each entry is a JSON object with the timestamp, acting user and role, action, target (IP, range, or entity IDs), and outcome:

[audit] {"time":"2026-08-17T01:30:00.000Z","user":"tommy","role":"admin","action":"decision.add","ip":"192.0.2.10","type":"ban","duration":"4h","reason":"manual","instances":["CrowdSec"],"outcome":"success"}

Outcomes are success, partial, failure, or queued. Alert deletions handled by the persistent deletion queue are recorded as queued, because CrowdSec may complete them after the HTTP request returns. Multi-target actions include target_results or instance_results so successful, failed, and queued targets remain distinguishable.

YAML fieldDefaultPurposeEnvironment override
audit.enabledtrueWrites audit entries for user actions to the application log.CONFIG_AUDIT_ENABLED
audit.logFileUnsetAlso appends audit entries as JSON lines to this file.CONFIG_AUDIT_LOG_FILE

Alert handling

Omitting crowdsec.alertFilters uses the standard non-CAPI feed. Setting any explicit filter field enables explicit filtering.

YAML fieldDefaultPurposeEnvironment override
crowdsec.simulationsEnabledfalseIncludes simulation-mode alerts and decisions.CONFIG_CROWDSEC_SIMULATIONS_ENABLED
crowdsec.alertFilters.includeOrigins[]Keeps alerts matching these exact origins.CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_ORIGINS or CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_ORIGINS_<INDEX>
crowdsec.alertFilters.excludeOrigins[]Drops alerts matching these exact origins.CONFIG_CROWDSEC_ALERT_FILTERS_EXCLUDE_ORIGINS or CONFIG_CROWDSEC_ALERT_FILTERS_EXCLUDE_ORIGINS_<INDEX>
crowdsec.alertFilters.includeCapifalseAdds the Central API/community-blocklist feed.CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_CAPI
crowdsec.alertFilters.includeOriginEmptyfalseKeeps empty-origin alerts with explicit include filters.CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_ORIGIN_EMPTY
crowdsec.alertFilters.excludeOriginEmptyfalseDrops alerts whose effective origin is empty.CONFIG_CROWDSEC_ALERT_FILTERS_EXCLUDE_ORIGIN_EMPTY
crowdsec.alertFilters.legacy.origins[]Compatibility origin allowlist; CAPI enables the CAPI feed.CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_ORIGINS or CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_ORIGINS_<INDEX>
crowdsec.alertFilters.legacy.extraScenarios[]Compatibility list of additional scenarios.CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_EXTRA_SCENARIOS or CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_EXTRA_SCENARIOS_<INDEX>

Global synchronization

Synchronization durations accept ms, s, m, h, or d, for example 500ms, 30s, 5m, or 7d. The lookback fields accept only m, h, or d.

YAML fieldDefaultPurposeEnvironment override
crowdsec.sync.lookback168hImported history and retention window.CONFIG_CROWDSEC_SYNC_LOOKBACK
crowdsec.sync.refreshInterval1mActive refresh cadence; 0 or manual disables scheduling.CONFIG_CROWDSEC_SYNC_REFRESH_INTERVAL
crowdsec.sync.manualRefreshEnabledfalseEnables manual refresh controls.CONFIG_CROWDSEC_SYNC_MANUAL_REFRESH_ENABLED
crowdsec.sync.idleRefreshInterval10mRefresh cadence while the application is idle; 0 disables it.CONFIG_CROWDSEC_SYNC_IDLE_REFRESH_INTERVAL
crowdsec.sync.idleThreshold2mInactivity before idle refresh behavior begins.CONFIG_CROWDSEC_SYNC_IDLE_THRESHOLD
crowdsec.sync.requestTimeout30sTimeout for individual LAPI requests.CONFIG_CROWDSEC_SYNC_REQUEST_TIMEOUT
crowdsec.sync.bouncerPropagationDelay15sGrace period before deleting alerts owned by expired decisions.CONFIG_CROWDSEC_SYNC_BOUNCER_PROPAGATION_DELAY
crowdsec.sync.deletionQueueMaxAge24hStops retrying failed queued deletions after this age; 0 disables the limit. Tombstones remain until the retention window passes.CONFIG_CROWDSEC_SYNC_DELETION_QUEUE_MAX_AGE
crowdsec.sync.metricsRequestTimeout5sDefault timeout for metrics endpoints.CONFIG_CROWDSEC_SYNC_METRICS_REQUEST_TIMEOUT
crowdsec.sync.heartbeatInterval30sCrowdSec machine heartbeat cadence; 0 disables it.CONFIG_CROWDSEC_SYNC_HEARTBEAT_INTERVAL
crowdsec.sync.alertSyncChunk12hHistorical import window size.CONFIG_CROWDSEC_SYNC_ALERT_SYNC_CHUNK
crowdsec.sync.alertSyncMinChunk15mMinimum retry window after a timed-out import.CONFIG_CROWDSEC_SYNC_ALERT_SYNC_MIN_CHUNK
crowdsec.sync.reconcileWindow1hFixed alert-history reconciliation window size.CONFIG_CROWDSEC_SYNC_RECONCILE_WINDOW
crowdsec.sync.reconcileRecentAge24hBoundary between recent and older windows.CONFIG_CROWDSEC_SYNC_RECONCILE_RECENT_AGE
crowdsec.sync.reconcileRecentInterval15mReconciliation cadence for recent windows.CONFIG_CROWDSEC_SYNC_RECONCILE_RECENT_INTERVAL
crowdsec.sync.reconcileActiveInterval5mReconciliation cadence for windows with active decisions.CONFIG_CROWDSEC_SYNC_RECONCILE_ACTIVE_INTERVAL
crowdsec.sync.reconcileOldInterval3hReconciliation cadence for older windows.CONFIG_CROWDSEC_SYNC_RECONCILE_OLD_INTERVAL
crowdsec.sync.reconcileWindowsPerRefresh2Maximum due windows processed per refresh.CONFIG_CROWDSEC_SYNC_RECONCILE_WINDOWS_PER_REFRESH
crowdsec.sync.bootstrapRetryDelay30sDelay between failed initial-sync retries; 0 retries immediately.CONFIG_CROWDSEC_SYNC_BOOTSTRAP_RETRY_DELAY
crowdsec.sync.bootstrapRetryEnabledtrueEnables background retry after initial synchronization failure.CONFIG_CROWDSEC_SYNC_BOOTSTRAP_RETRY_ENABLED

Instances and LAPI

  • <INDEX> is zero-based.
  • ID defaults to the index; name defaults to Instance <INDEX>; authentication type is inferred from credentials.
  • Inferred values appear as comments in initial YAML unless compatibility requires an explicit identity.
  • Explicit IDs use lowercase letters, digits, _, and -. Keep them stable after importing data.

[!IMPORTANT] Configure exactly one credential shape:

  • Password auth: set username and password
  • mTLS auth: set certFile and keyFile

type is optional and inferred from these fields. Set it explicitly to none, password, or mtls when desired. Do not mix password and mTLS credentials.

Plaintext secrets are supported, but mounted secret files are recommended so credentials do not end up in source control, backups, or configuration-management logs.

YAML fieldDefaultPurposeEnvironment override
instancesOne generated default instanceConfigures one or more CrowdSec connections.CONFIG_INSTANCES or CONFIG_INSTANCES_<INDEX>_*
instances[].idZero-based instance indexStable database identity for the instance.CONFIG_INSTANCES_<INDEX>_ID
instances[].nameInstance <INDEX>Unique display name.CONFIG_INSTANCES_<INDEX>_NAME
instances[].iconUnsetOptional short text or emoji shown in the selector.CONFIG_INSTANCES_<INDEX>_ICON
instances[].lapiRequiredComplete LAPI connection object.CONFIG_INSTANCES_<INDEX>_LAPI
instances[].lapi.urlRequired (http://crowdsec:8080 in starter config)Absolute HTTP(S) LAPI base URL without credentials, a path, or a fragment.CONFIG_INSTANCES_<INDEX>_LAPI_URL
instances[].lapi.authtype: noneLAPI authentication object.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH
instances[].lapi.auth.typeInferred from credentialsOptional authentication mode: none, password, or mtls.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_TYPE
instances[].lapi.auth.usernameRequired for passwordCrowdSec machine username.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_USERNAME
instances[].lapi.auth.passwordRequired for passwordCrowdSec machine password or secret reference.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_PASSWORD or CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_PASSWORD_FILE
instances[].lapi.auth.certFileRequired for mtlsClient certificate path.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_CERT_FILE
instances[].lapi.auth.keyFileRequired for mtlsClient private-key path.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_KEY_FILE
instances[].lapi.tlsEmpty mappingLAPI server-trust settings.CONFIG_INSTANCES_<INDEX>_LAPI_TLS
instances[].lapi.tls.caFileUnsetCA bundle used to verify the LAPI server.CONFIG_INSTANCES_<INDEX>_LAPI_TLS_CA_FILE
instances[].metrics[]Zero or more metrics endpoints.CONFIG_INSTANCES_<INDEX>_METRICS or CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_*
instances[].syncInherits global valuesPer-instance synchronization overrides.CONFIG_INSTANCES_<INDEX>_SYNC or CONFIG_INSTANCES_<INDEX>_SYNC_*

Metrics endpoints

  • <INDEX> selects the instance; zero-based <METRIC_INDEX> selects its endpoint.
  • Endpoint ID defaults to <METRIC_INDEX> and name to Metrics <METRIC_INDEX>.
  • Endpoint icons are optional short text or emoji values and appear in the metrics source selector.
  • Inferred values appear as comments in initial YAML.
YAML fieldDefaultPurposeEnvironment override
instances[].metrics[].idZero-based metrics indexStable identifier unique within the instance.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_ID
instances[].metrics[].nameMetrics <METRIC_INDEX>Display name.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_NAME
instances[].metrics[].iconUnsetOptional short text or emoji icon.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_ICON
instances[].metrics[].urlRequiredAbsolute HTTP(S) Prometheus endpoint URL.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_URL
instances[].metrics[].requestTimeoutGlobal 5sRequest timeout for this endpoint.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_REQUEST_TIMEOUT
instances[].metrics[].authtype: noneComplete metrics authentication object.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH
instances[].metrics[].auth.typeInferred from credentialsOptional authentication mode: none, basic, or bearer.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_TYPE
instances[].metrics[].auth.usernameRequired for basicBasic-auth username.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_USERNAME
instances[].metrics[].auth.passwordRequired for basicBasic-auth password or secret reference.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_PASSWORD or CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_PASSWORD_FILE
instances[].metrics[].auth.tokenRequired for bearerBearer token or secret reference.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_TOKEN or CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_TOKEN_FILE
instances[].metrics[].tlsEmpty mappingMetrics TLS settings.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS
instances[].metrics[].tls.caFileUnsetCA bundle used to verify the metrics server.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS_CA_FILE
instances[].metrics[].tls.certFileUnsetOptional metrics client certificate; requires keyFile.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS_CERT_FILE
instances[].metrics[].tls.keyFileUnsetOptional metrics client private key; requires certFile.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS_KEY_FILE

Per-instance synchronization overrides

Every field inherits its corresponding global value when omitted.

YAML fieldDefaultPurposeEnvironment override
instances[].sync.lookbackGlobal 168hHistory and retention window for this instance.CONFIG_INSTANCES_<INDEX>_SYNC_LOOKBACK
instances[].sync.refreshIntervalGlobal 1mActive refresh cadence.CONFIG_INSTANCES_<INDEX>_SYNC_REFRESH_INTERVAL
instances[].sync.idleRefreshIntervalGlobal 10mIdle refresh cadence.CONFIG_INSTANCES_<INDEX>_SYNC_IDLE_REFRESH_INTERVAL
instances[].sync.idleThresholdGlobal 2mTime before this instance is considered idle.CONFIG_INSTANCES_<INDEX>_SYNC_IDLE_THRESHOLD
instances[].sync.requestTimeoutGlobal 30sLAPI request timeout.CONFIG_INSTANCES_<INDEX>_SYNC_REQUEST_TIMEOUT
instances[].sync.heartbeatIntervalGlobal 30sMachine heartbeat cadence.CONFIG_INSTANCES_<INDEX>_SYNC_HEARTBEAT_INTERVAL
instances[].sync.alertSyncChunkGlobal 12hHistorical import window size.CONFIG_INSTANCES_<INDEX>_SYNC_ALERT_SYNC_CHUNK
instances[].sync.alertSyncMinChunkGlobal 15mMinimum retry window.CONFIG_INSTANCES_<INDEX>_SYNC_ALERT_SYNC_MIN_CHUNK
instances[].sync.reconcileWindowGlobal 1hReconciliation window size.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_WINDOW
instances[].sync.reconcileRecentAgeGlobal 24hRecent-window age boundary.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_RECENT_AGE
instances[].sync.reconcileRecentIntervalGlobal 15mRecent-window reconciliation cadence.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_RECENT_INTERVAL
instances[].sync.reconcileActiveIntervalGlobal 5mActive-decision reconciliation cadence.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_ACTIVE_INTERVAL
instances[].sync.reconcileOldIntervalGlobal 3hOlder-window reconciliation cadence.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_OLD_INTERVAL
instances[].sync.reconcileWindowsPerRefreshGlobal 2Due-window budget per refresh.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_WINDOWS_PER_REFRESH
instances[].sync.bootstrapRetryDelayGlobal 30sInitial-sync retry delay.CONFIG_INSTANCES_<INDEX>_SYNC_BOOTSTRAP_RETRY_DELAY
instances[].sync.bootstrapRetryEnabledGlobal trueEnables background initial-sync retry.CONFIG_INSTANCES_<INDEX>_SYNC_BOOTSTRAP_RETRY_ENABLED
instances[].sync.bouncerPropagationDelayGlobal 15sAlert-deletion grace period.CONFIG_INSTANCES_<INDEX>_SYNC_BOUNCER_PROPAGATION_DELAY

Multiple CrowdSec instances

Use zero-based CONFIG_INSTANCES_<INDEX>_* overrides to define each instance. Indexes must be contiguous, starting at 0.

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    environment:
      CONFIG_INSTANCES_0_ID: eu-prod
      CONFIG_INSTANCES_0_NAME: EU Production
      CONFIG_INSTANCES_0_LAPI_URL: http://crowdsec-eu:8080
      CONFIG_INSTANCES_0_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCES_0_LAPI_AUTH_PASSWORD_FILE: /run/secrets/eu-lapi-password
      CONFIG_INSTANCES_0_METRICS_0_ID: lapi
      CONFIG_INSTANCES_0_METRICS_0_NAME: EU LAPI
      CONFIG_INSTANCES_0_METRICS_0_URL: http://crowdsec-eu:6060/metrics

      CONFIG_INSTANCES_1_ID: us-prod
      CONFIG_INSTANCES_1_NAME: US Production
      CONFIG_INSTANCES_1_LAPI_URL: http://crowdsec-us:8080
      CONFIG_INSTANCES_1_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCES_1_LAPI_AUTH_PASSWORD_FILE: /run/secrets/us-lapi-password

      # For mTLS, replace instance 1's URL and password credentials above with:
      # CONFIG_INSTANCES_1_LAPI_URL: https://crowdsec-us:8080
      # CONFIG_INSTANCES_1_LAPI_AUTH_TYPE: mtls
      # CONFIG_INSTANCES_1_LAPI_AUTH_CERT_FILE: /certs/us-client-cert.pem
      # CONFIG_INSTANCES_1_LAPI_AUTH_KEY_FILE: /run/secrets/us-client-key.pem
      # CONFIG_INSTANCES_1_LAPI_TLS_CA_FILE: /certs/us-ca.pem
    volumes:
      - ./secrets/eu-lapi-password:/run/secrets/eu-lapi-password:ro
      - ./secrets/us-lapi-password:/run/secrets/us-lapi-password:ro
      # Mount these files when using the commented mTLS configuration:
      # - ./certs/us-client-cert.pem:/certs/us-client-cert.pem:ro
      # - ./secrets/us-client-key.pem:/run/secrets/us-client-key.pem:ro
      # - ./certs/us-ca.pem:/certs/us-ca.pem:ro
  • Use indexed variables for every instance in a multi-instance setup; reserve the CONFIG_INSTANCE_* shorthand for single-instance deployments.
  • Each instance needs a stable ID, unique display name, LAPI URL, and one authentication method.
  • Add metrics endpoints with CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_*.
  • Mount password files read-only. When using the optional mTLS configuration, mount its private keys and certificates read-only as well, then restart the container.

YAML alternative

Add entries to the top-level instances array. Each entry defines one LAPI connection and zero or more metrics endpoints.

instances:
  - id: eu-prod
    name: EU Production
    icon: 🇪🇺
    lapi:
      url: http://crowdsec-eu:8080
      auth:
        type: password
        username: crowdsec-web-ui
        password:
          file: /run/secrets/eu-lapi-password

    metrics:
      - id: lapi
        name: EU LAPI
        icon: 🧠
        url: http://crowdsec-eu:6060/metrics
        auth:
          type: bearer
          token:
            file: /run/secrets/eu-metrics-token

      - id: edge-engine
        name: EU Edge Engine
        icon: 🛡️
        url: http://crowdsec-edge:6060/metrics

    sync:
      requestTimeout: 45s
      alertSyncChunk: 6h

  - id: us-prod
    name: US Production
    icon: 🇺🇸
    lapi:
      url: http://crowdsec-us:8080
      auth:
        type: password
        username: crowdsec-web-ui
        password:
          file: /run/secrets/us-lapi-password
      # For mTLS, replace url and auth above with:
      # url: https://crowdsec-us:8080
      # auth:
      #   type: mtls
      #   certFile: /run/secrets/us-client-cert
      #   keyFile: /run/secrets/us-client-key
      # tls:
      #   caFile: /run/secrets/us-ca

Configuration rules

  • Instance and endpoint IDs are unique, URL-safe, and immutable database identities. They are 1–63 characters long, start with a lowercase letter or digit, use only lowercase letters, digits, _, or -, and must never be reused for another LAPI.
  • Display names are unique but editable. icon accepts up to eight Unicode code points of text or emoji without control characters; omitted icons use colored squares and Combined uses a grid.
  • Password secrets accept a direct value or exactly one env/file source. mTLS requires both certFile and keyFile; tls.caFile controls server trust.
  • Metrics authentication supports none, basic, and bearer; metrics TLS supports caFile plus an optional complete client certificate/key pair.
  • Embedded URL credentials, URL fragments, ambiguous secret sources, partial certificate pairs, unreadable files, and TLS verification bypasses fail validation. LAPI base URLs also reject paths.
  • Prefer mounted secret files. Restart after configuration, certificate, or secret changes.

Multi-instance behavior

AreaBehavior
Dashboard, Alerts, DecisionsSupport one instance or Combined scope
MetricsDefaults to a source-aware Combined view when the selected scope has multiple endpoints; individual endpoints remain selectable
Add decision / clean IPRuns against every LAPI in Combined scope and reports partial failures
Row deletionUses the row's owning instance; numeric upstream IDs are never broadcast

Authentication

Authentication covers the browser UI and protected APIs; /api/health remains public.

auth.enabledBehavior
autoEnables authentication for new databases; preserves the state of migrated databases
trueRequires authentication and initial administrator setup
falseDisables authentication; this deployment setting is not available in the UI

Upgraded installations

Enable authentication explicitly on installations migrated from older releases.

auth:
  enabled: true

Local accounts

  • Password changes and passkey registration/removal from Settings.
  • Optional TOTP enrollment through a QR code, mobile setup link, or manual key.
  • An enrolled TOTP seed overrides the optional base32 auth.totpSeed fallback.
  • Administrators can disable password login.

OIDC

Configure OIDC in Settings or YAML.

auth:
  enabled: true
  oidc:
    issuerUrl: https://idp.example.com/application/o/crowdsec-web-ui/
    clientId: crowdsec-web-ui
    clientSecret:
      file: /run/secrets/oidc_client_secret
    scope: openid profile email
    groupsClaim: groups
    adminGroups: [crowdsec-admins, secops]
    readOnlyGroups: [crowdsec-viewers]
    unmatchedRole: deny

Callback URL

Register this callback URI with the identity provider.

https://<crowdsec-web-ui-host>/api/auth/oidc/callback

Requirements and roles

  • The callback must exactly match the public scheme, host, port, and base path. For basePath: /crowdsec, use https://<host>/crowdsec/api/auth/oidc/callback.
  • Reverse proxies must forward Host or X-Forwarded-Host and X-Forwarded-Proto.
  • Saved Settings override YAML. Scopes must include openid; add provider-specific scopes such as groups only when required.
  • Admin-group matches have full access; read-only-group matches can view data and keep permitted preferences; unmatched users follow auth.oidc.unmatchedRole (deny by default).
  • Set an unmatched fallback role only when every user who can sign in should receive it.
  • ui.readOnly: true overrides all roles for the deployment. It blocks CrowdSec writes, refresh changes, notification destination/rule management, test sends, and notification deletion. Language changes and marking notifications read remain available. This is not per-user RBAC.
  • Identities use stable issuer and subject claims. Username collisions with local accounts remain separate.
  • Sessions have a 24-hour absolute lifetime. OIDC-only users cannot add local passkeys; password-backed local accounts retain passkey support.
  • Existing OIDC rows migrate on their next successful SSO login.

Deployment and Security

Trusted IPs for Alert Deletion

[!IMPORTANT] CrowdSec only permits alert deletion when the request comes from loopback or an address listed in api.server.trusted_ips. Registering the Web UI as a CrowdSec machine authenticates it, but does not grant this IP-based permission.

When the Web UI runs in Docker, add the Web UI container's source IP or, preferably, its Docker network CIDR to CrowdSec's /etc/crowdsec/config.yaml. This is not the browser's IP or the Docker host's public IP. Without this entry, decision operations can still work while alert deletion fails with 403 Forbidden.

api:
  server:
    trusted_ips:
      - 127.0.0.1
      - ::1
      - 172.16.0.0/12  # Docker default bridge network

Use the narrowest CIDR that contains the Web UI container and LAPI network. Container IPs can change when containers are recreated, so the Docker network CIDR is usually more reliable than one container IP. Restart CrowdSec after updating the file. The current CrowdSec container does not provide a TRUSTED_IPS environment override. See the CrowdSec configuration reference.

Local or Custom LAPI Certificate

A self-signed certificate or internal CA may produce the following error.

Login failed: unable to get local issuer certificate

Mount the CA certificate and configure it for the LAPI instance.

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec_web_ui
    ports:
      - "3000:3000"
    environment:
      CONFIG_INSTANCE_LAPI_URL: https://crowdsec:8080
      CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCE_LAPI_AUTH_PASSWORD_FILE: /run/secrets/crowdsec_password
      CONFIG_INSTANCE_LAPI_TLS_CA_FILE: /certs/root_ca.crt
    secrets:
      - crowdsec_password
    volumes:
      - ./data:/app/data
      - /path/on/host/root_ca.crt:/certs/root_ca.crt:ro
    restart: unless-stopped

secrets:
  crowdsec_password:
    file: ./secrets/crowdsec_password.txt

Keep the CA mount read-only. CONFIG_INSTANCE_LAPI_TLS_CA_FILE maps to instances[0].lapi.tls.caFile; no image rebuild is needed.

HTTPS Reverse Proxy

CrowdSec Web UI listens for HTTP on port 3000 and does not obtain or terminate TLS certificates itself. Put a reverse proxy in front of it for HTTPS deployments and keep port 3000 private to the proxy.

HTTPS is required for passkeys because browsers expose WebAuthn only in a secure context. http://localhost is suitable for local testing, but a remote deployment needs a stable hostname and a certificate trusted by the browser. Passkeys registered for one hostname cannot be used from a different hostname.

The proxy must preserve Host (or set X-Forwarded-Host) and set X-Forwarded-Proto to the original scheme. The application uses these headers for WebAuthn origins, secure session cookies, OIDC callback URLs, and mutation-origin checks.

Traefik example

This minimal example assumes Traefik already has a websecure entrypoint, a Let's Encrypt resolver named letsencrypt, and an external Docker network named proxy. Add the labels and proxy network to the existing Web UI service, replacing the hostname and CrowdSec settings.

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    expose:
      - "3000"
    environment:
      CONFIG_INSTANCE_LAPI_URL: http://crowdsec:8080
      CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCE_LAPI_AUTH_PASSWORD: your-crowdsec-password
      # For https://crowdsec.example.com/crowdsec/:
      # CONFIG_SERVER_BASE_PATH: /crowdsec
    volumes:
      - ./data:/app/data
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      # For /crowdsec/, append: && PathPrefix(`/crowdsec`)
      - 'traefik.http.routers.crowdsec-web-ui.rule=Host(`crowdsec.example.com`)'
      - traefik.http.routers.crowdsec-web-ui.entrypoints=websecure
      - traefik.http.routers.crowdsec-web-ui.tls.certresolver=letsencrypt
      - traefik.http.services.crowdsec-web-ui.loadbalancer.server.port=3000
    networks:
      - proxy
      - crowdsec
    restart: unless-stopped

networks:
  proxy:
    external: true
  crowdsec:
    external: true
    name: your_crowdsec_network

Traefik supplies the forwarded headers and WebSocket upgrade handling automatically. See the Traefik ACME documentation if the letsencrypt resolver is not configured yet.

Nginx example

This equivalent example assumes Nginx already terminates HTTPS and the Web UI port is published only on loopback, for example 127.0.0.1:3000:3000.

# For https://crowdsec.example.com/crowdsec/, set
# CONFIG_SERVER_BASE_PATH=/crowdsec and replace both `/` paths below
# with `/crowdsec/`.
location / {
    proxy_pass http://localhost:3000/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Proxy requirements

  • The base path starts with / and has no trailing slash.
  • / redirects to it; APIs, assets, and navigation follow it automatically.
  • With Traefik on a shared hostname, add CONFIG_SERVER_BASE_PATH: /crowdsec and use a router rule such as Host(`example.com`) && PathPrefix(`/crowdsec`). Do not configure StripPrefix; the application expects to receive the base path.
  • The backend checks browser mutation origins, applies a Content Security Policy, limits API bodies to 1 MiB, and marks API responses private, no-store.
  • Command-line and service clients without browser Origin and Sec-Fetch-Site headers remain compatible.
  • Configure HSTS at the TLS-terminating proxy; the application does not emit it.

Health Check

The public endpoint is GET /api/health. Startup does not wait for LAPI: bootstrap retries in the background, so the container can become healthy before synchronization completes.

curl http://localhost:3000/api/health
# {"status":"ok"}

The built-in check runs every 30 seconds with a five-second timeout, a 10-second start period, and three retries.

docker inspect --format='{{.State.Health.Status}}' crowdsec_web_ui

server.basePath does not affect the internal check at localhost:3000/api/health. If server.port changes, update the health-check command and port mapping.

Runtime Behavior

Prometheus Metrics Page

  • Reads a configured raw CrowdSec Prometheus endpoint. The Web UI does not configure one by default; CrowdSec normally exposes its local scrape at http://127.0.0.1:6060/metrics.
  • Shows remediation-component and log-processor LAPI activity, AppSec, parsers and datasources, scenarios, LAPI latency, parsing time, and whitelist hits. Log-processor activity uses POST /v1/alerts; CrowdSec builds that expose an exact last-heartbeat timestamp also provide the processor health badge.
  • When the current instance scope has multiple endpoints, Combined is selected by default. Headline totals sum successful sources, detailed rows retain their instance and endpoint, and unavailable endpoints produce a partial-data warning instead of hiding healthy sources.
  • Combined counters are cumulative across processes that may have different start times. Configuring the same Prometheus scrape more than once counts it more than once; endpoint roles and automatic duplicate detection are not applied.
  • Alert and decision analytics remain on the main dashboard.

Enable full metrics in CrowdSec's /etc/crowdsec/config.yaml.

prometheus:
  enabled: true
  level: full
  listen_addr: 127.0.0.1
  listen_port: 6060

For separate containers, bind listen_addr: 0.0.0.0 on a trusted network, then configure the matching Web UI instance.

environment:
  CONFIG_INSTANCE_METRICS_URL: http://crowdsec:6060/metrics
CrowdSec levelResult
fullAll supported details, including per-machine, per-bouncer, and per-node metrics
aggregatedLess detail; omits those per-entity metrics
noneDisables metrics registration

AppSec and latency sections appear only when CrowdSec emits those metrics. Time-window-only rate()/increase() metrics are intentionally omitted. See the CrowdSec Prometheus documentation.

Display Preferences

  • crowdsec.simulationsEnabled: true fetches non-remediating simulation alerts/decisions and shows badges, filters, and dashboard counts. Default: false.
  • Alerts and Decisions column layouts persist per browser profile in local storage.
  • ID, Machine, and Origin are hidden by default. Machine prefers machine_alias, then machine_id; multiple alert decision origins display as Mixed.
  • Hidden columns remain searchable through fields such as id:, machine:, and origin:.

Quick Filters

Dashboard, Alerts, and Decisions share one count-aware Quick Filters drawer. Its trigger shows the number of active selections, and the clear control in the drawer header resets all stored quick filters.

Instance and machine options use stable IDs for filtering while displaying their configured names or aliases. Country, instance, and machine searches match the displayed label as well as the stored value. Alerts with several origins or targets contribute to each distinct facet option instead of exposing a combined bucket.

  • Filter selections, date range, and simulation mode persist in local storage for the current browser profile and are restored when navigating between the three pages.
  • Changing a structured search field updates the matching quick-filter selection, and changing a quick filter updates the structured search. A field supplied explicitly in a page URL takes precedence over its stored value; stored values fill fields that are absent.
  • Dashboard top countries, scenarios, AS numbers, targets, the world map, and the Activity History range update the same shared state. Changes made in the drawer update those widgets in return.
  • Filtered Dashboard alert and active-decision counts use the same indexed predicates as the Alerts and Decisions lists. The alert card links with the alert-compatible query, while the decision card links with the decision-compatible query.
  • The drawer follows each table's configured column order. Filters for hidden columns are grouped under Hidden columns.
  • Page-only filters remain visible under Unavailable when another page cannot apply them. Alerts lists Action, Status, and Alert there; Decisions lists Decision; Dashboard lists all four. Their stored selections are preserved for the page that supports them and can be cleared from any drawer.
  • Facet selections use exact equality. Selecting the target tausend.me generates target=tausend.me and does not include bw.tausend.me. Manually entering target:tausend.me remains a broader contains search.
  • Each facet reports counts after the other active filters have been applied. Use the facet search control to find values beyond the initially loaded list.

Dashboard applies the shared fields Country, Scenario, Kind, AS, IP / Range, Target, ID, Instance, Region, City, Machine, and Origin. Filters that depend on decision-only or alert-list-only data are retained in Unavailable instead of being silently discarded.

Active decisions are deduplicated by instance, value, and simulation mode. When filters exclude the globally preferred decision, the best matching decision is promoted so enabling Hide duplicates cannot make an otherwise matching duplicate group disappear.

Saved and Recent Filters

Use the bookmark button beside search on Dashboard, Alerts, or Decisions to save the current valid query under a name. The menu lets you apply, rename, and delete saved filters, reuse the five most recently used queries, or clear recent history. Queries are shared across these pages; Apply is disabled when a query is not valid for the current page. Applying one changes the search query while keeping the selected instance and other page settings.

Saved filters and recent history are stored on the server for the signed-in user, so they are available on other devices. Read-only users can manage their own filters. If authentication is disabled, everyone using the installation shares one list. Valid nonempty queries enter recent history after a brief pause, including searches opened from bookmarked URLs. Quick Filter queries enter recent history only after the Quick Filters drawer closes.

Search Syntax

SyntaxExample
Free text / quoted phrasessh hetzner, "nginx bf"
Field contains / exact valuecountry:germany, country=DE
Date comparisondate>=2026-03-24, date<2026-03-25T12:00:00Z
Negative / empty-sim:simulated, sim<>simulated, origin:"", origin<>""
Boolean / groupingcountry:(germany OR france) AND -sim:simulated
Decision filtersstatus:active AND action:ban, alert:123 OR ip:"192.168.5.0/24"

The : operator performs a case-insensitive contains match, while = matches the complete field value. Quick Filter selections use =. Date fields support <, >, <=, >=, and =>. A bare field name is free text unless followed by :. Quote literal AND, OR, or NOT. The search Info button lists page-specific fields and examples.

Examples

PageQuery
Alertscountry:germany ssh
Alertsdate>=2026-03-24 AND date<2026-03-25
Alertscountry:(germany OR france) AND -sim:simulated
Alertskind=waf
Alertsorigin:""
Decisionsstatus:active AND action:ban
Decisionsdate>=2026-03-24 AND action:ban
Decisionsalert:123 OR ip:"192.168.5.0/24"
Decisionskind=waf

Alert Source Filtering

Limit the local cache by origin when CrowdSec ingests automation, blocklists, or community feeds.

crowdsec:
  alertFilters:
    includeOrigins: [crowdsec, cscli-import]
    excludeOrigins: [cscli]
    includeCapi: true
    includeOriginEmpty: true
    excludeOriginEmpty: false
OriginSource
crowdsecSecurity-engine decisions
cscliManual cscli decisions add
cscli-importcscli decisions import
listsImported list feeds
CAPICentral API / community blocklist

Behavior

  • No explicit filters fetches the normal non-CAPI/non-lists feed.
  • Includes are pushed upstream where possible. Generic excludes and empty-origin handling run locally because LAPI lacks those filters.
  • includeCapi: true adds CAPI to the default feed; includeOrigins: [CAPI] selects only CAPI.
  • If any origin is excluded, the whole alert is dropped.
  • Origins prefer associated decisions, then blocklist/list source scopes for alerts without decisions.
  • includeOriginEmpty retains origin-less alerts alongside includes; excludeOriginEmpty removes them.
  • Because Decisions is built from synchronized alerts, filters also change which imported decisions appear.

Examples

SettingResult
includeOrigins: [crowdsec]Keeps security-engine alerts only
includeOrigins: [lists]Keeps list-based alerts only
includeCapi: trueAdds CAPI to the default feed
includeOrigins: [CAPI]Keeps CAPI alerts only
includeOriginEmpty: trueKeeps origin-less alerts alongside explicit includes
excludeOriginEmpty: trueRemoves origin-less alerts
excludeOrigins: [cscli, lists]Removes manual and imported-list alerts

Notifications

Rules run against locally cached CrowdSec data, create in-app notifications, record delivery status, and optionally deliver outbound messages.

Rules

Every rule has a name, severity (info, warning, critical), incident deduplication, and destination channels. Alert rules filter scenario, target, and simulation state; the scenario filter can include or exclude matching names. IP Ban and New Alert/Decision also accept exact IP/CIDR filters. Window Minutes sets the rolling lookback for each rule evaluation; Alert Spike also compares it with the preceding period of equal length. It does not set how often rules are evaluated.

Rule typeBehavior
Alert SpikeCompares the current window with the previous window and triggers when percentage increase and minimum alert count are exceeded.
Alert ThresholdTriggers when matching alerts in the configured time window reach the threshold.
New Alert/DecisionCreates one notification for every matching alert, decision, or both within the lookback window. Includes record ID, timestamps, scenario, target, source/value, and related alert/decision details. Stable per-record deduplication prevents repeats.
IP BanTriggers once for each active ban decision in the configured window, supports exact IP/CIDR filters, and deduplicates duplicate active decision rows for the same ban.
Recent CVEExtracts CVE IDs from matching alerts and checks publication age before notifying.
LAPI AvailabilityTriggers when CrowdSec LAPI stays unavailable past the outage threshold, with optional recovery notifications.
Application UpdateUses the built-in update check and triggers when a newer CrowdSec Web UI version is available.

Multi-instance behavior

ScopeRules
Aggregate matching alerts across instancesAlert Spike, Alert Threshold, Recent CVE
Evaluate each matching recordNew Alert/Decision, IP Ban
Evaluate each instanceLAPI Availability
Evaluate each Prometheus endpointCrowdSec Update
Application-wideApplication Update

Instance-backed titles and metadata identify the contributing instance or instances.

[!NOTE] The Recent CVE rule queries the NVD API to determine when a CVE was published. If outbound access to services.nvd.nist.gov is blocked, recent-CVE notifications may be skipped.

The CrowdSec Update rule requires a configured Prometheus endpoint with a cs_info version metric and outbound access to the CrowdSec GitHub release API. It checks the latest stable release and notifies once per outdated endpoint and target version.

Destinations

Destinations are independently enabled and reusable across rules. Send Test validates saved settings immediately; results are stored as delivered or failed.

DestinationSettings
EmailSMTP host/port/security (Plain SMTP, STARTTLS, SMTPS / Implicit TLS), optional user/password, from address, comma-separated recipients, importance (auto, normal, important), and optional insecure TLS for trusted self-signed SMTP endpoints. Auto importance maps info to normal and warning/critical to important.
GotifyGotify URL, app token, and priority (auto or explicit integer). Auto priority maps info to 5, warning to 7, and critical to 10.
ntfyServer URL, topic, optional access token, and priority (auto, min, low, default, high, urgent). Auto priority maps info to default, warning to high, and critical to urgent.
MQTTGeneric publish-only output with broker URL, optional username/password/client ID, QoS 0 or 1, keepalive, connect timeout, topic, and retain flag. It does not include Home Assistant discovery, entity sync, or command handling.
WebhookCustom HTTP delivery with method (POST, PUT, PATCH), URL, optional query parameters/headers, auth (none, bearer token, or basic auth), body mode (JSON, Text, Form), timeout, retries, retry delay, and optional insecure TLS for trusted self-signed HTTPS endpoints.

Payloads and security

  • MQTT JSON contains title, message, severity, metadata, sent_at, channel_id, channel_name, channel_type, rule_id, rule_name, and rule_type. Tests use rule_id=test, rule_name=Test notification, and rule_type=test.
  • Webhook templates expose dotted event.* fields for title, message, severity, metadata, sent_at, channel_name, rule_id, rule_name, and rule_type. Each has a *Json variant; nullable rule fields also have OrUnknown and OrUnknownJson aliases.
  • Failed webhooks store HTTP status and a truncated response. notifications.debugPayloads: true also logs a truncated rendered body with sensitive form fields redacted; enable it only while troubleshooting.
  • Destination secrets are masked and encrypted by notifications.secretKey, or an auto-generated key stored in application metadata.
  • notifications.allowPrivateAddresses: false blocks private, loopback, and link-local destinations; default: true.
  • Telegram, Home Assistant discovery/state, and inbound MQTT commands are not supported.

Kubernetes

A Helm chart for CrowdSec Web UI is maintained by zekker6.

Persistence and Alert History

SQLite data lives under /app/data. Mount the directory—not only crowdsec.db—because WAL mode also uses crowdsec.db-wal and crowdsec.db-shm.

volumes:
  - ./data:/app/data
  • History survives restarts, merges with new LAPI data, and expires after crowdsec.sync.lookback (default: seven days).
  • Initial imports and reconciliation retry in smaller windows after timeouts.
  • During LAPI outages, the application serves its available cache and retries in the background; partial imports are marked.

Use POST /api/cache/clear for a full cache reset. Synchronization internals are documented in DEVELOPMENT.md.

SQLite database maintenance

New databases use SQLite incremental auto-vacuum by default. Existing databases keep their current format to avoid a potentially long, disk-intensive migration. If startup reports reclaimable space, migrate during a maintenance window:

  1. Back up the entire data directory, including crowdsec.db, crowdsec.db-wal, and crowdsec.db-shm when present.

  2. Stop CrowdSec Web UI and confirm that no process is using the database. Ensure free disk space is at least the current database size.

  3. Open crowdsec.db with SQLite and run:

    PRAGMA wal_checkpoint(TRUNCATE);
    PRAGMA auto_vacuum = INCREMENTAL;
    VACUUM;
    
  4. Close SQLite, restart the app, and verify the migration with PRAGMA auto_vacuum;. A result of 2 means incremental auto-vacuum is enabled.

VACUUM rewrites the database and may take significant time for large installations. Do not interrupt it. The application never runs this migration automatically.

Documentation

GuideContents
Configuration exampleComplete commented YAML configuration
API referenceAuthentication, routes, parameters, and request/response shapes
Development guideLocal setup, builds, tests, metadata, translations, and synchronization internals
Load testing guideSynthetic profiles, overrides, benchmarks, and container workflow

Star History

Star History Chart

Linux Update Dashboard Logo Linux Update Dashboard
A self-hosted dashboard for checking and applying Linux package updates across multiple servers.
crowdsec
crowdsec-lapi
crowdsec-manager
dashboard
docker
docker-compose
docker-container
gotify
metrics
mqtt
notifications
ntfy
self-hosted

Contributors

TheDuffman85

217 commits

Greite

1 commits

TheDuffman85/crowdsec-web-ui

A self-hosted dashboard for CrowdSec: investigate alerts, manage decisions, monitor runtime metrics, and send notifications from one responsive UI.

TypeScript

699

218 commits

updated Sep 25, 2026

See the code

See what people are saying

SourceMessageScoreDate

Feature overlap across your homelab tools (r/selfhosted)

I'm quite happy with [my home lab](https://github.com/Yann39/self-hosted-n100), after refining it for months (mainly swapping out tools until finding the best match), everything is running smoothly, securely, and backed up. So now that everything's working fine, I've been thinking hard to come up…

29

Sep 27, 2026

README

CrowdSec Web UI Logo

GitHub Workflow Status Trivy Scan GitHub License GitHub last commit Latest Container

CrowdSec Web UI

A self-hosted dashboard for CrowdSec: investigate alerts, manage decisions, monitor runtime metrics, and send notifications from one responsive UI.

React Vite Tailwind CSS Node.js Docker

Features

AreaHighlights
DashboardAlert and active-decision totals, attack map, drilldowns, top lists, shared quick filters, and simulation counts
AlertsSearchable alert history, persistent count-aware quick filters, CrowdSec alert contexts, IP/AS/location details, event metadata, simulation labels, and configurable columns
DecisionsActive and expired decisions, persistent count-aware quick filters, duplicate hiding, manual bans, custom durations, reasons, and cleanup actions
Multi-instanceSeveral CrowdSec LAPIs, per-instance views, and a Combined scope for Dashboard, Alerts, and Decisions
MetricsOptional Prometheus views for LAPI activity, bouncers, AppSec, parsers, latency, parsing time, and whitelists
NotificationsAlert, decision, CVE, availability, and update rules delivered through Email, Gotify, MQTT, ntfy, or Webhooks
SecurityInitial administrator setup, password and TOTP login, passkeys, OIDC SSO, group roles, and instance-wide read-only mode
LocalizationArabic, Chinese, English, French, German, Hindi, Japanese, Portuguese, Russian, and Spanish
ExperienceUnified search, server-synced saved and recent filters, dark/light themes, and responsive layouts

Screenshots

Dashboard Runtime Metrics

Combined multi-instance alerts Alerts

Alert details with CrowdSec context Search Syntax

Quick filters applied to alerts Saved and recently used filters

Decisions Add Decision

Notification Center Notification Rule

Settings

Quick Start

You need a running CrowdSec LAPI. Connect the Web UI using either watcher password authentication or agent mTLS.

1. Register the Web UI

Watcher password

openssl rand -hex 32
docker exec crowdsec cscli machines add crowdsec-web-ui --password 'replace-with-generated-password' -f /dev/null

# For local installations
sudo cscli machines add crowdsec-web-ui --password 'replace-with-generated-password' -f /dev/null

Replace replace-with-generated-password with the value printed by openssl.

[!IMPORTANT] Keep -f /dev/null. It registers the machine without overwriting the CrowdSec container's existing credentials file.

Agent mTLS

Configure LAPI TLS authentication and create a client certificate/key pair using the CrowdSec TLS authentication guide.

2. Start with Docker Compose

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec_web_ui
    ports:
      - "3000:3000"
    # For local CrowdSec instances
    # extra_hosts:
      # - "host.docker.internal:host-gateway"
    environment:
      CONFIG_INSTANCE_LAPI_URL: http://crowdsec:8080
      # For local CrowdSec instances
      # CONFIG_INSTANCE_LAPI_URL: http://host.docker.internal:8080
      CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCE_LAPI_AUTH_PASSWORD: your-crowdsec-password
    volumes:
      - ./data:/app/data
    restart: unless-stopped

A ready-to-use docker-compose.yml is included. Add the generated password, make sure the Web UI can reach CrowdSec on the same Docker network, then start it.

docker compose up -d

Open http://localhost:3000 and create the initial administrator account.

Docker Run Alternative

docker pull ghcr.io/theduffman85/crowdsec-web-ui:latest
mkdir -p data
docker run -d \
  --name crowdsec_web_ui \
  -p 3000:3000 \
  -v $(pwd)/data:/app/data \
  -e CONFIG_INSTANCE_LAPI_URL=http://crowdsec:8080 \
  -e CONFIG_INSTANCE_LAPI_AUTH_USERNAME=crowdsec-web-ui \
  -e CONFIG_INSTANCE_LAPI_AUTH_PASSWORD=your-crowdsec-password \
  --network your_crowdsec_network \
  ghcr.io/theduffman85/crowdsec-web-ui:latest

Current images use Node.js and do not have the former Bun/AVX-specific x64 limitation.

mTLS Compose Alternative

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec_web_ui
    ports:
      - "3000:3000"
    environment:
      CONFIG_INSTANCE_LAPI_URL: https://crowdsec:8080
      CONFIG_INSTANCE_LAPI_AUTH_TYPE: mtls
      CONFIG_INSTANCE_LAPI_AUTH_CERT_FILE: /certs/agent.pem
      CONFIG_INSTANCE_LAPI_AUTH_KEY_FILE: /certs/agent-key.pem
      # CONFIG_INSTANCE_LAPI_TLS_CA_FILE: /certs/ca.pem
    volumes:
      - ./data:/app/data
      - /path/on/host/agent.pem:/certs/agent.pem:ro
      - /path/on/host/agent-key.pem:/certs/agent-key.pem:ro
      # - /path/on/host/ca.pem:/certs/ca.pem:ro
    restart: unless-stopped

Adjust the URL and certificate paths. Enable CONFIG_INSTANCE_LAPI_TLS_CA_FILE and its volume when LAPI uses a private CA.

[!CAUTION] Use HTTPS and a hardened reverse proxy for public deployments. Built-in authentication protects the UI and API, but TLS terminates outside the application. OIDC integrations include Authentik, Authelia, and Keycloak. Migrated installations that predate authentication remain unauthenticated until explicitly enabled.

Architecture

ComponentImplementation
ClientReact, Vite, and Tailwind CSS; builds to dist/client
ServerNode.js and Hono; builds to dist/server
StorageSQLite via better-sqlite3 under /app/data
CrowdSecWatcher password or agent mTLS; delta refreshes and chunked historical synchronization
ContainerRuns as the non-root node user

Configuration

Configuration files

EnvironmentDefault path
Docker/app/data/config.yaml
Local./data/config.yaml

Use CONFIG_FILE only to select another existing file. config.example.yaml contains the complete commented YAML reference.

Configuration lifecycle

StageBehavior
First startCreates the missing default file. Supplied values become active YAML; defaults and optional examples remain comments. Generated mappings use block rows and the documented order.
Later startsTreats the file as user-managed. CONFIG_* values override it in memory without rewriting it; generated explanations and defaults are not refreshed.
Persistent overridesCONFIG_PERSIST_OVERRIDES: "true" writes validated merged values while preserving comments where possible. Removing a persisted non-secret override leaves its last value in YAML.
PrecedenceApplies section variables, then field variables, then indexed array variables. Removing a non-persisted override reveals the file value.
LoggingRecords applied paths and before/after values. Credentials are redacted; secret references show only their environment name or file path.
ReloadingRequires a restart after configuration changes or secret rotation.

Environment overrides

  • Values are parsed as YAML and validated.
  • Arrays use zero-based contiguous indexes: CONFIG_AUTH_OIDC_ADMIN_GROUPS_0, CONFIG_INSTANCES_0_ID, CONFIG_INSTANCES_0_METRICS_0_URL.
  • Whole sections accept YAML through CONFIG_SERVER, CONFIG_STORAGE, CONFIG_UI, CONFIG_AUTH, CONFIG_NOTIFICATIONS, CONFIG_AUDIT, CONFIG_UPDATES, CONFIG_CROWDSEC, or CONFIG_INSTANCES.
  • CONFIG_INSTANCE_* addresses instance 0: CONFIG_INSTANCE_NAME equals CONFIG_INSTANCES_0_NAME. Metrics index 0 may also be omitted: CONFIG_INSTANCES_0_METRICS_URL equals CONFIG_INSTANCES_0_METRICS_0_URL, and CONFIG_INSTANCE_METRICS_URL applies both shorthands. Do not set equivalent forms together.
  • Secrets accept a direct string or exactly one env: NAME / file: PATH reference. Secret overrides also accept _FILE.
  • Initial direct secret overrides are stored as environment references, never plaintext. Persisted secret references still require their environment variable.

Server, storage, UI, and updates

YAML fieldDefaultPurposeEnvironment override
server.port3000HTTP listen port.CONFIG_SERVER_PORT
server.basePath""Optional URL prefix such as /crowdsec; no trailing slash.CONFIG_SERVER_BASE_PATH
storage.dataDir/app/dataSQLite database and persistent application state.CONFIG_STORAGE_DATA_DIR
storage.geonamesDir/app/geonames in Docker; ./geonames locallyLocal GeoNames snapshot used for location labels.CONFIG_STORAGE_GEONAMES_DIR
storage.walEnabledtrueEnables SQLite write-ahead logging. Set to false for filesystems that do not support WAL.CONFIG_STORAGE_WAL_ENABLED
storage.incrementalVacuumEnabledtrueUses bounded incremental vacuum while idle on compatible databases. Existing databases are not migrated automatically.CONFIG_STORAGE_INCREMENTAL_VACUUM_ENABLED
storage.journalSizeLimit128MiBRetained WAL size after checkpoints. This does not cap transactions; use unlimited to disable trimming. Ignored when WAL is disabled.CONFIG_STORAGE_JOURNAL_SIZE_LIMIT
ui.timeZonebrowserBrowser timezone or an IANA zone such as Europe/Berlin or UTC.CONFIG_UI_TIME_ZONE
ui.timeFormatbrowserClock format: browser, 12h, or 24h.CONFIG_UI_TIME_FORMAT
ui.dateFormatbrowserBrowser date format or a custom pattern such as dd/mm/yyyy, mm/dd/yyyy, or yyyy-MM-dd.CONFIG_UI_DATE_FORMAT
ui.readOnlyfalseHides management actions and rejects mutating API operations.CONFIG_UI_READ_ONLY
updates.enabledtrue in packaged imagesEnables the built-in update check.CONFIG_UPDATES_ENABLED

Date patterns require one day, month, and year token in any order: d/dd (day), m/mm (numeric month), mmm/mmmm (localized month name), and yy/yyyy (year). Punctuation and spaces are literal; wrap words in single quotes, for example d 'of' mmmm yyyy. Date patterns affect full dates and timestamps; compact chart labels keep their own display format. Set ui.timeFormat separately for a 12 or 24 hour clock.

Authentication

YAML fieldDefaultPurposeEnvironment override
auth.enabledautoEnables authentication; auto enables new databases while preserving migrated database state.CONFIG_AUTH_ENABLED
auth.sessionSecretGenerated and storedSigns sessions and encrypts saved authentication settings.CONFIG_AUTH_SESSION_SECRET or CONFIG_AUTH_SESSION_SECRET_FILE
auth.totpSecretsessionSecretEncrypts stored per-account TOTP seeds.CONFIG_AUTH_TOTP_SECRET or CONFIG_AUTH_TOTP_SECRET_FILE
auth.totpSeedUnsetOptional base32 fallback TOTP seed for the password user; minimum 26 characters.CONFIG_AUTH_TOTP_SEED or CONFIG_AUTH_TOTP_SEED_FILE
auth.oidc.issuerUrlUnsetOIDC provider issuer URL.CONFIG_AUTH_OIDC_ISSUER_URL
auth.oidc.clientIdUnsetOIDC client identifier.CONFIG_AUTH_OIDC_CLIENT_ID
auth.oidc.clientSecretUnsetOIDC client secret.CONFIG_AUTH_OIDC_CLIENT_SECRET or CONFIG_AUTH_OIDC_CLIENT_SECRET_FILE
auth.oidc.scopeopenid profile emailRequested OIDC scopes; must include openid.CONFIG_AUTH_OIDC_SCOPE
auth.oidc.groupsClaimgroupsClaim containing role-mapping groups.CONFIG_AUTH_OIDC_GROUPS_CLAIM
auth.oidc.adminGroups[]Groups granted administrator access.CONFIG_AUTH_OIDC_ADMIN_GROUPS or CONFIG_AUTH_OIDC_ADMIN_GROUPS_<INDEX>
auth.oidc.readOnlyGroups[]Groups granted read-only access.CONFIG_AUTH_OIDC_READ_ONLY_GROUPS or CONFIG_AUTH_OIDC_READ_ONLY_GROUPS_<INDEX>
auth.oidc.unmatchedRoledenyRole for unmatched OIDC users: deny, admin, or read-only.CONFIG_AUTH_OIDC_UNMATCHED_ROLE

Notifications

YAML fieldDefaultPurposeEnvironment override
notifications.secretKeyGenerated and storedEncrypts saved notification credentials.CONFIG_NOTIFICATIONS_SECRET_KEY or CONFIG_NOTIFICATIONS_SECRET_KEY_FILE
notifications.allowPrivateAddressestrueAllows private, loopback, and link-local notification destinations.CONFIG_NOTIFICATIONS_ALLOW_PRIVATE_ADDRESSES
notifications.debugPayloadsfalseLogs truncated rendered payloads after failed notification delivery.CONFIG_NOTIFICATIONS_DEBUG_PAYLOADS

Audit log

User actions that change CrowdSec state (adding decisions, deleting decisions and alerts, and per-IP cleanup) are recorded as single [audit] lines in the application log. Each entry is a JSON object with the timestamp, acting user and role, action, target (IP, range, or entity IDs), and outcome:

[audit] {"time":"2026-08-17T01:30:00.000Z","user":"tommy","role":"admin","action":"decision.add","ip":"192.0.2.10","type":"ban","duration":"4h","reason":"manual","instances":["CrowdSec"],"outcome":"success"}

Outcomes are success, partial, failure, or queued. Alert deletions handled by the persistent deletion queue are recorded as queued, because CrowdSec may complete them after the HTTP request returns. Multi-target actions include target_results or instance_results so successful, failed, and queued targets remain distinguishable.

YAML fieldDefaultPurposeEnvironment override
audit.enabledtrueWrites audit entries for user actions to the application log.CONFIG_AUDIT_ENABLED
audit.logFileUnsetAlso appends audit entries as JSON lines to this file.CONFIG_AUDIT_LOG_FILE

Alert handling

Omitting crowdsec.alertFilters uses the standard non-CAPI feed. Setting any explicit filter field enables explicit filtering.

YAML fieldDefaultPurposeEnvironment override
crowdsec.simulationsEnabledfalseIncludes simulation-mode alerts and decisions.CONFIG_CROWDSEC_SIMULATIONS_ENABLED
crowdsec.alertFilters.includeOrigins[]Keeps alerts matching these exact origins.CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_ORIGINS or CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_ORIGINS_<INDEX>
crowdsec.alertFilters.excludeOrigins[]Drops alerts matching these exact origins.CONFIG_CROWDSEC_ALERT_FILTERS_EXCLUDE_ORIGINS or CONFIG_CROWDSEC_ALERT_FILTERS_EXCLUDE_ORIGINS_<INDEX>
crowdsec.alertFilters.includeCapifalseAdds the Central API/community-blocklist feed.CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_CAPI
crowdsec.alertFilters.includeOriginEmptyfalseKeeps empty-origin alerts with explicit include filters.CONFIG_CROWDSEC_ALERT_FILTERS_INCLUDE_ORIGIN_EMPTY
crowdsec.alertFilters.excludeOriginEmptyfalseDrops alerts whose effective origin is empty.CONFIG_CROWDSEC_ALERT_FILTERS_EXCLUDE_ORIGIN_EMPTY
crowdsec.alertFilters.legacy.origins[]Compatibility origin allowlist; CAPI enables the CAPI feed.CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_ORIGINS or CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_ORIGINS_<INDEX>
crowdsec.alertFilters.legacy.extraScenarios[]Compatibility list of additional scenarios.CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_EXTRA_SCENARIOS or CONFIG_CROWDSEC_ALERT_FILTERS_LEGACY_EXTRA_SCENARIOS_<INDEX>

Global synchronization

Synchronization durations accept ms, s, m, h, or d, for example 500ms, 30s, 5m, or 7d. The lookback fields accept only m, h, or d.

YAML fieldDefaultPurposeEnvironment override
crowdsec.sync.lookback168hImported history and retention window.CONFIG_CROWDSEC_SYNC_LOOKBACK
crowdsec.sync.refreshInterval1mActive refresh cadence; 0 or manual disables scheduling.CONFIG_CROWDSEC_SYNC_REFRESH_INTERVAL
crowdsec.sync.manualRefreshEnabledfalseEnables manual refresh controls.CONFIG_CROWDSEC_SYNC_MANUAL_REFRESH_ENABLED
crowdsec.sync.idleRefreshInterval10mRefresh cadence while the application is idle; 0 disables it.CONFIG_CROWDSEC_SYNC_IDLE_REFRESH_INTERVAL
crowdsec.sync.idleThreshold2mInactivity before idle refresh behavior begins.CONFIG_CROWDSEC_SYNC_IDLE_THRESHOLD
crowdsec.sync.requestTimeout30sTimeout for individual LAPI requests.CONFIG_CROWDSEC_SYNC_REQUEST_TIMEOUT
crowdsec.sync.bouncerPropagationDelay15sGrace period before deleting alerts owned by expired decisions.CONFIG_CROWDSEC_SYNC_BOUNCER_PROPAGATION_DELAY
crowdsec.sync.deletionQueueMaxAge24hStops retrying failed queued deletions after this age; 0 disables the limit. Tombstones remain until the retention window passes.CONFIG_CROWDSEC_SYNC_DELETION_QUEUE_MAX_AGE
crowdsec.sync.metricsRequestTimeout5sDefault timeout for metrics endpoints.CONFIG_CROWDSEC_SYNC_METRICS_REQUEST_TIMEOUT
crowdsec.sync.heartbeatInterval30sCrowdSec machine heartbeat cadence; 0 disables it.CONFIG_CROWDSEC_SYNC_HEARTBEAT_INTERVAL
crowdsec.sync.alertSyncChunk12hHistorical import window size.CONFIG_CROWDSEC_SYNC_ALERT_SYNC_CHUNK
crowdsec.sync.alertSyncMinChunk15mMinimum retry window after a timed-out import.CONFIG_CROWDSEC_SYNC_ALERT_SYNC_MIN_CHUNK
crowdsec.sync.reconcileWindow1hFixed alert-history reconciliation window size.CONFIG_CROWDSEC_SYNC_RECONCILE_WINDOW
crowdsec.sync.reconcileRecentAge24hBoundary between recent and older windows.CONFIG_CROWDSEC_SYNC_RECONCILE_RECENT_AGE
crowdsec.sync.reconcileRecentInterval15mReconciliation cadence for recent windows.CONFIG_CROWDSEC_SYNC_RECONCILE_RECENT_INTERVAL
crowdsec.sync.reconcileActiveInterval5mReconciliation cadence for windows with active decisions.CONFIG_CROWDSEC_SYNC_RECONCILE_ACTIVE_INTERVAL
crowdsec.sync.reconcileOldInterval3hReconciliation cadence for older windows.CONFIG_CROWDSEC_SYNC_RECONCILE_OLD_INTERVAL
crowdsec.sync.reconcileWindowsPerRefresh2Maximum due windows processed per refresh.CONFIG_CROWDSEC_SYNC_RECONCILE_WINDOWS_PER_REFRESH
crowdsec.sync.bootstrapRetryDelay30sDelay between failed initial-sync retries; 0 retries immediately.CONFIG_CROWDSEC_SYNC_BOOTSTRAP_RETRY_DELAY
crowdsec.sync.bootstrapRetryEnabledtrueEnables background retry after initial synchronization failure.CONFIG_CROWDSEC_SYNC_BOOTSTRAP_RETRY_ENABLED

Instances and LAPI

  • <INDEX> is zero-based.
  • ID defaults to the index; name defaults to Instance <INDEX>; authentication type is inferred from credentials.
  • Inferred values appear as comments in initial YAML unless compatibility requires an explicit identity.
  • Explicit IDs use lowercase letters, digits, _, and -. Keep them stable after importing data.

[!IMPORTANT] Configure exactly one credential shape:

  • Password auth: set username and password
  • mTLS auth: set certFile and keyFile

type is optional and inferred from these fields. Set it explicitly to none, password, or mtls when desired. Do not mix password and mTLS credentials.

Plaintext secrets are supported, but mounted secret files are recommended so credentials do not end up in source control, backups, or configuration-management logs.

YAML fieldDefaultPurposeEnvironment override
instancesOne generated default instanceConfigures one or more CrowdSec connections.CONFIG_INSTANCES or CONFIG_INSTANCES_<INDEX>_*
instances[].idZero-based instance indexStable database identity for the instance.CONFIG_INSTANCES_<INDEX>_ID
instances[].nameInstance <INDEX>Unique display name.CONFIG_INSTANCES_<INDEX>_NAME
instances[].iconUnsetOptional short text or emoji shown in the selector.CONFIG_INSTANCES_<INDEX>_ICON
instances[].lapiRequiredComplete LAPI connection object.CONFIG_INSTANCES_<INDEX>_LAPI
instances[].lapi.urlRequired (http://crowdsec:8080 in starter config)Absolute HTTP(S) LAPI base URL without credentials, a path, or a fragment.CONFIG_INSTANCES_<INDEX>_LAPI_URL
instances[].lapi.authtype: noneLAPI authentication object.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH
instances[].lapi.auth.typeInferred from credentialsOptional authentication mode: none, password, or mtls.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_TYPE
instances[].lapi.auth.usernameRequired for passwordCrowdSec machine username.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_USERNAME
instances[].lapi.auth.passwordRequired for passwordCrowdSec machine password or secret reference.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_PASSWORD or CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_PASSWORD_FILE
instances[].lapi.auth.certFileRequired for mtlsClient certificate path.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_CERT_FILE
instances[].lapi.auth.keyFileRequired for mtlsClient private-key path.CONFIG_INSTANCES_<INDEX>_LAPI_AUTH_KEY_FILE
instances[].lapi.tlsEmpty mappingLAPI server-trust settings.CONFIG_INSTANCES_<INDEX>_LAPI_TLS
instances[].lapi.tls.caFileUnsetCA bundle used to verify the LAPI server.CONFIG_INSTANCES_<INDEX>_LAPI_TLS_CA_FILE
instances[].metrics[]Zero or more metrics endpoints.CONFIG_INSTANCES_<INDEX>_METRICS or CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_*
instances[].syncInherits global valuesPer-instance synchronization overrides.CONFIG_INSTANCES_<INDEX>_SYNC or CONFIG_INSTANCES_<INDEX>_SYNC_*

Metrics endpoints

  • <INDEX> selects the instance; zero-based <METRIC_INDEX> selects its endpoint.
  • Endpoint ID defaults to <METRIC_INDEX> and name to Metrics <METRIC_INDEX>.
  • Endpoint icons are optional short text or emoji values and appear in the metrics source selector.
  • Inferred values appear as comments in initial YAML.
YAML fieldDefaultPurposeEnvironment override
instances[].metrics[].idZero-based metrics indexStable identifier unique within the instance.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_ID
instances[].metrics[].nameMetrics <METRIC_INDEX>Display name.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_NAME
instances[].metrics[].iconUnsetOptional short text or emoji icon.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_ICON
instances[].metrics[].urlRequiredAbsolute HTTP(S) Prometheus endpoint URL.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_URL
instances[].metrics[].requestTimeoutGlobal 5sRequest timeout for this endpoint.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_REQUEST_TIMEOUT
instances[].metrics[].authtype: noneComplete metrics authentication object.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH
instances[].metrics[].auth.typeInferred from credentialsOptional authentication mode: none, basic, or bearer.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_TYPE
instances[].metrics[].auth.usernameRequired for basicBasic-auth username.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_USERNAME
instances[].metrics[].auth.passwordRequired for basicBasic-auth password or secret reference.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_PASSWORD or CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_PASSWORD_FILE
instances[].metrics[].auth.tokenRequired for bearerBearer token or secret reference.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_TOKEN or CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_AUTH_TOKEN_FILE
instances[].metrics[].tlsEmpty mappingMetrics TLS settings.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS
instances[].metrics[].tls.caFileUnsetCA bundle used to verify the metrics server.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS_CA_FILE
instances[].metrics[].tls.certFileUnsetOptional metrics client certificate; requires keyFile.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS_CERT_FILE
instances[].metrics[].tls.keyFileUnsetOptional metrics client private key; requires certFile.CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_TLS_KEY_FILE

Per-instance synchronization overrides

Every field inherits its corresponding global value when omitted.

YAML fieldDefaultPurposeEnvironment override
instances[].sync.lookbackGlobal 168hHistory and retention window for this instance.CONFIG_INSTANCES_<INDEX>_SYNC_LOOKBACK
instances[].sync.refreshIntervalGlobal 1mActive refresh cadence.CONFIG_INSTANCES_<INDEX>_SYNC_REFRESH_INTERVAL
instances[].sync.idleRefreshIntervalGlobal 10mIdle refresh cadence.CONFIG_INSTANCES_<INDEX>_SYNC_IDLE_REFRESH_INTERVAL
instances[].sync.idleThresholdGlobal 2mTime before this instance is considered idle.CONFIG_INSTANCES_<INDEX>_SYNC_IDLE_THRESHOLD
instances[].sync.requestTimeoutGlobal 30sLAPI request timeout.CONFIG_INSTANCES_<INDEX>_SYNC_REQUEST_TIMEOUT
instances[].sync.heartbeatIntervalGlobal 30sMachine heartbeat cadence.CONFIG_INSTANCES_<INDEX>_SYNC_HEARTBEAT_INTERVAL
instances[].sync.alertSyncChunkGlobal 12hHistorical import window size.CONFIG_INSTANCES_<INDEX>_SYNC_ALERT_SYNC_CHUNK
instances[].sync.alertSyncMinChunkGlobal 15mMinimum retry window.CONFIG_INSTANCES_<INDEX>_SYNC_ALERT_SYNC_MIN_CHUNK
instances[].sync.reconcileWindowGlobal 1hReconciliation window size.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_WINDOW
instances[].sync.reconcileRecentAgeGlobal 24hRecent-window age boundary.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_RECENT_AGE
instances[].sync.reconcileRecentIntervalGlobal 15mRecent-window reconciliation cadence.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_RECENT_INTERVAL
instances[].sync.reconcileActiveIntervalGlobal 5mActive-decision reconciliation cadence.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_ACTIVE_INTERVAL
instances[].sync.reconcileOldIntervalGlobal 3hOlder-window reconciliation cadence.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_OLD_INTERVAL
instances[].sync.reconcileWindowsPerRefreshGlobal 2Due-window budget per refresh.CONFIG_INSTANCES_<INDEX>_SYNC_RECONCILE_WINDOWS_PER_REFRESH
instances[].sync.bootstrapRetryDelayGlobal 30sInitial-sync retry delay.CONFIG_INSTANCES_<INDEX>_SYNC_BOOTSTRAP_RETRY_DELAY
instances[].sync.bootstrapRetryEnabledGlobal trueEnables background initial-sync retry.CONFIG_INSTANCES_<INDEX>_SYNC_BOOTSTRAP_RETRY_ENABLED
instances[].sync.bouncerPropagationDelayGlobal 15sAlert-deletion grace period.CONFIG_INSTANCES_<INDEX>_SYNC_BOUNCER_PROPAGATION_DELAY

Multiple CrowdSec instances

Use zero-based CONFIG_INSTANCES_<INDEX>_* overrides to define each instance. Indexes must be contiguous, starting at 0.

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    environment:
      CONFIG_INSTANCES_0_ID: eu-prod
      CONFIG_INSTANCES_0_NAME: EU Production
      CONFIG_INSTANCES_0_LAPI_URL: http://crowdsec-eu:8080
      CONFIG_INSTANCES_0_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCES_0_LAPI_AUTH_PASSWORD_FILE: /run/secrets/eu-lapi-password
      CONFIG_INSTANCES_0_METRICS_0_ID: lapi
      CONFIG_INSTANCES_0_METRICS_0_NAME: EU LAPI
      CONFIG_INSTANCES_0_METRICS_0_URL: http://crowdsec-eu:6060/metrics

      CONFIG_INSTANCES_1_ID: us-prod
      CONFIG_INSTANCES_1_NAME: US Production
      CONFIG_INSTANCES_1_LAPI_URL: http://crowdsec-us:8080
      CONFIG_INSTANCES_1_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCES_1_LAPI_AUTH_PASSWORD_FILE: /run/secrets/us-lapi-password

      # For mTLS, replace instance 1's URL and password credentials above with:
      # CONFIG_INSTANCES_1_LAPI_URL: https://crowdsec-us:8080
      # CONFIG_INSTANCES_1_LAPI_AUTH_TYPE: mtls
      # CONFIG_INSTANCES_1_LAPI_AUTH_CERT_FILE: /certs/us-client-cert.pem
      # CONFIG_INSTANCES_1_LAPI_AUTH_KEY_FILE: /run/secrets/us-client-key.pem
      # CONFIG_INSTANCES_1_LAPI_TLS_CA_FILE: /certs/us-ca.pem
    volumes:
      - ./secrets/eu-lapi-password:/run/secrets/eu-lapi-password:ro
      - ./secrets/us-lapi-password:/run/secrets/us-lapi-password:ro
      # Mount these files when using the commented mTLS configuration:
      # - ./certs/us-client-cert.pem:/certs/us-client-cert.pem:ro
      # - ./secrets/us-client-key.pem:/run/secrets/us-client-key.pem:ro
      # - ./certs/us-ca.pem:/certs/us-ca.pem:ro
  • Use indexed variables for every instance in a multi-instance setup; reserve the CONFIG_INSTANCE_* shorthand for single-instance deployments.
  • Each instance needs a stable ID, unique display name, LAPI URL, and one authentication method.
  • Add metrics endpoints with CONFIG_INSTANCES_<INDEX>_METRICS_<METRIC_INDEX>_*.
  • Mount password files read-only. When using the optional mTLS configuration, mount its private keys and certificates read-only as well, then restart the container.

YAML alternative

Add entries to the top-level instances array. Each entry defines one LAPI connection and zero or more metrics endpoints.

instances:
  - id: eu-prod
    name: EU Production
    icon: 🇪🇺
    lapi:
      url: http://crowdsec-eu:8080
      auth:
        type: password
        username: crowdsec-web-ui
        password:
          file: /run/secrets/eu-lapi-password

    metrics:
      - id: lapi
        name: EU LAPI
        icon: 🧠
        url: http://crowdsec-eu:6060/metrics
        auth:
          type: bearer
          token:
            file: /run/secrets/eu-metrics-token

      - id: edge-engine
        name: EU Edge Engine
        icon: 🛡️
        url: http://crowdsec-edge:6060/metrics

    sync:
      requestTimeout: 45s
      alertSyncChunk: 6h

  - id: us-prod
    name: US Production
    icon: 🇺🇸
    lapi:
      url: http://crowdsec-us:8080
      auth:
        type: password
        username: crowdsec-web-ui
        password:
          file: /run/secrets/us-lapi-password
      # For mTLS, replace url and auth above with:
      # url: https://crowdsec-us:8080
      # auth:
      #   type: mtls
      #   certFile: /run/secrets/us-client-cert
      #   keyFile: /run/secrets/us-client-key
      # tls:
      #   caFile: /run/secrets/us-ca

Configuration rules

  • Instance and endpoint IDs are unique, URL-safe, and immutable database identities. They are 1–63 characters long, start with a lowercase letter or digit, use only lowercase letters, digits, _, or -, and must never be reused for another LAPI.
  • Display names are unique but editable. icon accepts up to eight Unicode code points of text or emoji without control characters; omitted icons use colored squares and Combined uses a grid.
  • Password secrets accept a direct value or exactly one env/file source. mTLS requires both certFile and keyFile; tls.caFile controls server trust.
  • Metrics authentication supports none, basic, and bearer; metrics TLS supports caFile plus an optional complete client certificate/key pair.
  • Embedded URL credentials, URL fragments, ambiguous secret sources, partial certificate pairs, unreadable files, and TLS verification bypasses fail validation. LAPI base URLs also reject paths.
  • Prefer mounted secret files. Restart after configuration, certificate, or secret changes.

Multi-instance behavior

AreaBehavior
Dashboard, Alerts, DecisionsSupport one instance or Combined scope
MetricsDefaults to a source-aware Combined view when the selected scope has multiple endpoints; individual endpoints remain selectable
Add decision / clean IPRuns against every LAPI in Combined scope and reports partial failures
Row deletionUses the row's owning instance; numeric upstream IDs are never broadcast

Authentication

Authentication covers the browser UI and protected APIs; /api/health remains public.

auth.enabledBehavior
autoEnables authentication for new databases; preserves the state of migrated databases
trueRequires authentication and initial administrator setup
falseDisables authentication; this deployment setting is not available in the UI

Upgraded installations

Enable authentication explicitly on installations migrated from older releases.

auth:
  enabled: true

Local accounts

  • Password changes and passkey registration/removal from Settings.
  • Optional TOTP enrollment through a QR code, mobile setup link, or manual key.
  • An enrolled TOTP seed overrides the optional base32 auth.totpSeed fallback.
  • Administrators can disable password login.

OIDC

Configure OIDC in Settings or YAML.

auth:
  enabled: true
  oidc:
    issuerUrl: https://idp.example.com/application/o/crowdsec-web-ui/
    clientId: crowdsec-web-ui
    clientSecret:
      file: /run/secrets/oidc_client_secret
    scope: openid profile email
    groupsClaim: groups
    adminGroups: [crowdsec-admins, secops]
    readOnlyGroups: [crowdsec-viewers]
    unmatchedRole: deny

Callback URL

Register this callback URI with the identity provider.

https://<crowdsec-web-ui-host>/api/auth/oidc/callback

Requirements and roles

  • The callback must exactly match the public scheme, host, port, and base path. For basePath: /crowdsec, use https://<host>/crowdsec/api/auth/oidc/callback.
  • Reverse proxies must forward Host or X-Forwarded-Host and X-Forwarded-Proto.
  • Saved Settings override YAML. Scopes must include openid; add provider-specific scopes such as groups only when required.
  • Admin-group matches have full access; read-only-group matches can view data and keep permitted preferences; unmatched users follow auth.oidc.unmatchedRole (deny by default).
  • Set an unmatched fallback role only when every user who can sign in should receive it.
  • ui.readOnly: true overrides all roles for the deployment. It blocks CrowdSec writes, refresh changes, notification destination/rule management, test sends, and notification deletion. Language changes and marking notifications read remain available. This is not per-user RBAC.
  • Identities use stable issuer and subject claims. Username collisions with local accounts remain separate.
  • Sessions have a 24-hour absolute lifetime. OIDC-only users cannot add local passkeys; password-backed local accounts retain passkey support.
  • Existing OIDC rows migrate on their next successful SSO login.

Deployment and Security

Trusted IPs for Alert Deletion

[!IMPORTANT] CrowdSec only permits alert deletion when the request comes from loopback or an address listed in api.server.trusted_ips. Registering the Web UI as a CrowdSec machine authenticates it, but does not grant this IP-based permission.

When the Web UI runs in Docker, add the Web UI container's source IP or, preferably, its Docker network CIDR to CrowdSec's /etc/crowdsec/config.yaml. This is not the browser's IP or the Docker host's public IP. Without this entry, decision operations can still work while alert deletion fails with 403 Forbidden.

api:
  server:
    trusted_ips:
      - 127.0.0.1
      - ::1
      - 172.16.0.0/12  # Docker default bridge network

Use the narrowest CIDR that contains the Web UI container and LAPI network. Container IPs can change when containers are recreated, so the Docker network CIDR is usually more reliable than one container IP. Restart CrowdSec after updating the file. The current CrowdSec container does not provide a TRUSTED_IPS environment override. See the CrowdSec configuration reference.

Local or Custom LAPI Certificate

A self-signed certificate or internal CA may produce the following error.

Login failed: unable to get local issuer certificate

Mount the CA certificate and configure it for the LAPI instance.

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    container_name: crowdsec_web_ui
    ports:
      - "3000:3000"
    environment:
      CONFIG_INSTANCE_LAPI_URL: https://crowdsec:8080
      CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCE_LAPI_AUTH_PASSWORD_FILE: /run/secrets/crowdsec_password
      CONFIG_INSTANCE_LAPI_TLS_CA_FILE: /certs/root_ca.crt
    secrets:
      - crowdsec_password
    volumes:
      - ./data:/app/data
      - /path/on/host/root_ca.crt:/certs/root_ca.crt:ro
    restart: unless-stopped

secrets:
  crowdsec_password:
    file: ./secrets/crowdsec_password.txt

Keep the CA mount read-only. CONFIG_INSTANCE_LAPI_TLS_CA_FILE maps to instances[0].lapi.tls.caFile; no image rebuild is needed.

HTTPS Reverse Proxy

CrowdSec Web UI listens for HTTP on port 3000 and does not obtain or terminate TLS certificates itself. Put a reverse proxy in front of it for HTTPS deployments and keep port 3000 private to the proxy.

HTTPS is required for passkeys because browsers expose WebAuthn only in a secure context. http://localhost is suitable for local testing, but a remote deployment needs a stable hostname and a certificate trusted by the browser. Passkeys registered for one hostname cannot be used from a different hostname.

The proxy must preserve Host (or set X-Forwarded-Host) and set X-Forwarded-Proto to the original scheme. The application uses these headers for WebAuthn origins, secure session cookies, OIDC callback URLs, and mutation-origin checks.

Traefik example

This minimal example assumes Traefik already has a websecure entrypoint, a Let's Encrypt resolver named letsencrypt, and an external Docker network named proxy. Add the labels and proxy network to the existing Web UI service, replacing the hostname and CrowdSec settings.

services:
  crowdsec-web-ui:
    image: ghcr.io/theduffman85/crowdsec-web-ui:latest
    expose:
      - "3000"
    environment:
      CONFIG_INSTANCE_LAPI_URL: http://crowdsec:8080
      CONFIG_INSTANCE_LAPI_AUTH_USERNAME: crowdsec-web-ui
      CONFIG_INSTANCE_LAPI_AUTH_PASSWORD: your-crowdsec-password
      # For https://crowdsec.example.com/crowdsec/:
      # CONFIG_SERVER_BASE_PATH: /crowdsec
    volumes:
      - ./data:/app/data
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      # For /crowdsec/, append: && PathPrefix(`/crowdsec`)
      - 'traefik.http.routers.crowdsec-web-ui.rule=Host(`crowdsec.example.com`)'
      - traefik.http.routers.crowdsec-web-ui.entrypoints=websecure
      - traefik.http.routers.crowdsec-web-ui.tls.certresolver=letsencrypt
      - traefik.http.services.crowdsec-web-ui.loadbalancer.server.port=3000
    networks:
      - proxy
      - crowdsec
    restart: unless-stopped

networks:
  proxy:
    external: true
  crowdsec:
    external: true
    name: your_crowdsec_network

Traefik supplies the forwarded headers and WebSocket upgrade handling automatically. See the Traefik ACME documentation if the letsencrypt resolver is not configured yet.

Nginx example

This equivalent example assumes Nginx already terminates HTTPS and the Web UI port is published only on loopback, for example 127.0.0.1:3000:3000.

# For https://crowdsec.example.com/crowdsec/, set
# CONFIG_SERVER_BASE_PATH=/crowdsec and replace both `/` paths below
# with `/crowdsec/`.
location / {
    proxy_pass http://localhost:3000/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Proxy requirements

  • The base path starts with / and has no trailing slash.
  • / redirects to it; APIs, assets, and navigation follow it automatically.
  • With Traefik on a shared hostname, add CONFIG_SERVER_BASE_PATH: /crowdsec and use a router rule such as Host(`example.com`) && PathPrefix(`/crowdsec`). Do not configure StripPrefix; the application expects to receive the base path.
  • The backend checks browser mutation origins, applies a Content Security Policy, limits API bodies to 1 MiB, and marks API responses private, no-store.
  • Command-line and service clients without browser Origin and Sec-Fetch-Site headers remain compatible.
  • Configure HSTS at the TLS-terminating proxy; the application does not emit it.

Health Check

The public endpoint is GET /api/health. Startup does not wait for LAPI: bootstrap retries in the background, so the container can become healthy before synchronization completes.

curl http://localhost:3000/api/health
# {"status":"ok"}

The built-in check runs every 30 seconds with a five-second timeout, a 10-second start period, and three retries.

docker inspect --format='{{.State.Health.Status}}' crowdsec_web_ui

server.basePath does not affect the internal check at localhost:3000/api/health. If server.port changes, update the health-check command and port mapping.

Runtime Behavior

Prometheus Metrics Page

  • Reads a configured raw CrowdSec Prometheus endpoint. The Web UI does not configure one by default; CrowdSec normally exposes its local scrape at http://127.0.0.1:6060/metrics.
  • Shows remediation-component and log-processor LAPI activity, AppSec, parsers and datasources, scenarios, LAPI latency, parsing time, and whitelist hits. Log-processor activity uses POST /v1/alerts; CrowdSec builds that expose an exact last-heartbeat timestamp also provide the processor health badge.
  • When the current instance scope has multiple endpoints, Combined is selected by default. Headline totals sum successful sources, detailed rows retain their instance and endpoint, and unavailable endpoints produce a partial-data warning instead of hiding healthy sources.
  • Combined counters are cumulative across processes that may have different start times. Configuring the same Prometheus scrape more than once counts it more than once; endpoint roles and automatic duplicate detection are not applied.
  • Alert and decision analytics remain on the main dashboard.

Enable full metrics in CrowdSec's /etc/crowdsec/config.yaml.

prometheus:
  enabled: true
  level: full
  listen_addr: 127.0.0.1
  listen_port: 6060

For separate containers, bind listen_addr: 0.0.0.0 on a trusted network, then configure the matching Web UI instance.

environment:
  CONFIG_INSTANCE_METRICS_URL: http://crowdsec:6060/metrics
CrowdSec levelResult
fullAll supported details, including per-machine, per-bouncer, and per-node metrics
aggregatedLess detail; omits those per-entity metrics
noneDisables metrics registration

AppSec and latency sections appear only when CrowdSec emits those metrics. Time-window-only rate()/increase() metrics are intentionally omitted. See the CrowdSec Prometheus documentation.

Display Preferences

  • crowdsec.simulationsEnabled: true fetches non-remediating simulation alerts/decisions and shows badges, filters, and dashboard counts. Default: false.
  • Alerts and Decisions column layouts persist per browser profile in local storage.
  • ID, Machine, and Origin are hidden by default. Machine prefers machine_alias, then machine_id; multiple alert decision origins display as Mixed.
  • Hidden columns remain searchable through fields such as id:, machine:, and origin:.

Quick Filters

Dashboard, Alerts, and Decisions share one count-aware Quick Filters drawer. Its trigger shows the number of active selections, and the clear control in the drawer header resets all stored quick filters.

Instance and machine options use stable IDs for filtering while displaying their configured names or aliases. Country, instance, and machine searches match the displayed label as well as the stored value. Alerts with several origins or targets contribute to each distinct facet option instead of exposing a combined bucket.

  • Filter selections, date range, and simulation mode persist in local storage for the current browser profile and are restored when navigating between the three pages.
  • Changing a structured search field updates the matching quick-filter selection, and changing a quick filter updates the structured search. A field supplied explicitly in a page URL takes precedence over its stored value; stored values fill fields that are absent.
  • Dashboard top countries, scenarios, AS numbers, targets, the world map, and the Activity History range update the same shared state. Changes made in the drawer update those widgets in return.
  • Filtered Dashboard alert and active-decision counts use the same indexed predicates as the Alerts and Decisions lists. The alert card links with the alert-compatible query, while the decision card links with the decision-compatible query.
  • The drawer follows each table's configured column order. Filters for hidden columns are grouped under Hidden columns.
  • Page-only filters remain visible under Unavailable when another page cannot apply them. Alerts lists Action, Status, and Alert there; Decisions lists Decision; Dashboard lists all four. Their stored selections are preserved for the page that supports them and can be cleared from any drawer.
  • Facet selections use exact equality. Selecting the target tausend.me generates target=tausend.me and does not include bw.tausend.me. Manually entering target:tausend.me remains a broader contains search.
  • Each facet reports counts after the other active filters have been applied. Use the facet search control to find values beyond the initially loaded list.

Dashboard applies the shared fields Country, Scenario, Kind, AS, IP / Range, Target, ID, Instance, Region, City, Machine, and Origin. Filters that depend on decision-only or alert-list-only data are retained in Unavailable instead of being silently discarded.

Active decisions are deduplicated by instance, value, and simulation mode. When filters exclude the globally preferred decision, the best matching decision is promoted so enabling Hide duplicates cannot make an otherwise matching duplicate group disappear.

Saved and Recent Filters

Use the bookmark button beside search on Dashboard, Alerts, or Decisions to save the current valid query under a name. The menu lets you apply, rename, and delete saved filters, reuse the five most recently used queries, or clear recent history. Queries are shared across these pages; Apply is disabled when a query is not valid for the current page. Applying one changes the search query while keeping the selected instance and other page settings.

Saved filters and recent history are stored on the server for the signed-in user, so they are available on other devices. Read-only users can manage their own filters. If authentication is disabled, everyone using the installation shares one list. Valid nonempty queries enter recent history after a brief pause, including searches opened from bookmarked URLs. Quick Filter queries enter recent history only after the Quick Filters drawer closes.

Search Syntax

SyntaxExample
Free text / quoted phrasessh hetzner, "nginx bf"
Field contains / exact valuecountry:germany, country=DE
Date comparisondate>=2026-03-24, date<2026-03-25T12:00:00Z
Negative / empty-sim:simulated, sim<>simulated, origin:"", origin<>""
Boolean / groupingcountry:(germany OR france) AND -sim:simulated
Decision filtersstatus:active AND action:ban, alert:123 OR ip:"192.168.5.0/24"

The : operator performs a case-insensitive contains match, while = matches the complete field value. Quick Filter selections use =. Date fields support <, >, <=, >=, and =>. A bare field name is free text unless followed by :. Quote literal AND, OR, or NOT. The search Info button lists page-specific fields and examples.

Examples

PageQuery
Alertscountry:germany ssh
Alertsdate>=2026-03-24 AND date<2026-03-25
Alertscountry:(germany OR france) AND -sim:simulated
Alertskind=waf
Alertsorigin:""
Decisionsstatus:active AND action:ban
Decisionsdate>=2026-03-24 AND action:ban
Decisionsalert:123 OR ip:"192.168.5.0/24"
Decisionskind=waf

Alert Source Filtering

Limit the local cache by origin when CrowdSec ingests automation, blocklists, or community feeds.

crowdsec:
  alertFilters:
    includeOrigins: [crowdsec, cscli-import]
    excludeOrigins: [cscli]
    includeCapi: true
    includeOriginEmpty: true
    excludeOriginEmpty: false
OriginSource
crowdsecSecurity-engine decisions
cscliManual cscli decisions add
cscli-importcscli decisions import
listsImported list feeds
CAPICentral API / community blocklist

Behavior

  • No explicit filters fetches the normal non-CAPI/non-lists feed.
  • Includes are pushed upstream where possible. Generic excludes and empty-origin handling run locally because LAPI lacks those filters.
  • includeCapi: true adds CAPI to the default feed; includeOrigins: [CAPI] selects only CAPI.
  • If any origin is excluded, the whole alert is dropped.
  • Origins prefer associated decisions, then blocklist/list source scopes for alerts without decisions.
  • includeOriginEmpty retains origin-less alerts alongside includes; excludeOriginEmpty removes them.
  • Because Decisions is built from synchronized alerts, filters also change which imported decisions appear.

Examples

SettingResult
includeOrigins: [crowdsec]Keeps security-engine alerts only
includeOrigins: [lists]Keeps list-based alerts only
includeCapi: trueAdds CAPI to the default feed
includeOrigins: [CAPI]Keeps CAPI alerts only
includeOriginEmpty: trueKeeps origin-less alerts alongside explicit includes
excludeOriginEmpty: trueRemoves origin-less alerts
excludeOrigins: [cscli, lists]Removes manual and imported-list alerts

Notifications

Rules run against locally cached CrowdSec data, create in-app notifications, record delivery status, and optionally deliver outbound messages.

Rules

Every rule has a name, severity (info, warning, critical), incident deduplication, and destination channels. Alert rules filter scenario, target, and simulation state; the scenario filter can include or exclude matching names. IP Ban and New Alert/Decision also accept exact IP/CIDR filters. Window Minutes sets the rolling lookback for each rule evaluation; Alert Spike also compares it with the preceding period of equal length. It does not set how often rules are evaluated.

Rule typeBehavior
Alert SpikeCompares the current window with the previous window and triggers when percentage increase and minimum alert count are exceeded.
Alert ThresholdTriggers when matching alerts in the configured time window reach the threshold.
New Alert/DecisionCreates one notification for every matching alert, decision, or both within the lookback window. Includes record ID, timestamps, scenario, target, source/value, and related alert/decision details. Stable per-record deduplication prevents repeats.
IP BanTriggers once for each active ban decision in the configured window, supports exact IP/CIDR filters, and deduplicates duplicate active decision rows for the same ban.
Recent CVEExtracts CVE IDs from matching alerts and checks publication age before notifying.
LAPI AvailabilityTriggers when CrowdSec LAPI stays unavailable past the outage threshold, with optional recovery notifications.
Application UpdateUses the built-in update check and triggers when a newer CrowdSec Web UI version is available.

Multi-instance behavior

ScopeRules
Aggregate matching alerts across instancesAlert Spike, Alert Threshold, Recent CVE
Evaluate each matching recordNew Alert/Decision, IP Ban
Evaluate each instanceLAPI Availability
Evaluate each Prometheus endpointCrowdSec Update
Application-wideApplication Update

Instance-backed titles and metadata identify the contributing instance or instances.

[!NOTE] The Recent CVE rule queries the NVD API to determine when a CVE was published. If outbound access to services.nvd.nist.gov is blocked, recent-CVE notifications may be skipped.

The CrowdSec Update rule requires a configured Prometheus endpoint with a cs_info version metric and outbound access to the CrowdSec GitHub release API. It checks the latest stable release and notifies once per outdated endpoint and target version.

Destinations

Destinations are independently enabled and reusable across rules. Send Test validates saved settings immediately; results are stored as delivered or failed.

DestinationSettings
EmailSMTP host/port/security (Plain SMTP, STARTTLS, SMTPS / Implicit TLS), optional user/password, from address, comma-separated recipients, importance (auto, normal, important), and optional insecure TLS for trusted self-signed SMTP endpoints. Auto importance maps info to normal and warning/critical to important.
GotifyGotify URL, app token, and priority (auto or explicit integer). Auto priority maps info to 5, warning to 7, and critical to 10.
ntfyServer URL, topic, optional access token, and priority (auto, min, low, default, high, urgent). Auto priority maps info to default, warning to high, and critical to urgent.
MQTTGeneric publish-only output with broker URL, optional username/password/client ID, QoS 0 or 1, keepalive, connect timeout, topic, and retain flag. It does not include Home Assistant discovery, entity sync, or command handling.
WebhookCustom HTTP delivery with method (POST, PUT, PATCH), URL, optional query parameters/headers, auth (none, bearer token, or basic auth), body mode (JSON, Text, Form), timeout, retries, retry delay, and optional insecure TLS for trusted self-signed HTTPS endpoints.

Payloads and security

  • MQTT JSON contains title, message, severity, metadata, sent_at, channel_id, channel_name, channel_type, rule_id, rule_name, and rule_type. Tests use rule_id=test, rule_name=Test notification, and rule_type=test.
  • Webhook templates expose dotted event.* fields for title, message, severity, metadata, sent_at, channel_name, rule_id, rule_name, and rule_type. Each has a *Json variant; nullable rule fields also have OrUnknown and OrUnknownJson aliases.
  • Failed webhooks store HTTP status and a truncated response. notifications.debugPayloads: true also logs a truncated rendered body with sensitive form fields redacted; enable it only while troubleshooting.
  • Destination secrets are masked and encrypted by notifications.secretKey, or an auto-generated key stored in application metadata.
  • notifications.allowPrivateAddresses: false blocks private, loopback, and link-local destinations; default: true.
  • Telegram, Home Assistant discovery/state, and inbound MQTT commands are not supported.

Kubernetes

A Helm chart for CrowdSec Web UI is maintained by zekker6.

Persistence and Alert History

SQLite data lives under /app/data. Mount the directory—not only crowdsec.db—because WAL mode also uses crowdsec.db-wal and crowdsec.db-shm.

volumes:
  - ./data:/app/data
  • History survives restarts, merges with new LAPI data, and expires after crowdsec.sync.lookback (default: seven days).
  • Initial imports and reconciliation retry in smaller windows after timeouts.
  • During LAPI outages, the application serves its available cache and retries in the background; partial imports are marked.

Use POST /api/cache/clear for a full cache reset. Synchronization internals are documented in DEVELOPMENT.md.

SQLite database maintenance

New databases use SQLite incremental auto-vacuum by default. Existing databases keep their current format to avoid a potentially long, disk-intensive migration. If startup reports reclaimable space, migrate during a maintenance window:

  1. Back up the entire data directory, including crowdsec.db, crowdsec.db-wal, and crowdsec.db-shm when present.

  2. Stop CrowdSec Web UI and confirm that no process is using the database. Ensure free disk space is at least the current database size.

  3. Open crowdsec.db with SQLite and run:

    PRAGMA wal_checkpoint(TRUNCATE);
    PRAGMA auto_vacuum = INCREMENTAL;
    VACUUM;
    
  4. Close SQLite, restart the app, and verify the migration with PRAGMA auto_vacuum;. A result of 2 means incremental auto-vacuum is enabled.

VACUUM rewrites the database and may take significant time for large installations. Do not interrupt it. The application never runs this migration automatically.

Documentation

GuideContents
Configuration exampleComplete commented YAML configuration
API referenceAuthentication, routes, parameters, and request/response shapes
Development guideLocal setup, builds, tests, metadata, translations, and synchronization internals
Load testing guideSynthetic profiles, overrides, benchmarks, and container workflow

Star History

Star History Chart

Linux Update Dashboard Logo Linux Update Dashboard
A self-hosted dashboard for checking and applying Linux package updates across multiple servers.
crowdsec
crowdsec-lapi
crowdsec-manager
dashboard
docker
docker-compose
docker-container
gotify
metrics
mqtt
notifications
ntfy
self-hosted

Contributors

TheDuffman85

217 commits

Greite

1 commits

Languages

TypeScript

97.8%

JavaScript

1.1%