diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx
index 9fe51754..0b63ac65 100644
--- a/src/components/NavigationDocs.jsx
+++ b/src/components/NavigationDocs.jsx
@@ -550,6 +550,10 @@ export const docsNavigation = [
title: 'Management Geolocation Database',
href: '/selfhosted/geo-support',
},
+ {
+ title: 'CrowdSec IP Reputation',
+ href: '/selfhosted/maintenance/crowdsec',
+ },
],
},
{
diff --git a/src/pages/manage/reverse-proxy/access-logs.mdx b/src/pages/manage/reverse-proxy/access-logs.mdx
index a972ac2d..8c79736b 100644
--- a/src/pages/manage/reverse-proxy/access-logs.mdx
+++ b/src/pages/manage/reverse-proxy/access-logs.mdx
@@ -66,6 +66,14 @@ The following deny reasons can appear for both HTTP and L4 services:
| `ip_restricted` | The client IP was blocked by a CIDR access restriction |
| `country_restricted` | The client's country was blocked by a country access restriction |
| `geo_unavailable` | Country restrictions are configured but the GeoIP database is unavailable (fail-closed) |
+| `crowdsec_ban` | The client IP has a CrowdSec ban decision |
+| `crowdsec_captcha` | The client IP has a CrowdSec captcha decision |
+| `crowdsec_throttle` | The client IP has a CrowdSec throttle decision |
+| `crowdsec_unavailable` | CrowdSec enforce mode is active but the bouncer has not completed its initial sync (fail-closed) |
+
+All CrowdSec decision types (ban, captcha, throttle) result in a connection denial in enforce mode. The proxy does not serve captcha challenges or apply rate limiting: the decision type is recorded for informational purposes only.
+
+When CrowdSec is in **observe** mode, the verdict appears in the log metadata but the deny reason field is empty (the connection is allowed). This lets you audit what CrowdSec would block without affecting traffic.
## Use cases
diff --git a/src/pages/manage/reverse-proxy/authentication.mdx b/src/pages/manage/reverse-proxy/authentication.mdx
index e1ebd81b..55602621 100644
--- a/src/pages/manage/reverse-proxy/authentication.mdx
+++ b/src/pages/manage/reverse-proxy/authentication.mdx
@@ -131,9 +131,9 @@ Common combinations include:
## Access restrictions
-Access restrictions control which connections are allowed to reach your service based on the client's IP address or geographic location. Unlike authentication methods, access restrictions operate at the connection level and work for both HTTP and L4 services.
+Access restrictions control which connections are allowed to reach your service based on the client's IP address, geographic location, or IP reputation. Unlike authentication methods, access restrictions operate at the connection level and work for both HTTP and L4 services.
-Access restrictions are evaluated **before** authentication. If a connection is blocked by an IP or country rule, it is rejected immediately without any authentication check.
+Access restrictions are evaluated **before** authentication. If a connection is blocked by an access restriction rule, it is rejected immediately without any authentication check.
@@ -172,12 +172,30 @@ The evaluation logic mirrors CIDR restrictions: if an allowed list is present, t
GeoIP accuracy depends on the database quality and the client's IP address. VPN and proxy users may appear from a different country than their physical location.
+### CrowdSec IP reputation
+
+[CrowdSec](https://www.crowdsec.net) is an open-source security engine that maintains a community-curated database of known malicious IP addresses. When enabled, the proxy checks every incoming client IP against a local cache of CrowdSec decisions and blocks connections from flagged addresses.
+
+CrowdSec operates in one of three modes per service:
+
+| Mode | Behavior |
+|------|----------|
+| **Off** | CrowdSec checks are disabled for this service (default). |
+| **Enforce** | Connections from flagged IPs are denied immediately. If the CrowdSec bouncer has not completed its initial sync, all connections are denied (fail-closed). |
+| **Observe** | Connections from flagged IPs are logged in [access logs](/manage/reverse-proxy/access-logs) but not blocked. Use this to evaluate the impact before switching to enforce. |
+
+CrowdSec decisions include different remediation types (ban, captcha, throttle). The proxy treats all types as connection denials in enforce mode: there is no captcha challenge or rate limiting. The specific decision type is recorded in the [access logs](/manage/reverse-proxy/access-logs) so you can distinguish between them when reviewing traffic.
+
+
+CrowdSec is only available when the proxy cluster has CrowdSec configured. If the cluster does not support CrowdSec, the option will not appear in the Access Control tab. For self-hosted deployments, see the [CrowdSec setup guide](/selfhosted/maintenance/crowdsec) to enable it.
+
+
### Combining restrictions with authentication
Access restrictions and authentication methods are independent layers:
1. **Connection arrives** at the proxy.
-2. **Access restrictions** are evaluated first: IP CIDRs, then country. If the connection is blocked, it is rejected with no further processing.
+2. **Access restrictions** are evaluated first: IP CIDRs, then country, then CrowdSec. If the connection is blocked at any layer, it is rejected with no further processing.
3. **Authentication** is evaluated next (for HTTP services): SSO, password, PIN, or header auth.
4. If both layers pass, the request is forwarded to the backend.
@@ -250,12 +268,36 @@ Authentication and access control are configured in separate tabs of the service
4. To restrict by country:
- Select countries in the **Allowed Countries** field to create a country allowlist.
- Select countries in the **Blocked Countries** field to create a country blocklist.
-5. Click **Save** (or **Save Changes** when editing).
+5. To enable CrowdSec IP reputation (when available):
+ - Set the **CrowdSec IP Reputation** dropdown to **Enforce** or **Observe**.
+6. Click **Save** (or **Save Changes** when editing).
Access restrictions apply immediately to new connections. Existing connections that were established before the restriction was added are not affected until they reconnect.
+### Restriction evaluation order
+
+Access restrictions are evaluated as a pipeline. Each layer can only further restrict: a denial at any layer is final and short-circuits later layers. No layer can relax a denial from an earlier one.
+
+| Layer | What it checks | On deny |
+|-------|----------------|---------|
+| 1. CIDR | Allowlist/blocklist by IP range | Stops here, country and CrowdSec are skipped |
+| 2. Country | Allowlist/blocklist by geolocation | Stops here, CrowdSec is skipped |
+| 3. CrowdSec | IP reputation against decision cache | Blocks (enforce) or logs (observe) |
+
+**Examples:**
+
+| Config | Client IP | Result | Reason |
+|--------|-----------|--------|--------|
+| Allow `10.0.0.0/8` + CrowdSec enforce | `10.1.2.3` (CrowdSec banned) | Denied | `crowdsec_ban` |
+| Allow `10.0.0.0/8` + CrowdSec enforce | `10.2.3.4` (clean) | Allowed | Passed all layers |
+| Allow `10.0.0.0/8` + CrowdSec enforce | `192.168.1.1` | Denied | `ip_restricted` (CIDR deny, CrowdSec never runs) |
+| Block `10.1.0.0/16` + CrowdSec enforce | `10.1.2.3` (CrowdSec banned) | Denied | `ip_restricted` (CIDR deny takes precedence) |
+| Allow country US + CrowdSec enforce | `1.2.3.4` US (CrowdSec banned) | Denied | `crowdsec_ban` |
+| Allow country US + CrowdSec enforce | `5.6.7.8` CN | Denied | `country_restricted` (country deny, CrowdSec never runs) |
+| Allow `10.0.0.0/8` + CrowdSec observe | `10.1.2.3` (CrowdSec banned) | Allowed | Ban logged but not enforced |
+
### Removing authentication
To remove an authentication method from a service:
@@ -284,3 +326,4 @@ Authenticated sessions for reverse proxy services are managed using JWT (JSON We
- [Single Sign-On](/manage/team/single-sign-on) - configure your identity provider for SSO across NetBird
- [Provision Users and Groups](/manage/team/idp-sync) - sync users and groups from your identity provider
- [Geolocation Database](/selfhosted/geo-support) - configure MaxMind GeoLite2 for country-based access restrictions (self-hosted)
+- [CrowdSec Setup](/selfhosted/maintenance/crowdsec) - enable CrowdSec IP reputation on a self-hosted proxy
diff --git a/src/pages/manage/reverse-proxy/index.mdx b/src/pages/manage/reverse-proxy/index.mdx
index 4dcde8b4..e38f51cd 100644
--- a/src/pages/manage/reverse-proxy/index.mdx
+++ b/src/pages/manage/reverse-proxy/index.mdx
@@ -39,7 +39,7 @@ A service is the core configuration unit of the Reverse Proxy. Each service maps
- **Domain** - the public URL where the service is reachable
- **Targets** - one or more backend destinations that handle incoming requests
- **Authentication** - optional SSO, password, PIN, or header-based protection
-- **Access restrictions** - optional IP CIDR and country-based access control
+- **Access restrictions** - optional IP CIDR, country, and CrowdSec IP reputation access control
- **Settings** - advanced options (varies by service mode)
- **Enabled/Disabled toggle** - turn the service on or off without deleting it
@@ -57,7 +57,7 @@ The service mode determines how the proxy handles traffic between clients and yo
L4 services (TCP, UDP, TLS) listen on a dedicated port on the proxy cluster. Depending on the cluster, the port may be auto-assigned or you can specify one manually. The proxy cluster's `supports_custom_ports` capability determines whether manual port selection is available.
- L4 services do not support browser-based authentication (SSO, password, PIN) or header authentication because there is no HTTP layer. You can use [access restrictions](/manage/reverse-proxy/authentication#access-restrictions) (IP CIDR and country rules) to protect L4 services.
+ L4 services do not support browser-based authentication (SSO, password, PIN) or header authentication because there is no HTTP layer. You can use [access restrictions](/manage/reverse-proxy/authentication#access-restrictions) (IP CIDR, country, and CrowdSec rules) to protect L4 services.
### Targets
@@ -126,7 +126,7 @@ You can protect a service with one or more authentication methods. When multiple
| **Password** | Yes | No | Protect with a shared password. |
| **PIN Code** | Yes | No | Protect with a numeric PIN code. |
| **Header Authentication** | Yes | No | Validate a static header value (API key, Bearer token, Basic auth). Useful for programmatic access. |
-| **Access Restrictions** | Yes | Yes | Restrict access by IP CIDR range or country. Works at the connection level, so it applies to all service modes. |
+| **Access Restrictions** | Yes | Yes | Restrict access by IP CIDR range, country, or CrowdSec IP reputation. Works at the connection level, so it applies to all service modes. |
If you save a service with no authentication or access restrictions configured, the dashboard will display a warning. Public services are accessible to anyone on the internet who knows the URL.
@@ -268,12 +268,13 @@ Switch to the **Authentication** tab to configure how users are authenticated be
### Step 3b: Configure access control
-Switch to the **Access Control** tab to restrict access by IP address or country. This tab is available for all service modes (HTTP and L4).
+Switch to the **Access Control** tab to restrict access by IP address, country, or IP reputation. This tab is available for all service modes (HTTP and L4).
- Add **allowed CIDRs** or **blocked CIDRs** to restrict by IP range.
- Add **allowed countries** or **blocked countries** to restrict by geographic location.
+- Set **CrowdSec IP Reputation** to **enforce** or **observe** to block or monitor known malicious IPs (when available on the proxy cluster).
-Access restrictions are evaluated before authentication: if a connection is blocked by an IP or country rule, it is rejected before any authentication check.
+Access restrictions are evaluated before authentication: if a connection is blocked by an access restriction rule, it is rejected before any authentication check.
### Step 4: Configure advanced settings
diff --git a/src/pages/selfhosted/maintenance/crowdsec.mdx b/src/pages/selfhosted/maintenance/crowdsec.mdx
new file mode 100644
index 00000000..4d0b2fc9
--- /dev/null
+++ b/src/pages/selfhosted/maintenance/crowdsec.mdx
@@ -0,0 +1,11 @@
+import {Note} from "@/components/mdx"
+
+export const description = 'Enable CrowdSec IP reputation blocking for self-hosted NetBird Proxy deployments.'
+
+# CrowdSec IP Reputation
+
+CrowdSec integration is configured as part of the reverse proxy setup. See [Step 7: Enable CrowdSec IP reputation](/selfhosted/migration/enable-reverse-proxy#step-7-optional-enable-crowdsec-ip-reputation) in the Enable Reverse Proxy guide for full setup instructions, environment variables, and troubleshooting.
+
+
+If you're running the [quickstart script](/selfhosted/selfhosted-quickstart) for a fresh installation, it offers to enable CrowdSec automatically when you choose the built-in Traefik option and enable the proxy.
+
diff --git a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx
index de20eba1..8f593edc 100644
--- a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx
+++ b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx
@@ -248,6 +248,155 @@ Once the proxy connects to the management server:
If the domain appears, the proxy is connected and ready. You can now [create your first service](/manage/reverse-proxy#quick-start).
+### Step 7 (optional): Enable CrowdSec IP reputation
+
+[CrowdSec](https://www.crowdsec.net) is an open-source security engine that shares threat intelligence across its community network. When integrated with the NetBird Proxy, it checks every incoming client IP against a local cache of known malicious addresses and blocks them before they reach your services. This step is entirely optional.
+
+
+**Already have the proxy running?** If you completed steps 1-6 previously and are coming back to add CrowdSec, start here. The instructions below work whether you're setting up the proxy for the first time or adding CrowdSec to an existing proxy deployment.
+
+
+A CrowdSec LAPI (Local API) container runs alongside your deployment, syncs decisions from community blocklists, and exposes them to the proxy via a stream bouncer. The proxy supports two enforcement modes per service:
+
+| Mode | Behavior |
+|------|----------|
+| **enforce** | Blocked IPs are denied immediately. If the bouncer is not yet synced, connections are denied (fail-closed). |
+| **observe** | Blocked IPs are logged but not denied. Use this to evaluate CrowdSec before enforcing. |
+
+#### 7a. Add the CrowdSec container
+
+Add the following service to your `docker-compose.yml`:
+
+```yaml
+ crowdsec:
+ image: crowdsecurity/crowdsec:v1.7.7
+ container_name: netbird-crowdsec
+ restart: unless-stopped
+ networks: [netbird]
+ environment:
+ COLLECTIONS: crowdsecurity/linux
+ volumes:
+ - ./crowdsec:/etc/crowdsec
+ - crowdsec_db:/var/lib/crowdsec/data
+ healthcheck:
+ test: ["CMD", "cscli", "capi", "status"]
+ interval: 10s
+ timeout: 5s
+ retries: 15
+ labels:
+ - traefik.enable=false
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "500m"
+ max-file: "2"
+```
+
+Add `crowdsec_db:` to the `volumes:` section, and update the proxy's `depends_on` so it waits for CrowdSec to be healthy:
+
+```yaml
+ proxy:
+ depends_on:
+ netbird-server:
+ condition: service_started
+ crowdsec:
+ condition: service_healthy
+```
+
+#### 7b. Start CrowdSec and register a bouncer
+
+```bash
+mkdir -p crowdsec
+docker compose up -d crowdsec
+```
+
+Wait for the container to become healthy:
+
+```bash
+docker compose exec crowdsec cscli capi status
+```
+
+Then register a bouncer:
+
+```bash
+docker compose exec crowdsec cscli bouncers add netbird-proxy -o raw
+```
+
+This prints a single API key. Copy it.
+
+#### 7c. Configure the proxy
+
+Add these lines to `proxy.env`:
+
+```bash
+NB_PROXY_CROWDSEC_API_URL=http://crowdsec:8080
+NB_PROXY_CROWDSEC_API_KEY=
+```
+
+Then restart the proxy:
+
+```bash
+docker compose up -d proxy
+```
+
+Verify the connection in the proxy logs:
+
+```bash
+docker compose logs proxy | grep -i crowdsec
+```
+
+You should see `CrowdSec bouncer synced initial decisions` once the LAPI connection is established.
+
+#### 7d. Enable per service
+
+CrowdSec must be enabled individually on each service through the dashboard under **Access Control > Access Restrictions**. Set the CrowdSec mode to **enforce** or **observe**.
+
+
+In **enforce** mode, if the bouncer has not completed its initial sync with the LAPI, all connections to that service will be denied. This is by design (fail-closed). If you want to avoid this during initial rollout, start with **observe** mode.
+
+
+#### 7e. Enroll in CrowdSec Console (optional)
+
+The CrowdSec Console gives you a web dashboard to monitor decisions, manage alerts, and subscribe to premium blocklists. Enrollment is optional: the community blocklists work without it.
+
+```bash
+docker compose exec crowdsec cscli console enroll
+```
+
+Get your enrollment key at [app.crowdsec.net](https://app.crowdsec.net).
+
+#### CrowdSec environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `NB_PROXY_CROWDSEC_API_URL` | CrowdSec LAPI URL. Example: `http://crowdsec:8080`. Empty disables CrowdSec. |
+| `NB_PROXY_CROWDSEC_API_KEY` | Bouncer API key generated by `cscli bouncers add`. |
+
+#### CrowdSec troubleshooting
+
+**Bouncer not connecting**: Check that the CrowdSec container is healthy and reachable from the proxy container:
+
+```bash
+docker compose exec proxy wget -qO- http://crowdsec:8080/v1/decisions/stream --header "X-Api-Key: " 2>&1 | head
+```
+
+If this fails, verify both containers are on the same Docker network.
+
+**All connections denied after enabling enforce mode**: This typically means the bouncer has not completed its initial sync. Check the proxy logs for the `CrowdSec bouncer synced initial decisions` message. If it's missing, the LAPI may be unreachable or the API key may be incorrect. Switch to **observe** mode on affected services until the issue is resolved.
+
+**Checking active decisions**:
+
+```bash
+# List current decisions
+docker compose exec crowdsec cscli decisions list
+
+# Ban an IP for 1 hour (for testing)
+docker compose exec crowdsec cscli decisions add --ip 1.2.3.4 --duration 1h --reason "manual test"
+
+# Remove all decisions for an IP
+docker compose exec crowdsec cscli decisions delete --ip 1.2.3.4
+```
+
## Configure SSO for external identity providers
### Who this applies to
diff --git a/src/pages/selfhosted/selfhosted-quickstart.mdx b/src/pages/selfhosted/selfhosted-quickstart.mdx
index b7f1ecdf..01c8f788 100644
--- a/src/pages/selfhosted/selfhosted-quickstart.mdx
+++ b/src/pages/selfhosted/selfhosted-quickstart.mdx
@@ -79,6 +79,10 @@ For certificates to work properly, ensure you have the proper records set with y
If you skipped the proxy during initial setup, you can add it later by following the [Enable Reverse Proxy migration guide](/selfhosted/migration/enable-reverse-proxy).
+### CrowdSec IP Reputation (Optional)
+
+When the proxy is enabled, the script also offers to add [CrowdSec](/selfhosted/maintenance/crowdsec) for automatic IP reputation blocking. A local CrowdSec LAPI container syncs community threat intelligence and the proxy checks every incoming connection against it. You can enable CrowdSec per service in enforce or observe mode through the dashboard.
+
### Generated Files
The script generates the following files: