<?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: Daniel Ogbuti</title>
    <description>The latest articles on DEV Community by Daniel Ogbuti (@dahnny).</description>
    <link>https://dev.to/dahnny</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%2F501246%2F16443ef2-0702-4657-99f0-ac8a69fa6482.jpeg</url>
      <title>DEV Community: Daniel Ogbuti</title>
      <link>https://dev.to/dahnny</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dahnny"/>
    <language>en</language>
    <item>
      <title>Building a DDoS Detection Tool</title>
      <dc:creator>Daniel Ogbuti</dc:creator>
      <pubDate>Wed, 29 Apr 2026 18:07:04 +0000</pubDate>
      <link>https://dev.to/dahnny/building-a-ddos-detection-tool-9b2</link>
      <guid>https://dev.to/dahnny/building-a-ddos-detection-tool-9b2</guid>
      <description>&lt;p&gt;When I first saw the HNG Stage 3 task, it looked intimidating.&lt;/p&gt;

&lt;p&gt;The assignment was not just asking me to deploy an application. It wanted me to build a small security system around the application.&lt;/p&gt;

&lt;p&gt;The system needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deploy a Nextcloud app,&lt;/li&gt;
&lt;li&gt;place Nginx in front of it,&lt;/li&gt;
&lt;li&gt;read Nginx access logs,&lt;/li&gt;
&lt;li&gt;detect abnormal traffic,&lt;/li&gt;
&lt;li&gt;block aggressive IP addresses,&lt;/li&gt;
&lt;li&gt;send Slack alerts,&lt;/li&gt;
&lt;li&gt;show live metrics on a dashboard,&lt;/li&gt;
&lt;li&gt;and keep audit logs as proof of what happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, this felt like many separate things. But after breaking it down, I understood that the project is really about one simple idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Watch traffic, learn what is normal, and react when something becomes suspicious.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This blog post explains the project in simple terms.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Project Does
&lt;/h2&gt;

&lt;p&gt;The project protects a Nextcloud application using a custom Python detector.&lt;/p&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 plaintext"&gt;&lt;code&gt;User traffic
    ↓
Nginx reverse proxy
    ↓
Nextcloud container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx is the first thing users touch. It receives public traffic and forwards it to Nextcloud.&lt;/p&gt;

&lt;p&gt;At the same time, Nginx writes a log for every request.&lt;/p&gt;

&lt;p&gt;Those logs are written in JSON format, 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;"102.91.92.195"&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-29T13:30:00+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="mi"&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="mi"&gt;5321&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;Then the Python detector reads those logs continuously.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nginx JSON logs
    ↓
Python detector daemon
    ↓
Sliding window + baseline + anomaly detection
    ↓
Slack alerts / iptables block / dashboard / audit log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the detector does not receive web traffic directly. It watches the log file and makes decisions from what it sees.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Project Matters
&lt;/h2&gt;

&lt;p&gt;This project matters because servers are always exposed to traffic from the internet.&lt;/p&gt;

&lt;p&gt;Some traffic is normal. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A user opens the login page.
A user refreshes the app.
A user uploads a file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But some traffic can be suspicious. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One IP sends hundreds of requests quickly.
Many bad requests hit invalid pages.
The whole server suddenly gets much more traffic than usual.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we do not monitor these patterns, the application can become slow or unavailable.&lt;/p&gt;

&lt;p&gt;This project teaches an important DevOps/security lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Logs are not just for debugging. Logs can also be used to detect attacks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By reading Nginx logs, the detector can understand what is happening and respond automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Main Parts of the System
&lt;/h2&gt;

&lt;p&gt;The project has a few important parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Nextcloud
&lt;/h3&gt;

&lt;p&gt;Nextcloud is the application being protected.&lt;/p&gt;

&lt;p&gt;In this project, I did not build Nextcloud myself. I used the provided Docker image.&lt;/p&gt;

&lt;p&gt;Nextcloud is the app users access.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Nginx
&lt;/h3&gt;

&lt;p&gt;Nginx is used as a reverse proxy.&lt;/p&gt;

&lt;p&gt;A reverse proxy is a server that receives requests first, then forwards them to another application behind it.&lt;/p&gt;

&lt;p&gt;In this project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Nginx → Nextcloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx also writes JSON access logs.&lt;/p&gt;

&lt;p&gt;That is very important because the detector depends on those logs.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Python Detector
&lt;/h3&gt;

&lt;p&gt;The detector is the main custom part of the project.&lt;/p&gt;

&lt;p&gt;It does these things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reads Nginx logs
Counts recent traffic
Learns normal traffic
Detects abnormal traffic
Blocks aggressive IPs
Sends Slack alerts
Updates dashboard metrics
Writes audit logs
Automatically unbans temporary bans
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used Python because it is easier to read and good for this type of project. Python also has useful tools for JSON parsing, background threads, HTTP requests, and running Linux commands.&lt;/p&gt;




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

&lt;p&gt;The first thing the detector needs to know is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How many requests happened recently?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For that, I used a 60-second sliding window.&lt;/p&gt;

&lt;p&gt;A sliding window means the detector always looks at the most recent 60 seconds of traffic.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current time: 10:01:00
Window:       10:00:00 → 10:01:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten seconds later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current time: 10:01:10
Window:       10:00:10 → 10:01:10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The window moves forward as time moves forward.&lt;/p&gt;

&lt;p&gt;The detector tracks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total requests in the last 60 seconds
Requests per IP in the last 60 seconds
Errors in the last 60 seconds
Top source IPs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the detector to answer questions like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many requests did this IP make recently?
How many total requests did the server receive recently?
Which IPs are the busiest right now?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Python, I used &lt;code&gt;deque&lt;/code&gt; for this.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;deque&lt;/code&gt; is like a list that is efficient when adding items to one end and removing old items from the other end.&lt;/p&gt;

&lt;p&gt;For every request, the detector adds a timestamp. When a timestamp becomes older than 60 seconds, it is removed.&lt;/p&gt;

&lt;p&gt;That way, the count always stays fresh.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Baseline Learns Traffic
&lt;/h2&gt;

&lt;p&gt;Counting current traffic is not enough.&lt;/p&gt;

&lt;p&gt;The detector also needs to know what is normal.&lt;/p&gt;

&lt;p&gt;For example, 100 requests may be normal for a busy server, but suspicious for a small server.&lt;/p&gt;

&lt;p&gt;So I added a rolling baseline.&lt;/p&gt;

&lt;p&gt;The baseline looks at the last 30 minutes of traffic and calculates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average request rate
Standard deviation
Normal error rate
Current hour traffic pattern
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detector stores traffic as per-second counts.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10:00:01 → 2 requests
10:00:02 → 0 requests
10:00:03 → 1 request
10:00:04 → 5 requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From these numbers, it calculates the average.&lt;/p&gt;

&lt;p&gt;The average is called the mean.&lt;/p&gt;

&lt;p&gt;If the counts are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2, 0, 1, 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mean is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(2 + 0 + 1 + 5) / 4 = 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the detector learns that normal traffic is around 2 requests per second.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Standard Deviation Means
&lt;/h2&gt;

&lt;p&gt;Standard deviation tells the detector how much traffic normally changes.&lt;/p&gt;

&lt;p&gt;If traffic is always like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2, 2, 2, 2, 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the traffic is very stable.&lt;/p&gt;

&lt;p&gt;If traffic is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0, 5, 1, 8, 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the traffic jumps around a lot.&lt;/p&gt;

&lt;p&gt;This matters because a small spike may be suspicious on a very quiet server, but normal on a server that already has unstable traffic.&lt;/p&gt;

&lt;p&gt;So the detector uses both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mean
standard deviation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to understand what normal traffic looks like.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Detection Decisions Are Made
&lt;/h2&gt;

&lt;p&gt;The detector compares the current request rate with the learned baseline.&lt;/p&gt;

&lt;p&gt;It uses two main rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rule 1: Z-score
&lt;/h3&gt;

&lt;p&gt;The z-score checks how far the current traffic is from normal.&lt;/p&gt;

&lt;p&gt;The formula is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z = (current_rate - baseline_mean) / baseline_stddev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the z-score is greater than &lt;code&gt;3.0&lt;/code&gt;, the traffic is treated as suspicious.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;If traffic is much higher than what the server normally sees, raise an alert.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Rule 2: 5x Baseline Rule
&lt;/h3&gt;

&lt;p&gt;The detector also checks if the current rate is more than 5 times the baseline mean.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Baseline mean: 1 request/sec
Current rate: 6 requests/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is more than 5 times the baseline.&lt;/p&gt;

&lt;p&gt;So the detector treats it as suspicious.&lt;/p&gt;

&lt;p&gt;This rule is useful because it is simple and easy to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error Surge Detection
&lt;/h2&gt;

&lt;p&gt;The detector also watches error responses.&lt;/p&gt;

&lt;p&gt;Errors are HTTP status codes like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;404 Not Found
403 Forbidden
500 Server Error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one IP produces too many errors, it may be scanning or attacking invalid paths.&lt;/p&gt;

&lt;p&gt;The detector checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP error rate &amp;gt; 3x baseline error rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that happens, the detector tightens the thresholds for that IP.&lt;/p&gt;

&lt;p&gt;Normal thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z-score &amp;gt; 3.0
rate &amp;gt; 5x baseline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tightened thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;z-score &amp;gt; 2.0
rate &amp;gt; 3x baseline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the detector becomes stricter when an IP is already behaving suspiciously.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-IP Anomaly vs Global Anomaly
&lt;/h2&gt;

&lt;p&gt;The detector handles two types of anomalies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-IP Anomaly
&lt;/h3&gt;

&lt;p&gt;This happens when one IP is sending too much traffic.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One IP sends 500 requests quickly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detector responds by blocking that IP.&lt;/p&gt;




&lt;h3&gt;
  
  
  Global Anomaly
&lt;/h3&gt;

&lt;p&gt;This happens when the whole server receives too much traffic.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The total server traffic suddenly becomes much higher than normal.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For global anomalies, the detector sends a Slack alert but does not block everyone.&lt;/p&gt;

&lt;p&gt;This is important because a global spike might be caused by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Many normal users
A test
A campaign
A real attack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blocking everyone would be dangerous.&lt;/p&gt;

&lt;p&gt;So global anomaly only sends an alert.&lt;/p&gt;




&lt;h2&gt;
  
  
  How iptables Blocks an IP
&lt;/h2&gt;

&lt;p&gt;To block aggressive IPs, the detector uses &lt;code&gt;iptables&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iptables&lt;/code&gt; is a Linux firewall tool.&lt;/p&gt;

&lt;p&gt;A firewall decides whether traffic should be accepted or dropped.&lt;/p&gt;

&lt;p&gt;The blocking command 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;iptables &lt;span class="nt"&gt;-I&lt;/span&gt; DOCKER-USER &lt;span class="nt"&gt;-s&lt;/span&gt; BAD_IP &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If traffic comes from BAD_IP, drop it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the rule is active, traffic from that IP should no longer reach Nginx or Nextcloud.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blocked IP
    ↓
iptables
    ↓
DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request does not reach the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Used the DOCKER-USER Chain
&lt;/h2&gt;

&lt;p&gt;At first, I thought blocking in the &lt;code&gt;INPUT&lt;/code&gt; chain was enough.&lt;/p&gt;

&lt;p&gt;But Nginx is running inside Docker, and Docker uses its own iptables rules.&lt;/p&gt;

&lt;p&gt;Because of that, a normal &lt;code&gt;INPUT&lt;/code&gt; rule may not always block traffic going to Docker-published ports.&lt;/p&gt;

&lt;p&gt;So I used the &lt;code&gt;DOCKER-USER&lt;/code&gt; chain.&lt;/p&gt;

&lt;p&gt;This is a better place to put custom firewall rules when working with Docker.&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; DOCKER-USER &lt;span class="nt"&gt;-s&lt;/span&gt; BAD_IP &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect the rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-L&lt;/span&gt; DOCKER-USER &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;--line-numbers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chain DOCKER-USER (1 references)
num  target  prot opt source          destination
1    DROP    all  --  102.91.92.195   0.0.0.0/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proves the IP is blocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-Unban Logic
&lt;/h2&gt;

&lt;p&gt;Blocking forever is not always the best idea.&lt;/p&gt;

&lt;p&gt;So I added an auto-unban system.&lt;/p&gt;

&lt;p&gt;The ban schedule is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1st offense → 10 minutes
2nd offense → 30 minutes
3rd offense → 2 hours
4th offense → permanent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The detector stores ban state in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detector/state.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file remembers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which IP is banned
How many times it has been banned
When it was banned
When it should be unbanned
Whether the ban is permanent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The auto-unbanner checks the state file regularly.&lt;/p&gt;

&lt;p&gt;If the ban time has expired, it removes the iptables 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; DOCKER-USER &lt;span class="nt"&gt;-s&lt;/span&gt; IP &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it sends a Slack message and writes an audit log entry.&lt;/p&gt;




&lt;h2&gt;
  
  
  Slack Alerts
&lt;/h2&gt;

&lt;p&gt;Slack alerts are used so that I do not have to watch the terminal all the time.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IP ban
IP unban
Global anomaly
Error surge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Slack webhook is not stored directly inside &lt;code&gt;config.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead, &lt;code&gt;config.yaml&lt;/code&gt; points to an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;slack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;webhook_url_env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SLACK_WEBHOOK_URL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real webhook is stored in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file is ignored by Git so the secret is not exposed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Live Metrics Dashboard
&lt;/h2&gt;

&lt;p&gt;The project also has a live dashboard.&lt;/p&gt;

&lt;p&gt;The dashboard shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Banned IPs
Global requests per second
Top 10 source IPs
CPU usage
Memory usage
Effective mean
Effective standard deviation
Uptime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard refreshes every 3 seconds or less.&lt;/p&gt;

&lt;p&gt;This helps the reviewer see what the detector is doing live.&lt;/p&gt;

&lt;p&gt;The dashboard is served through a DuckDNS subdomain, while Nextcloud remains accessible by server IP only.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit Logs
&lt;/h2&gt;

&lt;p&gt;The audit log is the permanent record of what happened.&lt;/p&gt;

&lt;p&gt;It is stored at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detector/audit.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[timestamp] ACTION ip | condition | rate | baseline | duration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2026-04-29T13:00:00+00:00] BASELINE - | recalculated using rolling-window | - | mean=0.1000,stddev=0.1000,error_rate=0.0100,samples=60,hour=13 | -

[2026-04-29T13:05:00+00:00] GLOBAL_ANOMALY - | z-score 8.50 exceeded threshold 3.00 | 30.00/s | mean=2.0000,stddev=1.0000,z=28.00,multiplier=15.00x | -

[2026-04-29T13:06:00+00:00] BAN 102.91.92.195 | rate exceeded 5x baseline | 5.00/s | mean=0.5000,stddev=0.2000,z=22.50,multiplier=10.00x,tightened=False | 10 minutes

[2026-04-29T13:16:00+00:00] UNBAN 102.91.92.195 | previous ban expired | - | - | 10 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This log proves that the detector is not just running, but actually making decisions and recording them.&lt;/p&gt;




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

&lt;p&gt;This project helped me understand how monitoring and security automation can work together.&lt;/p&gt;

&lt;p&gt;The most important things I learned were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx can be used as both a reverse proxy and a source of logs.
Logs can be used to detect suspicious behavior.&lt;/li&gt;
&lt;li&gt;A sliding window helps track recent traffic.&lt;/li&gt;
&lt;li&gt;A baseline helps define what normal means.&lt;/li&gt;
&lt;li&gt;iptables can block traffic at the Linux firewall level.&lt;/li&gt;
&lt;li&gt;Docker networking affects where firewall rules should be placed.&lt;/li&gt;
&lt;li&gt;Slack alerts make the system easier to monitor.&lt;/li&gt;
&lt;li&gt;Audit logs are important for proof and debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also learned that building a system like this is easier when broken into small parts.&lt;/p&gt;

&lt;p&gt;Instead of trying to build everything at once, I built it step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy app&lt;/li&gt;
&lt;li&gt;Add logs&lt;/li&gt;
&lt;li&gt;Read logs&lt;/li&gt;
&lt;li&gt;Count traffic&lt;/li&gt;
&lt;li&gt;Learn baseline&lt;/li&gt;
&lt;li&gt;Detect anomaly&lt;/li&gt;
&lt;li&gt;Send alert&lt;/li&gt;
&lt;li&gt;Block IP&lt;/li&gt;
&lt;li&gt;Auto-unban&lt;/li&gt;
&lt;li&gt;Show dashboard&lt;/li&gt;
&lt;li&gt;Write audit logs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That made the project easier to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project is a small but practical example of how a server can monitor and defend itself.&lt;/p&gt;

&lt;p&gt;It is not a full enterprise security system, but it shows the foundation of one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;observe traffic
learn normal behavior
Detect abnormal behavior
respond automatically
record what happened
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For me, the biggest lesson is that DevOps is not only about deploying applications. It is also about understanding how applications behave in real conditions and building systems that can respond when something goes wrong.&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>devops</category>
      <category>cybersecurity</category>
      <category>python</category>
    </item>
  </channel>
</rss>
