Turn DDoS detector signals into guarded BGP FlowSpec mitigations.
Detectors post attack events over HTTP. prefixd maps the target to your inventory, picks a playbook, checks guardrails, and asks a separate BGP speaker to announce a FlowSpec rule with a mandatory TTL. No detector speaks BGP.
Documentation · Quickstart · Cookbook · Releases
Preferred speaker: rustbgpd, "an API-first BGP daemon in Rust for route servers, route reflectors, and network automation." prefixd drives it over gRPC, and the
rustbgpdCompose profile is the default path. GoBGP stays maintained and selectable. Why rustbgpd
Status: pre-1.0, and the API can change before 1.0. The latest release, v0.20.0, is the first with the rustbgpd backend and Compose profiles. Upgrading from v0.19.1 needs one maintenance step; follow the upgrade guide.
FastNetMon, Prometheus Alertmanager or any JSON webhook sends attack events. prefixd groups signals per target, weighs corroborating sources and applies the matching playbook. Controller integration tests cover these adapters; the dated profile qualification covers manual mitigation, not detector traffic.
FastNetMon · Generic webhook · Corroborating signals
Mitigate a target by hand from the dashboard or API inside the same guardrails, then withdraw it with a reason that the audit log keeps. Rehearse policy in dry-run before you let it announce.
Manual mitigation · Dry-run rollout · Troubleshooting
Drive events, mitigations, the safelist and configuration through the HTTP API
or prefixdctl. The controller serves its OpenAPI document at /openapi.json.
HTTP API · CLI · Authentication
prefixd stops at the speaker. Router receipt and enforcement depend on your router software, address family and import policy. Existing router receipts are scoped to GoBGP; rustbgpd has local and peer tests but no vendor router receipt yet. See vendor compatibility.
With Git and Docker Compose installed, start the controller, dashboard, PostgreSQL and rustbgpd from published images. Nothing compiles locally.
git clone https://github.com/lance0/prefixd.git
cd prefixd
docker compose --profile rustbgpd up -d --pull always --no-buildCreate the first administrator at the hidden password prompt, then check liveness:
docker compose exec prefixd-rustbgpd prefixdctl operators create \
--username admin --role admin
curl http://localhost/v1/healthOpen http://localhost and sign in. nginx serves the dashboard and API on port
80, and rustbgpd publishes BGP on 179. Prometheus (9091) and Grafana (3001) bind
host loopback only. The sample peer uses a documentation address, so it stays
Idle until you configure your edge router in configs/rustbgpd.toml.
Synthetic data; router installation is unobserved.
For GoBGP, start a fresh deployment with
docker compose --profile gobgp up -d --pull always --no-build and run
controller commands in prefixd-gobgp. Run one profile at a time: switching
doesn't move active rules or the database's speaker binding. The
quickstart covers configuration storage,
building from source and troubleshooting.
Detector events and manual requests pass the same checks before prefixd saves
intent or calls the speaker. A rejection returns 422 with its reason and sends
nothing to BGP. With the shipped configs/prefixd.yaml,
sign in and request a mitigation for a safelisted address:
curl -sS -c cookies.txt -X POST http://localhost/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"your_password_here"}'
curl -sS -b cookies.txt -X POST http://localhost/v1/mitigations \
-H 'Content-Type: application/json' \
-d '{"reason":"guardrail check","victim_ip":"10.0.0.5","protocol":"udp",
"dst_ports":[53],"action":"discard","ttl_seconds":120}'{"error":"guardrail violation: IP 10.0.0.5/32 is safelisted"}The other limits in the shipped configuration reject requests the same way:
| Guardrail | Shipped setting | Example rejection |
|---|---|---|
| Safelist | 10.0.0.0/8, 192.168.0.0/16, plus API entries |
IP 10.0.0.5/32 is safelisted |
| TTL | Required, 30 to 1800 seconds | TTL 7200s out of bounds (min=30, max=1800) |
| Ports | At most 8 destination ports | too many ports: 9 (max=8) |
| Active quotas | 5 per customer, 200 per POP, 500 global | quota exceeded: customer (5/5) |
| New mitigation rate | 30 per POP in any 60 seconds; TTL extensions don't count | new mitigation rate exceeded: 30 created at this POP in the last 60s (max_new_per_minute=30) |
| Destination | IPv4 /32 and IPv6 /128 host routes only |
Intent is always built as a host route |
Two more rules protect routing state:
- Dry-run:
mode: dry-runruns the full pipeline and records mitigations, metrics and audit entries, but never calls the speaker. Those mitigations stay simulations after a switch to enforced. The shipped config setsmode: enforced; a config that omitsmoderuns in dry-run. - Immutable withdrawal intent: an operator withdrawal, detector unban or TTL expiry saves its rule, actor and reason before the speaker call. A failed or uncertain delete stays pending and retries against the same speaker; it can't turn back into an announcement.
A 201 means prefixd saved intent and the speaker accepted the local request.
It doesn't prove that a router installed the rule. The running controller
enforces TTLs: if prefixd stops while the speaker stays up, rules stay announced.
Guardrail settings · Withdrawal recovery · Dry-run rollout
CI publishes the controller and dashboard images to ghcr.io/lance0/prefixd
and ghcr.io/lance0/prefixd-dashboard. Pin a controller and dashboard built
from the same revision.
- Run the containers: follow image selection and the deployment guide.
- Build from source: Rust 1.89 or later and PostgreSQL 14 or later.
cargo build --releasebuildsprefixdandprefixdctlwith both speaker adapters; see bare-metal deployment and contributing. - Before production: set up authentication, TLS and router import policy. Run one controller per POP and speaker target, and read the upgrade guide before changing either.
prefixd decides what to mitigate; rustbgpd owns the BGP sessions. The two meet at a narrow, tested boundary:
- Typed gRPC calls: prefixd adds, deletes and lists FlowSpec rules through rustbgpd's injection and RIB services, on an owner-only Unix socket locally or verified mTLS remotely. Every mutation returns an explicit outcome.
- Reconciliation restores lost rules: rustbgpd holds injected rules in its process. After a speaker restart, prefixd's database still holds the desired state, and the next reconciliation poll (every 30 seconds in the shipped config) re-adds missing rules.
- Qualified versions: v0.76.0 is the minimum controller contract and v0.78.0 is the Compose pin. CI runs the lifecycle, TTL and restart suite against the v0.76.0, v0.77.0 and v0.78.0 immutable images.
This qualifies the controller-to-speaker boundary on Linux amd64. It says nothing about router enforcement.
rustbgpd README · rustbgpd docs · Speaker backends · ADR 023
Start with the documentation index, or go to your task:
| Task | Guide |
|---|---|
| Run it for the first time | Quickstart · Cookbook |
| Connect a detector | FastNetMon · Generic webhook · Correlation |
| Configure, deploy or upgrade | Configuration · Deployment · Upgrading |
| Automate | HTTP API · CLI · API versioning |
| Choose or operate a speaker | Speaker backends · Vendors · Troubleshooting |
| Check the evidence | Profile qualification · Benchmarks (historical) |
| Understand or contribute | Architecture · Decisions · Contributing · Roadmap |
| Report a problem | Issues · Security · Changelog |
MIT. See LICENSE.



