Protect a Django Server With CrowdSec
Set up CrowdSec to protect a Django server: install the agent and Nginx or firewall bouncer, parse Nginx and Django login logs, detect brute force on /admin and login views, share blocklists, and avoid banning legitimate users behind proxies.
Problem statement
Your Django server's logs show constant scanning: requests for /wp-login.php, .env files, and repeated failed logins against /admin/. fail2ban can ban IPs from log patterns, but you write and maintain every rule yourself and only see attacks on your own server. CrowdSec parses logs with community-maintained scenarios, bans offenders through bouncers (firewall, Nginx, Cloudflare), and shares signals so you also block IPs that attacked other servers.
- Install CrowdSec from its repository; it detects Nginx and SSH logs automatically.
- Add collections:
crowdsecurity/nginx,crowdsecurity/http-cve, andcrowdsecurity/base-http-scenarios. - Install a bouncer:
crowdsec-firewall-bouncer-nftables(or the Nginx bouncer). - Log failed Django logins with the client IP and add a custom scenario for login brute force.
- Make sure logs contain the real client IP when you are behind a proxy or CDN, and whitelist your own addresses.
Step-by-step solution
1. Install the agent
curl -s https://install.crowdsec.net | sudo sh
sudo apt-get install -y crowdsec
sudo cscli collections install crowdsecurity/nginx crowdsecurity/base-http-scenarios crowdsecurity/http-cve
sudo systemctl reload crowdsec
Read the install script before running it on production. cscli metrics shows which log files are read and how many lines are parsed. The HTTP scenarios detect probing for sensitive paths, bad user agents, and known CVE exploit attempts.
2. Install a bouncer
sudo apt-get install -y crowdsec-firewall-bouncer-nftables
sudo cscli bouncers list
The firewall bouncer drops traffic from banned IPs at the kernel level before it reaches Nginx or Gunicorn. The Nginx bouncer (Lua-based) can show a captcha instead of a hard ban. Behind Cloudflare, the Cloudflare bouncer pushes decisions to Cloudflare's firewall, because your server only sees Cloudflare's IPs.
3. Log Django login failures
Nginx logs show every request but not whether a login succeeded. Log failures from Django:
import logging
from django.contrib.auth.signals import user_login_failed
from django.dispatch import receiver
auth_logger = logging.getLogger("django.auth.failures")
@receiver(user_login_failed)
def log_failed_login(sender, credentials, request, **kwargs):
ip = request.META.get("REMOTE_ADDR") if request else "-"
auth_logger.warning("login failed ip=%s user=%s", ip, credentials.get("username", "-"))
Write this logger to a file such as /var/log/shop/auth.log (or journald) and add it to CrowdSec's acquisition:
# /etc/crowdsec/acquis.d/django.yaml
filenames:
- /var/log/shop/auth.log
labels:
type: django-auth
4. Add a scenario for login brute force
Create a parser that extracts ip from type: django-auth lines, and a leaky-bucket scenario:
# /etc/crowdsec/scenarios/django-login-bf.yaml
type: leaky
name: local/django-login-bf
description: "Brute force on Django login"
filter: "evt.Meta.log_type == 'django_failed_auth'"
groupby: evt.Meta.source_ip
capacity: 5
leakspeed: "1m"
blackhole: 5m
labels:
remediation: true
This bans an IP after more than five failures in a short period. Test with cscli explain --file /var/log/shop/auth.log --type django-auth.
5. Get the real client IP and whitelist yourself
Behind a load balancer or Cloudflare, REMOTE_ADDR and Nginx's $remote_addr are the proxy's address unless you configure the real IP module (set_real_ip_from, real_ip_header). Banning the proxy blocks everyone. Add your office, VPN, and monitoring IPs to a whitelist parser so health checks and staff are never banned.
Explanation
CrowdSec separates detection from enforcement. The agent reads logs, normalizes them with parsers, and feeds events into scenarios, mostly leaky buckets that overflow when too many matching events arrive from one source. Overflows produce decisions (ban, captcha) stored in the local API, and bouncers pull those decisions and enforce them. Opting into the community blocklist adds IPs reported by other CrowdSec users, so known attackers are blocked before they reach you.
Edge cases / notes
- Shared IPs. Corporate NATs and mobile carriers put many users behind one IP; keep ban durations short for HTTP scenarios.
- Containers. Run CrowdSec on the host reading container logs, or as a container with access to the log files; bouncers must act on the host firewall or proxy.
- Django rate limiting. CrowdSec complements, not replaces, app-level protection like django-axes or rate limits.
Next steps
- Compare with Protect Django Login With fail2ban.
- Add edge protection with Protect Django From Bots With a WAF.
- Fix client IP handling with Fix Django Wrong Client IP Behind a Proxy.
FAQ
Is CrowdSec a replacement for fail2ban?
It covers the same job with community scenarios, multiple bouncer types, and shared blocklists. Many teams switch, some run both briefly.
Can CrowdSec detect Django admin brute force?
With a log of failed logins and a custom scenario, yes. Nginx logs alone cannot tell failed from successful logins.
Will CrowdSec block legitimate users?
It can if logs show proxy IPs instead of real clients or if staff IPs are not whitelisted. Fix both before enforcing.
Does it work behind Cloudflare?
Yes, with the Cloudflare bouncer, which applies bans at Cloudflare's edge.