<?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: Damilola Ejalonibu</title>
    <description>The latest articles on DEV Community by Damilola Ejalonibu (@damilola_ejalonibu_7f5cfd).</description>
    <link>https://dev.to/damilola_ejalonibu_7f5cfd</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%2F3904973%2F08504e82-ce91-47d6-ba0b-c56422dbd455.jpg</url>
      <title>DEV Community: Damilola Ejalonibu</title>
      <link>https://dev.to/damilola_ejalonibu_7f5cfd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/damilola_ejalonibu_7f5cfd"/>
    <language>en</language>
    <item>
      <title>How I Built a Real-Time DDoS Detection Engine from Scratch</title>
      <dc:creator>Damilola Ejalonibu</dc:creator>
      <pubDate>Wed, 29 Apr 2026 20:01:01 +0000</pubDate>
      <link>https://dev.to/damilola_ejalonibu_7f5cfd/how-i-built-a-real-time-ddos-detection-engine-from-scratch-54f4</link>
      <guid>https://dev.to/damilola_ejalonibu_7f5cfd/how-i-built-a-real-time-ddos-detection-engine-from-scratch-54f4</guid>
      <description>&lt;p&gt;**Section 1 — Introduction&lt;/p&gt;

&lt;p&gt;What Is This Project and Why Does It Matter?&lt;/p&gt;

&lt;p&gt;Imagine you run a website that thousands of people use every day.&lt;br&gt;
One afternoon, an attacker decides to flood your server with millions&lt;br&gt;
of fake requests, slowing it down for everyone else. This is called&lt;br&gt;
a DDoS attack — Distributed Denial of Service.&lt;/p&gt;

&lt;p&gt;At HNG, I was given the task of building a tool that watches all&lt;br&gt;
incoming traffic to a Nextcloud server in real time, learns what&lt;br&gt;
normal traffic looks like, and automatically blocks attackers the&lt;br&gt;
moment something looks suspicious.&lt;/p&gt;

&lt;p&gt;No third party tools. No shortcuts. Just Python, Linux, and logic.&lt;/p&gt;

&lt;p&gt;Here is how I built it.&lt;/p&gt;

&lt;p&gt;**Section 2 — The sliding window&lt;/p&gt;

&lt;p&gt;How the Sliding Window Works&lt;/p&gt;

&lt;p&gt;The first problem I had to solve was: how do I track how many&lt;br&gt;
requests are arriving right now?&lt;/p&gt;

&lt;p&gt;The answer is a sliding window — a data structure that always&lt;br&gt;
shows you the last 60 seconds of traffic, no matter when you look.&lt;/p&gt;

&lt;p&gt;I used Python's &lt;code&gt;deque&lt;/code&gt; (double-ended queue) to build it.&lt;br&gt;
Think of it like a conveyor belt at a supermarket checkout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New items (requests) are added to the RIGHT end every second&lt;/li&gt;
&lt;li&gt;Old items older than 60 seconds are removed from the LEFT end&lt;/li&gt;
&lt;li&gt;At any moment, the belt only holds the last 60 seconds of data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the core idea in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_request&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Evict entries older than 60 seconds from the left
&lt;/span&gt;    &lt;span class="n"&gt;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cutoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_rate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I maintained two of these windows — one tracking global traffic&lt;br&gt;
across all IPs, and one per individual IP address. This lets me&lt;br&gt;
detect both a single aggressive attacker and a distributed attack&lt;br&gt;
spread across many IPs.&lt;/p&gt;

&lt;p&gt;**Section 3 — The baseline&lt;/p&gt;

&lt;p&gt;How the Baseline Learns From Traffic&lt;/p&gt;

&lt;p&gt;Knowing the current rate is not enough. I also need to know&lt;br&gt;
what NORMAL looks like so I can compare.&lt;/p&gt;

&lt;p&gt;If a street normally has 5 cars per minute and suddenly 50 show up,&lt;br&gt;
that is suspicious. But if it normally has 40 cars, then 50 is fine.&lt;/p&gt;

&lt;p&gt;I built a rolling baseline that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keeps a 30-minute history of per-second request counts&lt;/li&gt;
&lt;li&gt;Recalculates the mean (average) and stddev every 60 seconds&lt;/li&gt;
&lt;li&gt;Stores results per hour of the day (hour 14, hour 15, etc.)&lt;/li&gt;
&lt;li&gt;Prefers the current hour's data when enough has been collected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mean tells me the typical rate. The standard deviation tells&lt;br&gt;
me how much traffic normally varies. Together they let me judge&lt;br&gt;
whether current traffic is unusual.&lt;/p&gt;

&lt;p&gt;I also set floor values — the mean never drops below 1.0 and&lt;br&gt;
stddev never drops below 0.5. This prevents division by zero&lt;br&gt;
and stops the system from being too trigger-happy when traffic&lt;br&gt;
is very low.&lt;/p&gt;

&lt;p&gt;**Section 4 — The detection logic&lt;/p&gt;

&lt;p&gt;How the Detection Logic Makes a Decision&lt;/p&gt;

&lt;p&gt;With the current rate and the baseline ready, the detector&lt;br&gt;
asks two questions for every incoming request:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 1 — Z-score check&lt;/strong&gt;&lt;br&gt;
The z-score measures how many standard deviations above normal&lt;br&gt;
the current rate is. The formula is:&lt;/p&gt;

&lt;p&gt;z = (current_rate - mean) / stddev&lt;/p&gt;

&lt;p&gt;If the baseline mean is 5 req/s and stddev is 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 req/s → z = (7-5)/2 = 1.0 → normal&lt;/li&gt;
&lt;li&gt;11 req/s → z = (11-5)/2 = 3.0 → borderline&lt;/li&gt;
&lt;li&gt;20 req/s → z = (20-5)/2 = 7.5 → attack!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the z-score exceeds 3.0, an anomaly is flagged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question 2 — Rate multiplier check&lt;/strong&gt;&lt;br&gt;
If the current rate is more than 5 times the baseline mean,&lt;br&gt;
flag it regardless of the z-score. This catches sudden extreme&lt;br&gt;
spikes even before the baseline has enough data.&lt;/p&gt;

&lt;p&gt;Whichever check fires first wins.&lt;/p&gt;

&lt;p&gt;There is also an error surge check — if an IP's 4xx/5xx error&lt;br&gt;
rate is 3 times the baseline error rate, the detection threshold&lt;br&gt;
is tightened automatically, making it easier to catch that IP.&lt;/p&gt;

&lt;p&gt;**Section 5 — How iptables blocks an IP&lt;/p&gt;

&lt;p&gt;How iptables Blocks an Attacker&lt;/p&gt;

&lt;p&gt;Once an anomaly is detected, the tool needs to actually stop&lt;br&gt;
the attacker from sending more traffic. I used iptables for this&lt;br&gt;
— a built-in Linux firewall that operates at the kernel level.&lt;/p&gt;

&lt;p&gt;Think of iptables as a bouncer standing at the door of your server.&lt;br&gt;
You can give it a list of rules — "if a packet comes from this IP,&lt;br&gt;
drop it before it even reaches Nginx."&lt;/p&gt;

&lt;p&gt;When an attacker is detected, my tool runs this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-I&lt;/span&gt; INPUT &lt;span class="nt"&gt;-s&lt;/span&gt; 1.2.3.4 &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking that down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-I INPUT&lt;/code&gt; — insert this rule at the top of the incoming traffic list&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-s 1.2.3.4&lt;/code&gt; — match packets from this source IP&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-j DROP&lt;/code&gt; — silently discard them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The attacker's requests never reach Nginx or Nextcloud.&lt;br&gt;
They just disappear into nothing.&lt;/p&gt;

&lt;p&gt;The ban follows a backoff schedule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First offense → 10 minute ban&lt;/li&gt;
&lt;li&gt;Second offense → 30 minute ban&lt;/li&gt;
&lt;li&gt;Third offense → 2 hour ban&lt;/li&gt;
&lt;li&gt;Fourth offense → permanent ban&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the timer expires, the tool automatically removes the rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-D&lt;/span&gt; INPUT &lt;span class="nt"&gt;-s&lt;/span&gt; 1.2.3.4 &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every ban and unban sends a Slack notification so I always know&lt;br&gt;
what is happening on my server in real time.&lt;/p&gt;

&lt;p&gt;**Section 6 — Closing&lt;/p&gt;

&lt;p&gt;Wrapping Up&lt;/p&gt;

&lt;p&gt;Building this project taught me a lot about how real security&lt;br&gt;
tooling works under the hood. The key lessons were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A sliding window is just a deque with eviction logic&lt;/li&gt;
&lt;li&gt;A baseline is just a mean and stddev recalculated over time&lt;/li&gt;
&lt;li&gt;A z-score is just a way of asking "how unusual is this number?"&lt;/li&gt;
&lt;li&gt;iptables is just Linux telling itself to ignore certain packets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these concepts are complicated once you strip away the&lt;br&gt;
jargon. Security tooling is just math plus system administration.&lt;/p&gt;

&lt;p&gt;The full source code is available on GitHub:&lt;br&gt;
[&lt;a href="https://github.com/ejalonibudamilola/hng-anomaly-detector.git" rel="noopener noreferrer"&gt;https://github.com/ejalonibudamilola/hng-anomaly-detector.git&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The live dashboard is running at:&lt;br&gt;
[&lt;a href="http://hng-detector.damiloladeborah.link:8080" rel="noopener noreferrer"&gt;http://hng-detector.damiloladeborah.link:8080&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;This project was built as part of the HNG14 DevOps track.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>python</category>
      <category>beginners</category>
      <category>security</category>
    </item>
  </channel>
</rss>
