Improve CrowdSec hardening and reverse proxy troubleshooting - #917
Improve CrowdSec hardening and reverse proxy troubleshooting#917TechHutTV wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe documentation updates clarify reverse-proxy access-log fields and expand CrowdSec setup, verification, enforcement, allowlist, dashboard protection, recovery, and troubleshooting procedures for self-hosted deployments. ChangesCrowdSec and reverse-proxy documentation
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: Poem Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/manage/reverse-proxy/access-logs.mdx`:
- Around line 79-83: Split the two top-level JSON examples in the access-log
documentation into separate json fences, keeping each object unchanged so every
fenced example is independently valid JSON.
- Line 12: Update the adjacent image alt text near the Access Logs description
to use the renamed “Access Logs” dashboard view instead of “Proxy Events table,”
while preserving the existing image and surrounding documentation.
- Line 62: Update the access-log field descriptions in the table around the
“Auth Method,” “Reason,” and “Denied requests” entries to match the HTTP API
contract: document that auth_method_used contains the restriction code, reason
contains “Authentication failed” for authentication failures and “Request
failed” for later request failures, and keep the descriptions consistent with
the existing HTTP note.
In `@src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx`:
- Around line 397-399: Update the CrowdSec troubleshooting documentation to
distinguish AppSec outage behavior from NetBird Proxy’s initial-sync fail-closed
behavior. Keep the statement that crowdsecAppsecUnreachableBlock=true denies
dashboard requests when AppSec is unreachable, but remove the broader claim that
every later LAPI outage denies all reverse-proxy traffic unless that post-sync
behavior is explicitly verified.
- Around line 316-332: Remove the optional Traefik API health-check
configuration, including --api.insecure=true and the internal API router checks,
from the maintenance documentation. Direct the monitoring guidance to use the
external /.env probe instead, treating HTTP 403 as the healthy response; do not
recommend exposing the unauthenticated API on the shared Docker network.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e86d2b90-877a-4f54-901c-d1367701d645
📒 Files selected for processing (4)
src/pages/manage/reverse-proxy/access-logs.mdxsrc/pages/selfhosted/maintenance/crowdsec-dashboard.mdxsrc/pages/selfhosted/maintenance/crowdsec.mdxsrc/pages/selfhosted/migration/enable-reverse-proxy.mdx
| ## Viewing access logs | ||
|
|
||
| Access logs are available in the NetBird dashboard under **Activity** > **Proxy Events**. This view displays a table of all HTTP requests and L4 connections that have passed through your reverse proxy services, with filters to narrow down results by time range, status, or other fields. | ||
| Access logs are available in the NetBird dashboard under **Reverse Proxy** > **Access Logs**. This view displays a table of all HTTP requests and L4 connections that have passed through your reverse proxy services, with filters to narrow down results by time range, status, or other fields. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the screenshot alt text to match the renamed view.
Line 12 now names the page Access Logs, but the adjacent image at Line 15 still says Proxy Events table. Update the alt text so screen-reader users receive the current dashboard name.
Proposed fix
- <img src="/docs-static/img/manage/reverse-proxy/access-logs/access-logs-table.png" alt="Proxy Events table showing reverse proxy access log entries" className="imagewrapper"/>
+ <img src="/docs-static/img/manage/reverse-proxy/access-logs/access-logs-table.png" alt="Access Logs table showing reverse proxy access log entries" className="imagewrapper"/>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/manage/reverse-proxy/access-logs.mdx` at line 12, Update the
adjacent image alt text near the Access Logs description to use the renamed
“Access Logs” dashboard view instead of “Proxy Events table,” while preserving
the existing image and surrounding documentation.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx (1)
338-391: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winInclude Central API decisions in the lockout diagnosis.
cscli decisions listhides CAPI-sourced decisions by default. A community blocklist decision can therefore be mistaken for AppSec. Query the affected IP with--alland filter AppSec alerts by the same IP:ip="<your-ip>" docker compose exec -T crowdsec cscli decisions list --ip "$ip" --all docker compose exec -T crowdsec cscli alerts list --ip "$ip" --kind waf --limit 0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx` around lines 338 - 391, Update the lockout diagnosis section around “cscli decisions list” to include Central API decisions by querying the affected address with --all, and distinguish those results from AppSec matches. Add the corresponding AppSec alert query filtered by the same IP and kind waf, using the established shell variable pattern, before the allowlisting guidance.Source: MCP tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/manage/reverse-proxy/access-logs.mdx`:
- Around line 40-42: Update the access-log field descriptions in the table
around “Auth Method,” “User,” and “Reason” to match the raw /api/events/proxy
fields: list auth methods as oidc, password, pin, and header; state that empty
auth_method_used values are omitted and credential failures retain only the
attempted method when available; clarify the exact reason values; and document
that denied L4 entries store the restriction code in auth_method_used while
omitting reason.
In `@src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx`:
- Around line 316-322: Update the external monitoring guidance around the
`https://<your-domain>/.env` probe to also cover the protected `/api` router by
adding `https://<your-domain>/api/.env`, or explicitly state that the existing
check covers only the dashboard router. Preserve the instruction to keep the
unauthenticated Traefik API disabled.
---
Outside diff comments:
In `@src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx`:
- Around line 338-391: Update the lockout diagnosis section around “cscli
decisions list” to include Central API decisions by querying the affected
address with --all, and distinguish those results from AppSec matches. Add the
corresponding AppSec alert query filtered by the same IP and kind waf, using the
established shell variable pattern, before the allowlisting guidance.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0ec487e1-2236-4ed4-87d3-dc8e22bd71c8
📒 Files selected for processing (2)
src/pages/manage/reverse-proxy/access-logs.mdxsrc/pages/selfhosted/maintenance/crowdsec-dashboard.mdx
| | **Auth Method** | Authentication method used (SSO, password, PIN, header, or none). For denied requests, carries the restriction code instead (e.g. `ip_restricted`, `crowdsec_ban`) | Yes | N/A | | ||
| | **User** | The authenticated user's ID (if SSO was used) | Yes | N/A | | ||
| | **Reason** | Reason for denial, if applicable | Yes | Yes | | ||
| | **Reason** | `Authentication failed` when authentication or an access restriction rejected the request, or `Request failed` when an authenticated request returned `4xx`/`5xx`. Not a specific denial code: see the note under [Deny reasons](#deny-reasons) | Yes | No | |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ref="${NETBIRD_REF:?Set NETBIRD_REF to the matching NetBird server commit or release}"
base="https://raw.githubusercontent.com/netbirdio/netbird/${ref}"
for path in \
proxy/auth/auth.go \
proxy/internal/auth/middleware.go \
proxy/internal/accesslog/logger.go \
management/internals/modules/reverseproxy/accesslogs/accesslogentry.go
do
printf '\n--- %s ---\n' "$path"
curl -fsSL "$base/$path" |
rg -n -C 4 \
'MethodPassword|MethodPIN|MethodOIDC|MethodHeader|attemptedMethod|SetAuthMethod|DenyReason|AuthMechanism|AuthMethodUsed|Authentication failed|Request failed'
doneRepository: netbirdio/docs
Length of output: 244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- changed file status ---'
git status --short
printf '%s\n' '--- relevant file lines ---'
cat -n src/pages/manage/reverse-proxy/access-logs.mdx | sed -n '1,180p'
printf '%s\n' '--- recent repository references ---'
rg -n 'NETBIRD_REF|/api/events/proxy|auth_method_used|Denied L4|Deny reasons' . -g '*.mdx' -g '*.md' -g '*.yml' -g '*.yaml' -g '*.json' | head -200Repository: netbirdio/docs
Length of output: 13427
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- changed file status ---'
git status --short
printf '%s\n' '--- relevant file lines ---'
cat -n src/pages/manage/reverse-proxy/access-logs.mdx | sed -n '1,180p'
printf '%s\n' '--- repository references ---'
rg -n 'NETBIRD_REF|/api/events/proxy|auth_method_used|Denied L4|Deny reasons' . -g '*.mdx' -g '*.md' -g '*.yml' -g '*.yaml' -g '*.json' | head -200Repository: netbirdio/docs
Length of output: 13420
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fetch() {
local ref="$1" path="$2"
printf '\n--- %s %s ---\n' "$ref" "$path"
curl -fsSL "https://raw.githubusercontent.com/netbirdio/netbird/${ref}/${path}" |
nl -ba |
rg -n -C 8 \
'MethodPassword|MethodPIN|MethodOIDC|MethodHeader|attemptedMethod|SetAuthMethod|DenyReason|AuthMechanism|AuthMethodUsed|Authentication failed|Request failed|reason|auth_method_used|metadata|AccessLog'
}
for ref in main 1e7b16db; do
fetch "$ref" proxy/auth/auth.go
fetch "$ref" proxy/internal/auth/middleware.go
fetch "$ref" proxy/internal/accesslog/logger.go
fetch "$ref" management/internals/modules/reverseproxy/accesslogs/accesslogentry.go
doneRepository: netbirdio/docs
Length of output: 247
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fetch() {
local ref="$1" path="$2" tmp
tmp="$(mktemp)"
curl -fsSL "https://raw.githubusercontent.com/netbirdio/netbird/${ref}/${path}" >"$tmp"
printf '\n--- %s %s ---\n' "$ref" "$path"
awk '{printf "%6d\t%s\n", NR, $0}' "$tmp" |
rg -n -C 8 \
'MethodPassword|MethodPIN|MethodOIDC|MethodHeader|attemptedMethod|SetAuthMethod|DenyReason|AuthMechanism|AuthMethodUsed|Authentication failed|Request failed|reason|auth_method_used|metadata|AccessLog'
rm -f "$tmp"
}
for ref in main 1e7b16db; do
fetch "$ref" proxy/auth/auth.go
fetch "$ref" proxy/internal/auth/middleware.go
fetch "$ref" proxy/internal/accesslog/logger.go
fetch "$ref" management/internals/modules/reverseproxy/accesslogs/accesslogentry.go
doneRepository: netbirdio/docs
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ref="1e7b16db"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
for spec in \
"proxy/internal/auth/middleware.go middleware.go" \
"proxy/internal/accesslog/logger.go logger.go" \
"management/internals/modules/reverseproxy/accesslogs/accesslogentry.go accesslogentry.go"
do
path="${spec% *}"
file="${spec##* }"
curl -fsSL "https://raw.githubusercontent.com/netbirdio/netbird/${ref}/${path}" > "$tmp/$file"
done
python3 - "$tmp" <<'PY'
import re
import sys
from pathlib import Path
root = Path(sys.argv[1])
auth = (root / "middleware.go").read_text()
logger = (root / "logger.go").read_text()
entry = (root / "accesslogentry.go").read_text()
checks = {
"raw auth methods": all(
f'Method{name} Method = "{value}"' in auth.replace("auth.", "")
for name, value in (
("Password", "password"),
("PIN", "pin"),
("OIDC", "oidc"),
("Header", "header"),
)
),
"failed credentials retain attempted method": (
"attemptedMethod = scheme.Type().String()" in auth
and "cd.SetAuthMethod(attemptedMethod)" in auth
),
"restriction denial stores restriction code": (
"cd.SetAuthMethod(reason)" in auth
),
"L4 denial maps to auth mechanism": (
"le.AuthMechanism = entry.DenyReason" in logger
and "le.AuthSuccess = false" in logger
),
"L4 denial does not set reason": (
"if a.Protocol == \"\" || a.Protocol == AccessLogProtocolHTTP" in entry
),
"reason classification": all(
text in entry
for text in (
'a.Reason = "Authentication failed"',
'a.Reason = "Request failed"',
'if !serviceLog.GetAuthSuccess()',
'else if serviceLog.GetResponseCode() >= 400',
)
),
"empty auth method omitted": (
"if a.AuthMethodUsed != \"\"" in entry
and "authMethod = &a.AuthMethodUsed" in entry
),
}
for name, passed in checks.items():
print(f"{'PASS' if passed else 'FAIL'}: {name}")
if not all(checks.values()):
raise SystemExit(1)
PYRepository: netbirdio/docs
Length of output: 408
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ref="1e7b16db"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
for spec in \
"proxy/internal/auth/middleware.go middleware.go" \
"proxy/internal/accesslog/logger.go logger.go" \
"management/internals/modules/reverseproxy/accesslogs/accesslogentry.go accesslogentry.go"
do
path="${spec% *}"
file="${spec##* }"
curl -fsSL "https://raw.githubusercontent.com/netbirdio/netbird/${ref}/${path}" > "$tmp/$file"
done
python3 - "$tmp" <<'PY'
import sys
from pathlib import Path
root = Path(sys.argv[1])
auth = (root / "middleware.go").read_text()
logger = (root / "logger.go").read_text()
entry = (root / "accesslogentry.go").read_text()
checks = {
"raw auth methods": all(
f'Method{name} Method = "{value}"' in auth.replace("auth.", "")
for name, value in (
("Password", "password"),
("PIN", "pin"),
("OIDC", "oidc"),
("Header", "header"),
)
),
"failed credentials retain attempted method": (
"attemptedMethod = scheme.Type().String()" in auth
and "cd.SetAuthMethod(attemptedMethod)" in auth
),
"restriction denial stores restriction code": (
"cd.SetAuthMethod(reason)" in auth
),
"L4 denial maps to auth mechanism": (
"le.AuthMechanism = entry.DenyReason" in logger
and "le.AuthSuccess = false" in logger
),
"L4 denial does not set reason": (
"if a.Protocol == \"\" || a.Protocol == AccessLogProtocolHTTP" in entry
),
"reason classification": all(
text in entry
for text in (
'a.Reason = "Authentication failed"',
'a.Reason = "Request failed"',
'if !serviceLog.GetAuthSuccess()',
'else if serviceLog.GetResponseCode() >= 400',
)
),
"empty auth method omitted": (
"if a.AuthMethodUsed != \"\"" in entry
and "authMethod = &a.AuthMethodUsed" in entry
),
}
for name, passed in checks.items():
print(f"{'PASS' if passed else 'FAIL'}: {name}")
if not all(checks.values()):
raise SystemExit(1)
PYRepository: netbirdio/docs
Length of output: 408
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ref="1e7b16db"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
for spec in \
"proxy/auth/auth.go auth.go" \
"proxy/internal/auth/middleware.go middleware.go" \
"proxy/internal/accesslog/logger.go logger.go" \
"management/internals/modules/reverseproxy/accesslogs/accesslogentry.go accesslogentry.go"
do
path="${spec% *}"
file="${spec##* }"
curl -fsSL "https://raw.githubusercontent.com/netbirdio/netbird/${ref}/${path}" > "$tmp/$file"
done
python3 - "$tmp" <<'PY'
import sys
from pathlib import Path
root = Path(sys.argv[1])
auth_types = (root / "auth.go").read_text()
middleware = (root / "middleware.go").read_text()
logger = (root / "logger.go").read_text()
entry = (root / "accesslogentry.go").read_text()
checks = {
"raw auth methods": all(
f'Method{name} Method = "{value}"' in auth_types
for name, value in (
("Password", "password"),
("PIN", "pin"),
("OIDC", "oidc"),
("Header", "header"),
)
),
"failed credentials retain attempted method": (
"attemptedMethod = scheme.Type().String()" in middleware
and "cd.SetAuthMethod(attemptedMethod)" in middleware
),
"restriction denial stores restriction code": (
"cd.SetAuthMethod(reason)" in middleware
),
"L4 denial maps to auth mechanism": (
"le.AuthMechanism = entry.DenyReason" in logger
and "le.AuthSuccess = false" in logger
),
"L4 denial does not set reason": (
"if a.Protocol == \"\" || a.Protocol == AccessLogProtocolHTTP" in entry
),
"reason classification": all(
text in entry
for text in (
'a.Reason = "Authentication failed"',
'a.Reason = "Request failed"',
'if !serviceLog.GetAuthSuccess()',
'else if serviceLog.GetResponseCode() >= 400',
)
),
"empty auth method omitted": (
"if a.AuthMethodUsed != \"\"" in entry
and "authMethod = &a.AuthMethodUsed" in entry
),
}
for name, passed in checks.items():
print(f"{'PASS' if passed else 'FAIL'}: {name}")
if not all(checks.values()):
raise SystemExit(1)
PYRepository: netbirdio/docs
Length of output: 408
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl -fsSL 'https://raw.githubusercontent.com/netbirdio/netbird/1e7b16db/proxy/auth/auth.go' > "$tmp"
printf '%s\n' '--- declaration lines ---'
sed -n '12,24p' "$tmp"
python3 - "$tmp" <<'PY'
import sys
from pathlib import Path
text = Path(sys.argv[1]).read_text()
for line in text.splitlines():
if "Method" in line and ("password" in line or "pin" in line or "oidc" in line or "header" in line):
print(repr(line))
PYRepository: netbirdio/docs
Length of output: 542
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl -fsSL 'https://raw.githubusercontent.com/netbirdio/netbird/1e7b16db/proxy/internal/accesslog/logger.go' > "$tmp"
printf '%s\n' '--- AuthSuccess assignments and log construction ---'
awk '{printf "%6d\t%s\n", NR, $0}' "$tmp" |
rg -n -C 10 'AuthSuccess\s*:|AuthSuccess|LogHTTP|LogL4|logEntry\{'
printf '%s\n' '--- public/auth middleware references ---'
for path in \
proxy/internal/auth/middleware.go \
proxy/internal/proxy/proxy.go \
proxy/internal/proxy/handler.go
do
url="https://raw.githubusercontent.com/netbirdio/netbird/1e7b16db/$path"
if curl -fsSL "$url" > "$tmp"; then
printf '\n--- %s ---\n' "$path"
awk '{printf "%6d\t%s\n", NR, $0}' "$tmp" |
rg -n -C 8 'AuthSuccess|LogHTTP|AuthMechanism|public|authenticate' || true
fi
doneRepository: netbirdio/docs
Length of output: 12851
Align access-log documentation with the raw /api/events/proxy fields.
- Use
oidc,password,pin, andheader. Emptyauth_method_usedvalues are omitted. - For credential failures,
auth_method_usedstores the attempted method when available. It does not store detailed causes. reasonisAuthentication failedfor authentication failures andRequest failedfor other HTTP4xx/5xxresponses.- Denied L4 entries store the restriction code in
auth_method_usedand omitreason.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/manage/reverse-proxy/access-logs.mdx` around lines 40 - 42, Update
the access-log field descriptions in the table around “Auth Method,” “User,” and
“Reason” to match the raw /api/events/proxy fields: list auth methods as oidc,
password, pin, and header; state that empty auth_method_used values are omitted
and credential failures retain only the attempted method when available; clarify
the exact reason values; and document that denied L4 entries store the
restriction code in auth_method_used while omitting reason.
Source: MCP tools
| To surface this automatically, monitor the same probe from outside the deployment and treat `403` as the healthy response. Point an uptime monitor or a cron job at: | ||
|
|
||
| ```text | ||
| https://<your-domain>/.env | ||
| ``` | ||
|
|
||
| Alert when the response is anything other than `403`. This is an inverted check, alerting on the absence of a block rather than on an outage, but it is the only signal that confirms the protection layer is still in the request path. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
base="https://<your-domain>"
for path in /.env /api/.env; do
code="$(curl -ks -o /dev/null -w '%{http_code}' "${base}${path}")"
test "$code" = 403 || exit 1
doneRepository: netbirdio/docs
Length of output: 152
🏁 Script executed:
#!/bin/bash
set -eu
file="src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx"
printf '%s\n' '--- target section ---'
sed -n '300,345p' "$file"
printf '%s\n' '--- route and middleware references ---'
rg -n -C 3 'middleware|/api|/oauth2|\\.env|router|Traefik' "$file"Repository: netbirdio/docs
Length of output: 16088
🏁 Script executed:
#!/bin/bash
set -eu
file="src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx"
printf '%s\n' '--- complete route configuration ---'
sed -n '115,165p' "$file"
printf '%s\n' '--- probes and router labels ---'
rg -n -C 2 'https?://.*(\\.env|api)|routers\\.|middlewares=' "$file"
printf '%s\n' '--- repository references to the same router names ---'
rg -n -C 2 'netbird-dashboard-api|netbird-dashboard-crowdsec|PathPrefix\\(`/api`\\)|PathPrefix\\(`/oauth2`\\)' .Repository: netbirdio/docs
Length of output: 7790
Probe both protected routers. The /.env check covers only the dashboard router. The guide also protects /api and /oauth2 through a separate router.
Add /api/.env to the monitor, or state that the check covers only the dashboard router. Keep the unauthenticated Traefik API disabled.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx` around lines 316 -
322, Update the external monitoring guidance around the
`https://<your-domain>/.env` probe to also cover the protected `/api` router by
adding `https://<your-domain>/api/.env`, or explicitly state that the existing
check covers only the dashboard router. Preserve the instruction to keep the
unauthenticated Traefik API disabled.
Source: MCP tools
Description
This PR expands the self-hosted CrowdSec and reverse proxy documentation with clearer setup, verification, monitoring, and recovery guidance. It documents observed CrowdSec behavior across access logs, community decisions, AppSec protection, and dashboard lockouts so operators can validate their deployment and recover safely when enforcement behaves unexpectedly.
Changes
NB_PROXY_PRIVATE.Summary by CodeRabbit