<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mordecai </title>
    <description>The latest articles on DEV Community by Mordecai  (@mordecai_amehson).</description>
    <link>https://dev.to/mordecai_amehson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1155526%2F571a50ca-44ca-4bc9-93a1-9339de58110c.jpeg</url>
      <title>DEV Community: Mordecai </title>
      <link>https://dev.to/mordecai_amehson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mordecai_amehson"/>
    <language>en</language>
    <item>
      <title>How I Built a Real-Time DDoS Detection Engine from Scratch</title>
      <dc:creator>Mordecai </dc:creator>
      <pubDate>Mon, 27 Apr 2026 03:12:54 +0000</pubDate>
      <link>https://dev.to/mordecai_amehson/how-i-built-a-real-time-ddos-detection-engine-from-scratch-cll</link>
      <guid>https://dev.to/mordecai_amehson/how-i-built-a-real-time-ddos-detection-engine-from-scratch-cll</guid>
      <description>&lt;p&gt;Imagine you run a cloud storage platform serving thousands of users. One day, an attacker floods your server with millions of requests per second. Your server crashes. Real users can't access their files. You lose money and trust.&lt;br&gt;
This is a DDoS attack — Distributed Denial of Service. The goal of this project was to build a tool that detects these attacks automatically and blocks them before they cause damage.&lt;br&gt;
No off-the-shelf tools. No Fail2Ban. Pure Python, built from scratch.&lt;/p&gt;

&lt;p&gt;What the Tool Does&lt;br&gt;
Here's the full picture of what I built:&lt;br&gt;
Nginx (logs every request as JSON)&lt;br&gt;
         ↓&lt;br&gt;
Detector daemon reads logs in real time&lt;br&gt;
         ↓&lt;br&gt;
Sliding window tracks request rates&lt;br&gt;
         ↓&lt;br&gt;
Baseline learns what normal traffic looks like&lt;br&gt;
         ↓&lt;br&gt;
Anomaly detector compares current rate to baseline&lt;br&gt;
         ↓&lt;br&gt;
If anomalous → block IP with iptables + send Slack alert&lt;br&gt;
         ↓&lt;br&gt;
Auto-unban after cooldown period&lt;br&gt;
Everything runs continuously as a background service on a Linux server.&lt;/p&gt;

&lt;p&gt;Part 1: Reading Nginx Logs in Real Time&lt;br&gt;
The first challenge was getting the tool to watch incoming traffic live. Nginx was configured to write every HTTP request as a JSON line to a log file:&lt;br&gt;
json{&lt;br&gt;
  "source_ip": "102.91.99.217",&lt;br&gt;
  "timestamp": "2026-04-27T02:31:00+00:00",&lt;br&gt;
  "method": "GET",&lt;br&gt;
  "path": "/",&lt;br&gt;
  "status": 200,&lt;br&gt;
  "response_size": 6674&lt;br&gt;
}&lt;br&gt;
To read this in real time, I used a technique called log tailing — the same thing tail -f does in Linux. The program opens the file, jumps to the end, and then sits in a loop reading new lines as they appear:&lt;br&gt;
pythonwith open(log_path, "r") as f:&lt;br&gt;
    f.seek(0, 2)  # jump to end of file&lt;br&gt;
    while True:&lt;br&gt;
        line = f.readline()&lt;br&gt;
        if not line:&lt;br&gt;
            time.sleep(0.1)  # wait for new data&lt;br&gt;
            continue&lt;br&gt;
        yield parse_line(line)  # process the line&lt;br&gt;
Every time Nginx writes a new request, the detector picks it up within 100 milliseconds.&lt;/p&gt;

&lt;p&gt;Part 2: The Sliding Window&lt;br&gt;
Now that we're reading requests in real time, we need to know how fast each IP is sending requests. This is where the sliding window comes in.&lt;br&gt;
A sliding window answers the question: "How many requests has this IP sent in the last 60 seconds?"&lt;br&gt;
The naive approach would be a counter that resets every minute. But that's inaccurate — an attacker could send 1000 requests in the last 10 seconds of one minute and the first 10 seconds of the next, and the counter would never catch it.&lt;br&gt;
Instead, I used Python's collections.deque — a double-ended queue that lets us add to one end and remove from the other efficiently.&lt;br&gt;
Here's how it works:&lt;br&gt;
pythonfrom collections import deque&lt;br&gt;
import time&lt;/p&gt;

&lt;p&gt;ip_window = deque()  # stores timestamps of recent requests&lt;/p&gt;

&lt;p&gt;def record_request(ip):&lt;br&gt;
    now = time.time()&lt;br&gt;
    cutoff = now - 60  # 60 second window&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Add current timestamp
ip_window.append(now)

# Evict timestamps older than 60 seconds from the left
while ip_window and ip_window[0] &amp;lt; cutoff:
    ip_window.popleft()

# Rate = number of requests in window / window size
rate = len(ip_window) / 60
return rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Every time a request comes in, we add its timestamp. Every time we check the rate, we first remove any timestamps older than 60 seconds from the left side of the deque. The rate is simply the count of remaining timestamps divided by 60.&lt;br&gt;
This gives us an accurate, always up-to-date requests-per-second count for every IP on the server.&lt;/p&gt;

&lt;p&gt;Part 3: The Baseline — Teaching the Tool What "Normal" Looks Like&lt;br&gt;
Knowing the current rate isn't enough. We need to know if that rate is unusual.&lt;br&gt;
At 3am, 5 requests per second might be suspicious. At noon, it might be completely normal. The tool needs to learn from actual traffic patterns — not from hardcoded values.&lt;br&gt;
This is the rolling baseline. Here's how it works:&lt;/p&gt;

&lt;p&gt;Every second, we record how many requests the server received that second&lt;br&gt;
We keep a 30-minute history of these per-second counts&lt;br&gt;
Every 60 seconds, we calculate the mean (average) and standard deviation of these counts&lt;/p&gt;

&lt;p&gt;pythonsamples = [count for _, count in rolling_window]&lt;/p&gt;

&lt;p&gt;mean = sum(samples) / len(samples)&lt;br&gt;
variance = sum((x - mean) ** 2 for x in samples) / len(samples)&lt;br&gt;
stddev = math.sqrt(variance)&lt;br&gt;
The mean tells us what a typical second looks like. The standard deviation tells us how much variation is normal.&lt;br&gt;
I also maintain per-hour slots — separate baselines for each hour of the day. If the current hour has enough data (at least 5 samples), I prefer that over the general baseline. This means the tool naturally adapts to rush hours vs quiet hours.&lt;br&gt;
To prevent the tool from failing on a fresh start with no data, I set floor values:&lt;/p&gt;

&lt;p&gt;Minimum mean: 0.1 req/s&lt;br&gt;
Minimum stddev: 0.1&lt;/p&gt;

&lt;p&gt;Part 4: Detecting Anomalies&lt;br&gt;
With a baseline established, detection becomes a statistical question: "Is this IP's current rate unusually high compared to normal?"&lt;br&gt;
I use two detection methods — whichever fires first:&lt;br&gt;
Method 1: Z-Score&lt;br&gt;
The z-score measures how many standard deviations above the mean a value is:&lt;br&gt;
pythonz_score = (current_rate - baseline_mean) / baseline_stddev&lt;br&gt;
If the z-score exceeds 1.5, the IP is anomalous. A z-score of 1.5 means the rate is 1.5 standard deviations above normal — statistically unusual.&lt;br&gt;
For example:&lt;/p&gt;

&lt;p&gt;Baseline mean: 1.0 req/s&lt;br&gt;
Baseline stddev: 0.5&lt;br&gt;
Current rate: 2.5 req/s&lt;br&gt;
Z-score: (2.5 - 1.0) / 0.5 = 3.0 → anomalous!&lt;/p&gt;

&lt;p&gt;Method 2: Rate Multiplier&lt;br&gt;
Sometimes the stddev is very small and the z-score math doesn't capture obvious spikes. So I also check if the rate is more than 1.5x the baseline mean:&lt;br&gt;
pythonif current_rate &amp;gt; 1.5 * baseline_mean:&lt;br&gt;
    # flag as anomalous&lt;br&gt;
Error Rate Tightening&lt;br&gt;
If an IP is sending a lot of 4xx or 5xx errors (bad requests, unauthorized attempts), I automatically tighten the thresholds by 30%. An IP probing for vulnerabilities gets less tolerance.&lt;/p&gt;

&lt;p&gt;Part 5: Blocking with iptables&lt;br&gt;
When an IP is flagged as anomalous, we block it at the kernel level using iptables. This is more powerful than blocking at the application level because the packets are dropped before they even reach Nginx.&lt;br&gt;
pythonimport subprocess&lt;/p&gt;

&lt;p&gt;def ban_ip(ip):&lt;br&gt;
    subprocess.run([&lt;br&gt;
        "iptables", "-I", "INPUT", "-s", ip, "-j", "DROP"&lt;br&gt;
    ])&lt;br&gt;
The -I INPUT inserts the rule at the top of the INPUT chain. -j DROP silently drops all packets from that IP. The attacker's requests never even reach the server.&lt;br&gt;
You can verify bans are active with:&lt;br&gt;
bashsudo iptables -L INPUT -n&lt;br&gt;
Auto-Unban with Backoff Schedule&lt;br&gt;
Permanent bans aren't always appropriate — the IP might be a legitimate user who got flagged by mistake. So I implemented an automatic unban system with a backoff schedule:&lt;/p&gt;

&lt;p&gt;First offence: banned for 10 minutes&lt;br&gt;
Second offence: banned for 30 minutes&lt;br&gt;
Third offence: banned for 2 hours&lt;br&gt;
Fourth offence: permanently banned&lt;/p&gt;

&lt;p&gt;Each time an IP is unbanned, a Slack notification is sent with the next ban duration if they reoffend.&lt;/p&gt;

&lt;p&gt;Part 6: Slack Alerts&lt;br&gt;
Every ban and unban sends an immediate Slack notification via webhook:&lt;br&gt;
pythonrequests.post(webhook_url, json={&lt;br&gt;
    "text": f"🚨 IP BANNED: {ip}\nRate: {rate} req/s\nBaseline: {mean} req/s\nDuration: {duration}"&lt;br&gt;
})&lt;br&gt;
The alert includes the condition that fired, the current rate, the baseline, and the ban duration — everything needed to understand what happened without digging through logs.&lt;/p&gt;

&lt;p&gt;Part 7: The Live Dashboard&lt;br&gt;
The tool serves a web dashboard on port 8080 that refreshes every 3 seconds showing:&lt;/p&gt;

&lt;p&gt;Global requests per second&lt;br&gt;
Current baseline mean and stddev&lt;br&gt;
List of banned IPs with reasons&lt;br&gt;
Top 10 source IPs&lt;br&gt;
CPU and memory usage&lt;/p&gt;

&lt;p&gt;Built with pure Python's http.server — no frameworks needed.&lt;/p&gt;

&lt;p&gt;What I Learned&lt;br&gt;
Building this from scratch taught me things no tutorial ever could:&lt;/p&gt;

&lt;p&gt;Statistical anomaly detection is surprisingly approachable once you understand z-scores&lt;br&gt;
deque is one of the most useful Python data structures for time-based problems&lt;br&gt;
iptables is incredibly powerful — blocking at kernel level is orders of magnitude more efficient than application-level blocking&lt;br&gt;
Baselines must be dynamic — hardcoded thresholds always fail in production because traffic patterns change by hour, day, and season&lt;br&gt;
A daemon is not a cron job — continuous processing requires careful thought about memory, threading, and graceful shutdown&lt;/p&gt;

&lt;p&gt;The Stack&lt;/p&gt;

&lt;p&gt;Python 3.12 — detector daemon&lt;br&gt;
Docker + Docker Compose — Nextcloud and Nginx deployment&lt;br&gt;
Nginx — reverse proxy with JSON access logging&lt;br&gt;
iptables — kernel-level IP blocking&lt;br&gt;
Slack webhooks — real-time alerts&lt;br&gt;
systemd — keeps the daemon running persistently&lt;/p&gt;

&lt;p&gt;Repository&lt;br&gt;
The full source code is available at:&lt;br&gt;
&lt;a href="https://github.com/Hacker-Dark/hng-stage3-devops" rel="noopener noreferrer"&gt;https://github.com/Hacker-Dark/hng-stage3-devops&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built as part of the HNG DevOps Internship Stage 3 task.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
