<?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>Forem: Goteh Mbaza</title>
    <description>The latest articles on Forem by Goteh Mbaza (@goteh_mbaza_e513bdbf1871a).</description>
    <link>https://forem.com/goteh_mbaza_e513bdbf1871a</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%2F3900331%2Ffdf0ee82-e66c-4c9f-9264-d918ca6353e6.png</url>
      <title>Forem: Goteh Mbaza</title>
      <link>https://forem.com/goteh_mbaza_e513bdbf1871a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/goteh_mbaza_e513bdbf1871a"/>
    <language>en</language>
    <item>
      <title>How I Built a Real-Time HTTP Anomaly Detector for cloud.ng with Python, Nginx, Docker, and iptables</title>
      <dc:creator>Goteh Mbaza</dc:creator>
      <pubDate>Mon, 27 Apr 2026 11:32:59 +0000</pubDate>
      <link>https://forem.com/goteh_mbaza_e513bdbf1871a/-how-i-built-a-real-time-http-anomaly-detector-for-cloudng-with-python-nginx-docker-and-1d01</link>
      <guid>https://forem.com/goteh_mbaza_e513bdbf1871a/-how-i-built-a-real-time-http-anomaly-detector-for-cloudng-with-python-nginx-docker-and-1d01</guid>
      <description>&lt;p&gt;When a platform is public and always online, one of the biggest security questions is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do you know when traffic is normal, and when something suspicious is happening?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the goal of this project.&lt;/p&gt;

&lt;p&gt;I built a real-time anomaly detection engine for cloud.ng, a cloud storage platform powered by Nextcloud, that watches incoming HTTP traffic, learns what normal traffic looks like, detects unusual behavior, and reacts automatically.&lt;/p&gt;

&lt;p&gt;If one IP becomes abusive, the system blocks it with iptables. If the whole platform suddenly gets a global traffic spike, the system sends an alert to Slack.&lt;/p&gt;

&lt;p&gt;It also provides a live dashboard so you can watch traffic behavior in real time.&lt;/p&gt;

&lt;p&gt;In this post, I’ll explain how I built it in a beginner-friendly way.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this project does
&lt;/h2&gt;

&lt;p&gt;At a high level, the system works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user sends an HTTP request
&lt;/li&gt;
&lt;li&gt;Nginx receives the request first
&lt;/li&gt;
&lt;li&gt;Nginx forwards it to Nextcloud
&lt;/li&gt;
&lt;li&gt;Nginx writes the request into a JSON access log
&lt;/li&gt;
&lt;li&gt;A Python detector daemon reads that log continuously
&lt;/li&gt;
&lt;li&gt;The detector compares live traffic against a learned baseline
&lt;/li&gt;
&lt;li&gt;If traffic becomes abnormal, it blocks the IP or sends a Slack alert
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So instead of using a fixed hardcoded limit like “100 requests per minute,” this project tries to learn what normal looks like first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;A fixed limit is easy to write, but not always smart.&lt;/p&gt;

&lt;p&gt;Traffic at 2 a.m. is usually different from traffic at 2 p.m. Some endpoints naturally get bursts. Some spikes are harmless, and some are not.&lt;/p&gt;

&lt;p&gt;If your threshold is too low:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you block legitimate users
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your threshold is too high:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;suspicious traffic slips through
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why I used a rolling baseline instead of a static number.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack I used
&lt;/h2&gt;

&lt;p&gt;This project uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Compose
&lt;/li&gt;
&lt;li&gt;Nextcloud
&lt;/li&gt;
&lt;li&gt;Nginx
&lt;/li&gt;
&lt;li&gt;Python
&lt;/li&gt;
&lt;li&gt;iptables
&lt;/li&gt;
&lt;li&gt;Slack webhook
&lt;/li&gt;
&lt;li&gt;a live metrics dashboard
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Nextcloud image came from Docker Hub and was used exactly as required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;The traffic flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Internet Clients
      |
      v
Nginx Reverse Proxy
      |
      +--&amp;gt; Nextcloud
      |
      +--&amp;gt; JSON access logs
              |
              v
     Python Detector Daemon
        |       |       |
        v       v       v
    iptables  Slack  Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx and the detector share a Docker volume so the detector can read the live access log without modifying the application container.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Logging traffic with Nginx
&lt;/h2&gt;

&lt;p&gt;The detector needs reliable traffic data before it can make decisions.&lt;/p&gt;

&lt;p&gt;So I configured Nginx to log every request in JSON format with fields like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source IP
&lt;/li&gt;
&lt;li&gt;timestamp
&lt;/li&gt;
&lt;li&gt;method
&lt;/li&gt;
&lt;li&gt;path
&lt;/li&gt;
&lt;li&gt;status code
&lt;/li&gt;
&lt;li&gt;response size
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified example looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"203.0.113.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-27T09:25:51+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"612"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structured logs are much easier to parse safely than plain text.&lt;/p&gt;

&lt;p&gt;I also configured Nginx to trust and forward the real client IP using X-Forwarded-For, so the detector sees the actual request source.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Continuously reading logs with Python
&lt;/h2&gt;

&lt;p&gt;The detector is not a cron job and not a one-time script.&lt;/p&gt;

&lt;p&gt;It runs as a long-lived daemon and continuously tails the Nginx access log file.&lt;/p&gt;

&lt;p&gt;For every new line, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parses the JSON
&lt;/li&gt;
&lt;li&gt;extracts the traffic fields
&lt;/li&gt;
&lt;li&gt;updates request windows
&lt;/li&gt;
&lt;li&gt;updates baselines
&lt;/li&gt;
&lt;li&gt;checks whether the traffic looks anomalous
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means detection happens in near real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Using a sliding window with deques
&lt;/h2&gt;

&lt;p&gt;One of the most important parts of this project is the 60-second sliding window.&lt;/p&gt;

&lt;p&gt;I used Python deque objects because they are excellent for “keep the latest items, remove the oldest items” logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I tracked
&lt;/h3&gt;

&lt;p&gt;I kept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one global request deque
&lt;/li&gt;
&lt;li&gt;one per-IP request deque
&lt;/li&gt;
&lt;li&gt;one per-IP error deque for 4xx and 5xx responses
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;When a request arrives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;append the current timestamp to the relevant deque
&lt;/li&gt;
&lt;li&gt;remove any timestamps older than 60 seconds
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives a true moving view of the latest traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;A simple “requests per minute” counter resets at fixed minute boundaries, which can hide short bursts.&lt;/p&gt;

&lt;p&gt;A sliding window answers the better question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How much traffic happened in the last 60 seconds right now?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is much better for anomaly detection.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Teaching the baseline to learn from traffic
&lt;/h2&gt;

&lt;p&gt;A sliding window shows what is happening now, but it does not tell us whether that traffic is unusual.&lt;/p&gt;

&lt;p&gt;For that, I built a rolling baseline manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the baseline tracks
&lt;/h3&gt;

&lt;p&gt;The baseline stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-second request counts
&lt;/li&gt;
&lt;li&gt;per-second error counts
&lt;/li&gt;
&lt;li&gt;a rolling 30-minute history
&lt;/li&gt;
&lt;li&gt;hourly traffic slots
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What gets recalculated
&lt;/h3&gt;

&lt;p&gt;Every 60 seconds, the detector recalculates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mean requests per second
&lt;/li&gt;
&lt;li&gt;standard deviation
&lt;/li&gt;
&lt;li&gt;error rate
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why idle seconds matter
&lt;/h3&gt;

&lt;p&gt;One important detail was making sure quiet seconds are also included.&lt;/p&gt;

&lt;p&gt;If you only record seconds where traffic exists, the average becomes artificially high. Then the system thinks normal traffic is busier than it really is.&lt;/p&gt;

&lt;p&gt;So I made sure the baseline includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;active seconds
&lt;/li&gt;
&lt;li&gt;idle seconds with zero traffic
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the learned average much more realistic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hour-slot preference
&lt;/h3&gt;

&lt;p&gt;Traffic usually changes throughout the day.&lt;/p&gt;

&lt;p&gt;So I added a rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the current hour has enough samples, use the current hour’s baseline
&lt;/li&gt;
&lt;li&gt;otherwise, fall back to the rolling 30-minute baseline
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps the detector adapt to time-of-day behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: How the detector makes decisions
&lt;/h2&gt;

&lt;p&gt;Once I had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a live request rate
&lt;/li&gt;
&lt;li&gt;a learned baseline
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I needed a way to decide whether traffic is abnormal.&lt;/p&gt;

&lt;p&gt;I used two checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Z-score
&lt;/h3&gt;

&lt;p&gt;The z-score answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How far is the current traffic from the normal average, measured in standard deviations?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A high z-score means traffic is statistically unusual.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rate multiplier
&lt;/h3&gt;

&lt;p&gt;I also added a simpler check:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is the current rate more than N times the learned average?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That catches obvious spikes even when the z-score is not dramatic yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detection rule
&lt;/h3&gt;

&lt;p&gt;A request pattern is considered anomalous if either fires first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;z-score exceeds threshold
&lt;/li&gt;
&lt;li&gt;current rate exceeds multiplier of baseline mean
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used this logic for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-IP traffic
&lt;/li&gt;
&lt;li&gt;global traffic
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: Tightening thresholds when errors surge
&lt;/h2&gt;

&lt;p&gt;Not all suspicious behavior is about volume alone.&lt;/p&gt;

&lt;p&gt;Sometimes an IP causes a lot of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;401
&lt;/li&gt;
&lt;li&gt;403
&lt;/li&gt;
&lt;li&gt;404
&lt;/li&gt;
&lt;li&gt;500
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That often suggests scanning, brute force, or probing.&lt;/p&gt;

&lt;p&gt;So I added an error surge rule.&lt;/p&gt;

&lt;p&gt;If an IP’s 4xx/5xx rate becomes much worse than its normal baseline, the detector automatically tightens its thresholds.&lt;/p&gt;

&lt;p&gt;That way, a suspicious IP gets less tolerance than a normal user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Blocking bad IPs with iptables
&lt;/h2&gt;

&lt;p&gt;When a per-IP anomaly is confirmed, the detector blocks the source IP using Linux iptables.&lt;/p&gt;

&lt;p&gt;The command is:&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; &amp;lt;ip-address&amp;gt; &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&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; 203.0.113.10 &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What this means
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-I INPUT&lt;/code&gt; inserts the rule into the input chain
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-s&lt;/code&gt; selects the source IP
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-j DROP&lt;/code&gt; silently drops all packets from that IP
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If traffic comes from this IP, ignore it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is useful because it stops abusive traffic at the firewall level.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Automatically unbanning IPs
&lt;/h2&gt;

&lt;p&gt;Blocking forever on a first offense is not always ideal.&lt;/p&gt;

&lt;p&gt;So I added a backoff-based unban system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;first ban: 10 minutes
&lt;/li&gt;
&lt;li&gt;second ban: 30 minutes
&lt;/li&gt;
&lt;li&gt;third ban: 2 hours
&lt;/li&gt;
&lt;li&gt;fourth offense onward: permanent
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A background unban loop checks whether each active ban has expired.&lt;/p&gt;

&lt;p&gt;If a ban expires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the firewall rule is removed
&lt;/li&gt;
&lt;li&gt;the audit log records the release
&lt;/li&gt;
&lt;li&gt;Slack gets an unban notification
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 9: Sending Slack alerts
&lt;/h2&gt;

&lt;p&gt;The detector sends Slack notifications for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-IP bans
&lt;/li&gt;
&lt;li&gt;unbans
&lt;/li&gt;
&lt;li&gt;global anomaly alerts
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each alert includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the condition that fired
&lt;/li&gt;
&lt;li&gt;the current rate
&lt;/li&gt;
&lt;li&gt;the baseline
&lt;/li&gt;
&lt;li&gt;the timestamp
&lt;/li&gt;
&lt;li&gt;the ban duration if applicable
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes each notification immediately useful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 10: Building the live dashboard
&lt;/h2&gt;

&lt;p&gt;I also built a live dashboard that shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global requests per second
&lt;/li&gt;
&lt;li&gt;top source IPs
&lt;/li&gt;
&lt;li&gt;currently banned IPs
&lt;/li&gt;
&lt;li&gt;CPU usage
&lt;/li&gt;
&lt;li&gt;memory usage
&lt;/li&gt;
&lt;li&gt;uptime
&lt;/li&gt;
&lt;li&gt;effective baseline values
&lt;/li&gt;
&lt;li&gt;baseline graph over time
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This made testing much easier, because I could see how the detector was behaving without constantly reading raw logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: sliding window idea in Python
&lt;/h2&gt;

&lt;p&gt;Here is the basic idea behind the 60-second deque window:&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;requests&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;add_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="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;requests&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="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;requests&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;requests&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;requests&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;return&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;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tiny pattern is the core of the live request-rate logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problems I ran into
&lt;/h2&gt;

&lt;p&gt;This project also taught me that detection logic is only half the job. The other half is operational reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The baseline can learn the wrong thing
&lt;/h3&gt;

&lt;p&gt;If you attack too early, the detector can start treating attack traffic as normal.&lt;/p&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;warm the system with light traffic first
&lt;/li&gt;
&lt;li&gt;wait for a baseline recalculation
&lt;/li&gt;
&lt;li&gt;then run the burst
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Too much per-request logging
&lt;/h3&gt;

&lt;p&gt;Logging every request at INFO created too much output during heavy bursts.&lt;/p&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make request-by-request logging configurable
&lt;/li&gt;
&lt;li&gt;keep it off by default
&lt;/li&gt;
&lt;li&gt;keep audit events on
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Blocking my own SSH session
&lt;/h3&gt;

&lt;p&gt;At one point, I attacked from the same IP I used for SSH, and the detector correctly blocked that IP.&lt;/p&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add a whitelist for admin IPs
&lt;/li&gt;
&lt;li&gt;use a separate IP for attack traffic
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Capturing iptables state at the right time
&lt;/h3&gt;

&lt;p&gt;Sometimes the ban happened correctly, but the live iptables state was hard to catch.&lt;/p&gt;

&lt;p&gt;The fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automatically write iptables snapshots during BAN and UNBAN
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;This project helped me understand that security tooling is not just about rules.&lt;/p&gt;

&lt;p&gt;It is also about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;observability
&lt;/li&gt;
&lt;li&gt;realistic baselines
&lt;/li&gt;
&lt;li&gt;good logging
&lt;/li&gt;
&lt;li&gt;safe testing
&lt;/li&gt;
&lt;li&gt;automated response
&lt;/li&gt;
&lt;li&gt;collecting proof that your system actually worked
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also showed me how simple data structures like a deque can be powerful when used carefully.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;In the end, I built a system that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;monitor HTTP traffic in real time
&lt;/li&gt;
&lt;li&gt;learn what normal looks like
&lt;/li&gt;
&lt;li&gt;detect per-IP anomalies
&lt;/li&gt;
&lt;li&gt;detect global anomalies
&lt;/li&gt;
&lt;li&gt;block abusive IPs with iptables
&lt;/li&gt;
&lt;li&gt;notify Slack
&lt;/li&gt;
&lt;li&gt;automatically unban IPs
&lt;/li&gt;
&lt;li&gt;expose live metrics in a dashboard
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a beginner-friendly DevSecOps project, this was a great way to connect traffic monitoring, anomaly detection, alerting, and response in one real system.&lt;/p&gt;

&lt;p&gt;If you are learning security engineering or DevSecOps, this kind of project is a very practical way to understand how defensive controls work in production-style environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Live dashboard: &lt;a href="http://mbaza.duckdns.org:8081" rel="noopener noreferrer"&gt;http://mbaza.duckdns.org:8081&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub repository: &lt;a href="https://github.com/Patrickmbaza/hng14-stage3-devops-" rel="noopener noreferrer"&gt;https://github.com/Patrickmbaza/hng14-stage3-devops-&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>automation</category>
      <category>monitoring</category>
      <category>python</category>
      <category>security</category>
    </item>
  </channel>
</rss>
