Your Servers Are Learning Nothing From Each Other
Someone hammers SSH on your NAS at 2 AM. CrowdSec on that box notices, scores the pattern, and bans the IP within a couple of minutes. Good. Then, twenty minutes later, the same IP shows up on your reverse proxy box and gets a fresh set of login attempts, because that machine has no idea the NAS just fought this exact fight. Each of your boxes is running its own separate brain, learning the same lesson over and over, in isolation, forever.
That’s the default CrowdSec install: every server bundles its own log-parsing agent and its own Local API (LAPI) together in one process. It works fine for a single box. It falls apart the moment you have three or four machines in a rack or on a shelf, because “learned” never travels between them.
Full example: Clone the working files at github.com/KingPin/sumguy-examples/security/crowdsec-multi-server
This piece assumes you already know what CrowdSec collections and bouncers are. If you don’t, read CrowdSec Collections & Bouncers: fail2ban for 2026 first, then come back. Here we’re wiring several boxes into one shared brain instead of three lonely ones. Everything below was checked against CrowdSec v1.8.1, the latest stable release as of September 2026.
Why Every Box Gets Its Own Brain by Default
The crowdsec binary does two jobs in one process: it parses logs into alerts (the agent part), and it turns alerts into decisions like bans (the LAPI part). A stock install on any single machine runs both, talking to itself over 127.0.0.1:8080. That’s the sane default for one box: no network config, no extra moving parts, nothing to secure beyond the local socket.
The problem shows up the moment you own more than one server. Each install still ships its own agent and its own LAPI, so each one keeps a private list of banned IPs. Nothing shares. Your NAS, your reverse proxy, and your game server all fight the same internet independently, at different speeds, with different blind spots.
The Topology: One Brain, Several Watchdogs
The fix is to split the two jobs apart and centralize the one that matters:
- One box runs the LAPI. Pick something that’s on 24/7 (a NAS, a low-power mini PC, whatever already never reboots). This machine stores decisions and nothing else needs to.
- Every box runs an agent, including the LAPI box itself. Agents parse local logs (
auth.log, Traefik or Caddy access logs, whatever) and push alerts up to the central LAPI instead of keeping their own copy. - Every box runs one or more bouncers: a firewall bouncer to block at the packet level, and a reverse-proxy bouncer (the Traefik or Caddy plugin) to block at the HTTP layer. Bouncers pull decisions from the central LAPI, not from a local one.
An SSH brute-force on box A gets parsed by box A’s agent, pushed to the central LAPI, turned into a ban, and pulled down by the firewall bouncers on box B and box C, usually inside a few seconds. Nobody else has to get hit first.
Point the LAPI’s Ear Somewhere Reachable
By default the LAPI only listens on 127.0.0.1:8080, which is exactly why a fresh install works with zero config and exactly why remote agents can’t reach it yet. On a bare-metal or systemd install, that’s the listen_uri key in /etc/crowdsec/config.yaml:
api: server: listen_uri: 192.168.1.10:8080Bind it to a LAN address or, if you’re already running Tailscale across your lab, the Tailscale IP works too and skips exposing the port to your whole LAN. Either way:
- Firewall port 8080. Only the boxes running agents and bouncers need to reach it. A stray rule that opens 8080 to the world turns your threat-intel brain into a target.
- Never expose the LAPI to the internet. There’s no reason a home lab’s LAPI needs a public IP, and CrowdSec’s own docs are blunt about restricting access to trusted IPs.
- TLS is available if you want mutual auth instead of usernames and passwords: set
cert_fileandkey_fileunderapi.server.tls, plusca_cert_pathand anagents_allowed_ou/bouncers_allowed_ouallowlist. Agents and bouncers with a matching client certificate get registered automatically, no password exchange at all. Worth it if you’re provisioning boxes often; overkill for three machines you SSH into by hand.
If you’re running the official Docker image instead, listen_uri stays 0.0.0.0:8080 inside the container by design. You control exposure through the Docker port mapping instead, so publish it to a specific address:
services: crowdsec: image: crowdsecurity/crowdsec:v1.8.1 ports: - "192.168.1.10:8080:8080"Enrolling Agents: Two Ways In
CrowdSec gives you two paths to register a remote agent with the central LAPI, and which one you use depends on whether you’re running the Docker image or a package install.
On the LAPI side (Docker, or any install), register the machine first:
cscli machines add boxb-agent --password CHANGE_ME_AGENT_PASSWORDThat writes credentials the LAPI will accept. Do this once per remote box.
On a package install, the agent can also self-register, then get validated on the LAPI:
# on the agent boxsudo cscli lapi register --machine boxb-agent --url http://192.168.1.10:8080
# back on the LAPI boxsudo cscli machines listsudo cscli machines validate boxb-agentcscli lapi register creates a pending machine entry; cscli machines validate on the LAPI side is what actually lets it start pushing alerts. Skip validation and the agent sits there registered but ignored.
Telling an Agent to Stop Being Its Own Boss
Registering the machine isn’t enough by itself. Every remote box still needs its local LAPI turned off, or you end up with two brains again: a real one you’re centralizing and a dead local one nobody uses.
On a package install, that’s one key in /etc/crowdsec/config.yaml, then a sudo systemctl restart crowdsec:
api: server: enable: falseThe agent still needs to know where the real LAPI lives, and cscli lapi register already wrote that URL and the machine credentials into /etc/crowdsec/local_api_credentials.yaml. On the official Docker image the same switch is an environment variable (the entrypoint literally sets api.server.enable=false for you):
services: crowdsec: image: crowdsecurity/crowdsec:v1.8.1 environment: DISABLE_LOCAL_API: "true" LOCAL_API_URL: "http://192.168.1.10:8080" AGENT_USERNAME: "boxb-agent" AGENT_PASSWORD: "CHANGE_ME_AGENT_PASSWORD" COLLECTIONS: "crowdsecurity/linux crowdsecurity/sshd" volumes: - crowdsec-config:/etc/crowdsec - /var/log:/var/log:ro restart: unless-stoppedvolumes: crowdsec-config:DISABLE_LOCAL_API=true skips starting the LAPI server inside that container entirely, agent-only. LOCAL_API_URL has to change too: its default is http://0.0.0.0:8080, meant for a container running its own LAPI, and it’s meaningless once the local one is off. Point it at the central box instead. AGENT_USERNAME and AGENT_PASSWORD are the credentials from the cscli machines add step above, and they need to match exactly.
Bouncers Everywhere, Brains Nowhere Else
Every box that fronts traffic, not just the LAPI box, needs its own bouncers pointed at the central LAPI. Register each one from the LAPI side:
cscli bouncers add firewall-boxbThat prints an API key, once, on the spot. Copy it into the bouncer’s config on the remote box. For the firewall bouncer (cs-firewall-bouncer), that’s /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml:
mode: nftablesupdate_frequency: 10sapi_url: http://192.168.1.10:8080/api_key: CHANGE_ME_FIREWALL_BOUNCER_KEYdeny_action: DROPsupported_decisions_types: - banapi_url defaults to the loopback address, which only made sense back when the bouncer and the LAPI lived on the same box. Point it at the central LAPI and the box’s own iptables or nftables set fills up with decisions made anywhere in your lab, not just from its own logs. update_frequency controls how often the bouncer polls for new and expired decisions; 10 seconds is the shipped default and it’s fine for a home lab.
Do the same for the reverse-proxy bouncer if you run Traefik or Caddy: same cscli bouncers add step, same api_url pointed at the central LAPI, different config file. The plugin-level setup for those is already covered in the collections and bouncers post linked above, so we won’t repeat it here; the only thing that changes for a multi-server setup is which LAPI address you hand it.
Proving the Whole Thing Actually Works
Skip waiting for a real attacker. Fake a ban from the LAPI box and watch it land everywhere else:
cscli decisions add --ip 203.0.113.42 --duration 4h --reason "multi-server smoke test"--duration defaults to 4h and --type defaults to ban if you don’t set them. Now go check the other boxes. On the LAPI itself:
cscli bouncers listcscli decisions listcscli bouncers list should show every remote bouncer as connected with a recent last-seen time, not just the local ones. On box B or C, whichever runs the firewall bouncer, check the actual firewall state:
sudo nft list table ip crowdsec | grep 203.0.113.42The bouncer keeps one set per decision origin, so a manual ban lands in crowdsec-blacklists-cscli while agent bans go to crowdsec-blacklists-crowdsec. Listing the whole ip crowdsec table saves you guessing which one. 203.0.113.42 should show up there within one update_frequency cycle, ten seconds by default, with zero manual steps on box B. One ban, entered once on the LAPI, enforced on every box that runs a bouncer.
What Happens When the One Brain Goes Quiet
Centralizing the LAPI creates an obvious single point of failure, so it’s worth being straight about what actually happens when that box reboots or drops off the network.
CrowdSec’s firewall bouncer runs in “stream” mode by default: it doesn’t ask the LAPI a question per request, it periodically pulls the full list of active and expired decisions and keeps its own local copy in the box’s ipset or nftables set. When the LAPI goes unreachable, the bouncer simply stops getting new updates. It doesn’t flush the set it already built. Bans already pushed to that box stay enforced, because the firewall rule lives in the kernel, not in a live connection to the LAPI. What you lose during the outage is new bans and expirations: nothing fresh propagates, and decisions that should time out won’t get cleared until the LAPI is back and the next sync runs.
Practically: an outage doesn’t open the door, it just freezes it at whatever state it was in. Put the LAPI on the most boring, least-rebooted box you own (a NAS beats a desktop you shut down at night), and don’t treat this as a real high-availability setup, because CrowdSec doesn’t ship LAPI clustering. If that keeps you up at night, run a spare box with its own local LAPI as backup and manually re-point agents if the primary dies for good.
One more failure mode worth knowing: CrowdSec’s own guidance is to keep the agent and LAPI on matching versions across every box. A mismatch isn’t always silent. There are real reports of an agent-only container built against a newer release crashing on startup against an older LAPI, because it called an API endpoint the older LAPI didn’t have yet. Upgrade the LAPI box first, then roll the same version out to agents and bouncers.
The Console, If You Want Someone Else’s Dashboard
CrowdSec also ships a hosted web dashboard called the Console, at app.crowdsec.net, that gives you one screen across every enrolled Security Engine: alerts, decisions, bouncer health, all in one place instead of cscli on each box over SSH. You enroll an engine with an ENROLL_KEY from the console, and it’s additive: your self-hosted LAPI keeps making decisions locally whether or not the Console is enrolled. Skip it entirely if cscli decisions list and a terminal are all you want, but for four or more boxes it’s a faster way to notice a bouncer that’s gone quiet than checking each one by hand.
Common Questions
Do the CrowdSec agent and LAPI need to run the same version?
Yes, keep them matched. CrowdSec’s own upgrade guidance says to update crowdsec, cscli, and the LAPI together rather than let them drift. Mismatches aren’t always harmless: a newer agent-only container has crashed on startup against an older LAPI because it called an endpoint that version didn’t expose yet.
Do I need the CrowdSec Console for a home lab multi-server setup?
No. The Console is an optional hosted dashboard for watching multiple engines from one screen; your self-hosted LAPI makes every ban decision on its own regardless of enrollment. Skip it if cscli decisions list on the LAPI box tells you everything you need.
How much hardware does the central LAPI need for a home lab?
Not much. The LAPI is a lightweight Go binary storing decisions in SQLite; a Raspberry Pi 4 or a low-power NAS handles a handful of home lab boxes without breaking a sweat. The bigger requirement is uptime, not CPU or RAM: put it on whatever machine in your lab reboots the least.
Can I migrate an existing per-box CrowdSec setup to one central LAPI?
Yes, one box at a time, no wipe needed. Pick one existing install to keep as the LAPI, register each other box’s agent against it with cscli machines add, then flip DISABLE_LOCAL_API on those other boxes once their agents connect. Existing local decisions on the boxes you’re demoting just expire naturally.
What do people get wrong when centralizing CrowdSec across servers?
The most common mistake is forgetting to firewall port 8080 after binding listen_uri to a LAN address, leaving the LAPI reachable from more of the network than intended. A close second: running cscli lapi register but skipping cscli machines validate, so the LAPI refuses the agent’s login and none of its alerts arrive.