Fail2ban has been the go-to answer for "someone's hammering my SSH login" for about two decades now, and for good reason — it's simple and it works. But it only knows about attacks that hit your own server. If a botnet has been pounding some stranger's VPS in Germany for the last hour, your Proxmox box has no idea until that same IP shows up at your door. CrowdSec fixes that specific gap: it watches your logs the same way Fail2ban does, but it also plugs into a shared blocklist built from every other CrowdSec user reporting bad IPs in near real time.

This guide walks through installing CrowdSec in its own dedicated LXC container on Proxmox VE, wiring it up to actually watch something (SSH, in this case), and installing a bouncer so it can block traffic instead of just logging it. By the end you'll have a working intrusion prevention setup you can point at other services later, and you'll understand enough of the moving parts to not be scared of them.

What You Will Learn

  • What CrowdSec actually is, and how it's different from Fail2ban
  • How to build a small, isolated LXC container for it on Proxmox VE
  • How to install the CrowdSec engine and a bouncer using the official repository
  • How to confirm it's actually parsing logs and generating decisions
  • How to safely test a ban without waiting for a real attacker
  • The errors people run into most often, and what actually causes them

What Is This Feature?

CrowdSec is an open-source security engine made up of a few distinct pieces, and understanding the split makes the rest of this guide much easier to follow.

The agent (the crowdsec service itself) reads log sources you tell it about — the systemd journal, a log file, whatever — and runs them through parsers and scenarios. A scenario is basically a rule: "5 failed SSH logins from the same IP within 60 seconds means bad news." When a scenario triggers, the agent creates a decision, which is usually a ban for that IP, and stores it locally.

On its own, none of that blocks anything. That's the job of a bouncer — a separate small program that asks the agent's local API "is this IP currently banned?" and, if so, drops the connection using your firewall (iptables or nftables) before it ever reaches the service behind it. Engine and bouncer are deliberately separate processes, which is why you'll install both later.

The part that actually sets CrowdSec apart from something like Fail2ban is the Central API, often shortened to CAPI. If you choose to enroll your instance (it's optional and free at the community tier), your local decisions get anonymized and shared upward, and in exchange you receive a constantly updated blocklist of IPs that other CrowdSec users worldwide have already flagged. In practice this means your server can start blocking a scanning bot before that bot has even tried your IP yet, because someone else already caught it first.

If you haven't worked with an LXC container before: it's a lightweight alternative to a full VM. Rather than emulating hardware and booting its own kernel, it shares the Proxmox host's kernel and gets an isolated slice of processes and filesystem. That's why a container this small can boot in a couple of seconds and idle at a tiny fraction of a CPU core — more than enough for a log-watching service like CrowdSec.

Why Would You Use It?

If you already followed our Fail2ban guide and it's working fine, you don't strictly need this too. Running both against the same service is redundant and can occasionally fight over the same firewall rules, so pick one per jail rather than stacking them.

Where CrowdSec earns its keep is scale and reach. Fail2ban only reacts to attacks it personally witnesses. CrowdSec's community blocklist means your server benefits from millions of other endpoints' bad experiences, not just your own. It also has a much wider library of pre-built detection scenarios — not just SSH, but Nginx, Apache, WordPress login pages, mail servers, and more — so if you're running several exposed services, one CrowdSec install with the right collections covers more ground than several separate Fail2ban jails.

I'd skip it if you're running a single home server that's never exposed to the internet at all. There's nothing wrong with installing it anyway for the learning value, but the community blocklist only pays off once something out there can actually reach you.

Prerequisites

  • A Proxmox VE host on 8.x or 9.x — nothing in this guide depends on a specific point release.
  • Root access to the Proxmox web interface and the node's Shell.
  • A Debian LXC template available locally. If you don't have one yet, download it from local (node)CT TemplatesTemplates, and grab the current Debian 12 standard image.
  • About 4 GB of free space on whatever storage you'll use for the container's disk.
  • Outbound internet access from the container, since both the install script and the community blocklist depend on it.
  • Roughly 15–20 minutes.

Step-by-Step Tutorial

1. Create the LXC container

In the Proxmox web UI, click your node and choose Create CT. Use these settings unless you have a reason to change them:

  • Hostname: crowdsec (or whatever fits your naming scheme)
  • Template: debian-12-standard
  • Disk: 4 GB is plenty for the engine, its rule hub, and logs
  • Cores: 1 — CrowdSec is not CPU-hungry unless you throw a huge log volume at it
  • Memory: 1024 MB, with 512 MB swap
  • Network: DHCP on vmbr0 is fine to start; switch to a static IP later if you want
  • Leave Unprivileged container checked

Start the container once it's created, then open its console or run pct enter <vmid> from the Proxmox shell to get a root prompt inside it.

2. Update the container and install prerequisites

apt update && apt full-upgrade -y
apt install -y curl gnupg openssh-server

The minimal Debian template doesn't ship curl by default, and you'll need it for the install script. Installing openssh-server here gives you something concrete for CrowdSec to actually protect once it's running — skip it if you already have a service in mind.

3. Add the CrowdSec repository and install the engine

curl -s https://install.crowdsec.net | sh
apt install crowdsec -y

The first command adds CrowdSec's official APT repository and signing key; it does not install the package itself, so don't skip the second line. During install, CrowdSec's post-install setup scans the container for known services (it'll notice sshd if you installed it above) and automatically installs the matching collection — in this case crowdsecurity/sshd, a bundle of parsers and scenarios tuned for SSH brute-force patterns.

4. Confirm the engine is running

systemctl status crowdsec

You're looking for active (running). If it's there, the agent is already tailing the journal and evaluating log lines against whatever collections got installed.

5. Check what's actually installed

cscli hub list

cscli is CrowdSec's command-line tool, and you'll use it constantly. This command lists every parser, scenario, and collection currently active. You should see crowdsecurity/sshd and its dependencies (crowdsecurity/linux, crowdsecurity/syslog-logs, and similar) listed as installed.

6. Install a bouncer

apt install crowdsec-firewall-bouncer-nftables -y

This is the piece that turns detections into actual blocks. The installer generates an API key automatically and registers the bouncer with the local engine, so on a fresh install there's usually nothing extra to configure — check systemctl status crowdsec-firewall-bouncer to confirm it started. If you're on an older system still using pure iptables instead of nftables, swap in crowdsec-firewall-bouncer-iptables instead.

7. Verify the bouncer registered correctly

cscli bouncers list

You should see one entry with a recent Last API pull timestamp. If that column is empty or stuck, the bouncer isn't actually talking to the engine yet — see the troubleshooting section below.

8. Test a ban without waiting for a real attacker

cscli decisions add --ip 203.0.113.50 --duration 4h --reason "manual test"
cscli decisions list

203.0.113.50 is a documentation-only address that will never be real traffic, which makes it safe to use here. You should see it appear in the decisions list within a second or two, and a matching drop rule show up in the firewall (nft list ruleset | grep crowdsec if you want to see it directly). Clean it up once you've confirmed it worked:

cscli decisions delete --ip 203.0.113.50

Commands Explained

CommandWhat it does
cscli hub listShows every parser, scenario, and collection currently installed and active
cscli hub upgradePulls the latest versions of your installed collections from the CrowdSec hub
cscli metricsDisplays counters for log lines parsed, scenarios matched, and decisions made — the fastest way to tell if it's doing anything at all
cscli decisions listLists every currently active ban, where it came from (local or the community blocklist), and when it expires
cscli bouncers listShows every registered bouncer and when it last checked in with the engine
cscli lapi statusConfirms the local API (the engine's internal API that bouncers and cscli talk to) is reachable and healthy

Common Errors

A few things trip people up more than anything else during setup:

  • crowdsec.service fails with a bind error on port 8080. The local API listens on 127.0.0.1:8080 by default. If something else in the container is already using that port, the engine won't start. Check with ss -tlnp | grep 8080 and either stop the conflicting process or change the LAPI port in /etc/crowdsec/config.yaml.
  • cscli bouncers list shows an empty "Last API pull" column. This almost always means the bouncer's API key in /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml doesn't match what the engine has on record, usually because the bouncer was reinstalled or the key got edited by hand. Regenerate it with cscli bouncers add firewall-bouncer and paste the new key into that file.
  • Nothing shows up in cscli metrics even after failed logins. Almost always a missing or wrong acquisition source. Check /etc/crowdsec/acquis.yaml and confirm it actually points at journalctl or the log file you expect, then restart the service.
  • Enrollment or CAPI sync fails. This usually just means the container can't reach the internet. Run curl -I https://api.crowdsec.net from inside the container and fix whatever's blocking outbound traffic (a firewall rule on the container's network interface is the usual culprit).

Troubleshooting

Start with the logs. journalctl -u crowdsec -f will show you exactly what the engine is doing in real time, including every scenario it evaluates and every decision it creates. If the service won't start at all, journalctl -u crowdsec -n 50 usually shows the actual error in the last few lines rather than making you guess.

If decisions are being created but nothing's actually getting blocked, the problem is almost always on the bouncer side, not the engine. Confirm the bouncer service is running, confirm its API key matches, and check nft list ruleset (or iptables -L -n on the iptables variant) to see whether the bouncer's chain even exists. A bouncer that's running but never registered won't insert any rules at all.

If you're testing from your own IP and manage to lock yourself out, you can always remove the decision from inside the container's console (which bypasses the network entirely): cscli decisions delete --ip <your-ip>.

Best Practices

Keep your collections current with cscli hub upgrade every so often — scenarios get refined as attack patterns change, and an old ruleset misses things a current one would catch. Don't expose the local API port (8080) outside the container; it should only ever be reachable from the bouncer running alongside it.

If you decide to run CrowdSec on more than one machine in your homelab, look into running one central engine and multiple remote bouncers rather than a full engine on every box. It's more efficient and gives you one place to check decisions instead of several. And don't forget the container itself needs backups like anything else — /etc/crowdsec holds your configuration and API keys, and losing it means starting enrollment over from scratch.

Frequently Asked Questions

Does CrowdSec replace Fail2ban?

Functionally, yes, for most use cases. There's no reason to run both against the same service at once.

Is CrowdSec free?

The engine, bouncers, and community blocklist are all free and open source. Paid tiers exist for teams that want a hosted console with more history and alerting, but nothing in this guide requires one.

Does the container need a public IP?

No. It needs outbound internet access to reach the CrowdSec API and pull the community blocklist, but it doesn't need to be reachable from the internet itself unless the service it's protecting is.

What happens to existing bans if the container reboots?

Decisions are stored in a local SQLite database, so they survive a reboot. The bouncer re-syncs with the engine and reapplies its firewall rules automatically on startup.

Can I run CrowdSec directly on the Proxmox host instead of a separate container?

You can, and the install steps are identical. Putting it in its own LXC container instead just keeps it isolated from the host and easy to snapshot, back up, or rebuild without touching anything else.

Conclusion

What you've got now is a small, low-resource container quietly watching your SSH log, ready to block anyone who trips a scenario, and backed by a blocklist that grows every time someone else on the internet gets attacked first. That's a meaningfully different security posture than a purely local tool like Fail2ban, for not much more setup effort.

From here, the natural next step is pointing CrowdSec at whatever else you're exposing — a reverse proxy, a web app's login page, anything with a log file worth watching. The collection hub has pre-built scenarios for most of the common ones, and the install pattern is the same every time: add the collection, confirm it's parsing, let the bouncer do the rest.