Most developers deploy servers. Few think about what happens when someone tries to take them down. I did. I built ShieldDaemon — a tool that watches every request hitting your server, learns your normal traffic patterns, and automatically blocks attackers the moment something looks wrong. No manual intervention. No hardcoded rules. Just a daemon that never sleeps. Here is exactly how I built it.
What Is This Project About?
Imagine you run an online shop. Everything is working fine until one day thousands of fake requests flood your website all at once. Your server crashes. Real customers can't access your shop. You lose money and trust.
That is called a DDoS attack — Distributed Denial of Service. It is one of the most common ways attackers take down websites.
In this project I built ShieldDaemon — a tool that watches every request coming into a server, learns what normal traffic looks like, and automatically blocks any IP address that starts behaving suspiciously.
The best part? It does all of this in real time, without any human intervention.
The Stack
- Python — the detection daemon
- Nginx — reverse proxy that logs all traffic in JSON format
- Nextcloud — the application being protected
- Docker Compose — runs everything together
- iptables — Linux firewall used to block bad IPs
- Flask — powers the live dashboard
- Slack — receives instant alert notifications
How the System Works — In Plain English
Think of it like a security camera system at a shopping mall:
1. The Camera (Nginx)
Every person who walks through the mall entrance gets recorded. Their face, the time they arrived, which shop they visited, and whether they were let in or turned away. Nginx does the same thing — it records every request that hits your server in JSON format and saves it to a shared log file.
2. The Recording (JSON Log File)
All that information is saved to a log file in real time. Every single request — who made it, when, what they asked for, and what happened. It looks like this:
{
"source_ip": "45.33.32.156",
"timestamp": "2026-05-11T22:07:28+00:00",
"method": "GET",
"path": "/",
"status": 200,
"response_size": 6674
}
3. The Security Guard (ShieldDaemon)
There is a guard watching that recording live. Not checking it hours later — watching it as it happens. The guard has been watching long enough to know what a normal busy day looks like versus something suspicious.
4. The Pattern Recognition
If one person walks past the same shop 300 times in one minute, the guard knows that is not normal. ShieldDaemon does the same — it compares current traffic against what it has learned is normal and raises an alarm when something is off.
5. The Bouncer (iptables)
When the alarm is raised, the bouncer steps in. The suspicious visitor is blocked at the door — they cannot get back in. This happens automatically within 10 seconds.
6. The Radio (Slack)
Every time someone is blocked or unblocked, a message is sent to the security team instantly via Slack.
7. The Monitor Screen (Dashboard)
A live screen shows everything happening in real time — who is visiting, how fast, who is blocked, and how the system is performing.
Part 1 — Watching the Logs
The first thing ShieldDaemon does is read the Nginx access log line by line as new requests come in. This is called tailing a file.
Nginx is configured to write logs in JSON format like this:
{
"source_ip": "45.33.32.156",
"timestamp": "2026-05-11T22:07:28+00:00",
"method": "GET",
"path": "/",
"status": 200,
"response_size": 6674
}
Every line tells us exactly who made a request, when, what they requested, and whether it succeeded.
My monitor script tails this file and passes each line to the detector:
def tail_log(log_path, callback):
with open(log_path, 'r') as f:
f.seek(0, 2) # start at end of file
while True:
line = f.readline()
if line:
parsed = parse_log_line(line)
if parsed:
callback(parsed)
else:
time.sleep(0.1)
Part 2 — The Sliding Window
Now that we can see every request, we need to measure how fast they are coming.
I use a sliding window — a structure that tracks requests over the last 60 seconds. I use Python's deque (double-ended queue) for this.
Here is how it works in simple terms:
Imagine a conveyor belt that is 60 seconds long. Every new request gets placed on the right end. Any request older than 60 seconds falls off the left end automatically. The number of items on the belt at any moment is the current request rate.
from collections import deque
ip_window = deque()
def record(ip, timestamp):
ip_window.append(timestamp)
# Remove entries older than 60 seconds
cutoff = timestamp - 60
while ip_window and ip_window[0] < cutoff:
ip_window.popleft()
# Current rate = items on belt / belt length
rate = len(ip_window) / 60
This gives us an accurate requests-per-second value for every IP at any moment.
Part 3 — The Rolling Baseline
Knowing the current rate is not enough. We need to know whether that rate is normal or not.
For example 10 requests per second might be completely normal for a busy website during the day. But at 3am it might be a sign of an attack.
This is where the rolling baseline comes in. It learns what normal traffic looks like over the last 30 minutes.
Every second we record how many requests came in. Every 60 seconds we calculate the mean (average) and standard deviation (how much it varies) of those counts.
mean = sum(counts) / len(counts)
variance = sum((x - mean) ** 2 for x in counts) / len(counts)
std = math.sqrt(variance)
The baseline also maintains per-hour slots — so it learns that traffic during business hours is higher than traffic at night, and adjusts accordingly.
Floor values of 0.1 are applied to both mean and standard deviation to prevent false positives when there is zero traffic.
Part 4 — Detecting Anomalies
Now we have two things: the current rate and the baseline. We compare them using two methods.
Method 1 — Z-Score
The z-score tells us how many standard deviations the current rate is above normal:
z_score = (current_rate - baseline_mean) / baseline_std
If the z-score is above 2.0, something is unusual. A z-score of 2.0 means the rate is so high it would only happen naturally about 2% of the time.
Method 2 — Rate Multiplier
We also check if the rate is simply more than 2 times the baseline mean:
if current_rate > 2.0 * baseline_mean:
# anomaly detected
Whichever fires first triggers the response. This gives us two layers of protection.
If an IP also has a high rate of error responses (4xx and 5xx), the thresholds tighten automatically to catch it sooner.
Part 5 — Blocking with iptables
When an anomaly is detected the IP gets blocked at the firewall level using iptables. This means the server stops accepting any traffic from that IP before it even reaches Nginx or Nextcloud.
subprocess.run([
"iptables", "-I", "INPUT",
"-s", ip, "-j", "DROP"
])
This happens within 10 seconds of detection.
Here is what a blocked IP looks like in iptables:
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP all -- 45.33.32.156 0.0.0.0/0
Part 6 — Auto-Unban with Backoff Schedule
Blocking an IP forever for a first offence is too harsh — it might be a false positive. But being too lenient encourages repeat attacks.
I implemented a progressive backoff schedule:
| Offence | Ban Duration |
|---|---|
| 1st ban | 10 minutes |
| 2nd ban | 30 minutes |
| 3rd ban | 2 hours |
| 4th+ ban | Permanent |
Each ban is scheduled using a Python timer thread that fires after the duration and removes the iptables rule automatically. A Slack notification is sent every time an IP is unbanned.
Part 7 — Slack Alerts
Every significant event sends an alert to Slack:
Ban alert example:
IP BANNED
• IP: 45.33.32.156
• Condition: z-score=5.43 > threshold=2.0
• Current rate: 3.72 req/s
• Baseline: 0.10 req/s
• Ban duration: 600 seconds
• Timestamp: 2026-05-11T22:07:33Z
Global anomaly alert:
GLOBAL TRAFFIC ANOMALY
• Condition: Global request rate spike
• Current rate: 3.10 req/s
• Baseline: 0.10 req/s
• Action: No IP ban — monitoring closely
Part 8 — The Live Dashboard
The dashboard at port 8080 refreshes every 3 seconds and shows everything happening in real time:
- Global request rate
- Baseline mean and standard deviation
- Blocked IPs with ban count
- CPU and memory usage
- System uptime
- Top 10 source IPs
- Live traffic chart vs baseline
It is built with Flask and Chart.js with a dark blue security-themed design.
Challenges I Faced
The baseline kept adapting to attack traffic. When I injected test requests the baseline learned those high rates as normal and stopped flagging them. The fix was to restart the daemon with a clean baseline before testing.
The latency calculation was wrong. My first attempt used date +%s%N which is not supported on all Linux versions. I switched to curl's built-in %{time_total} timing instead.
The Slack webhook was accidentally exposed. I committed the webhook URL to GitHub and GitHub's secret scanning blocked the push. I revoked the token immediately and used a placeholder in the config file.
Docker volume mounting. The detector container needed to read the Nginx log file through a shared Docker volume called HNG-nginx-logs. Getting the volume permissions right took some debugging.
What I Learned
Building ShieldDaemon taught me that real security tools are statistical, not rule-based. A fixed threshold of "block anyone who sends more than 100 requests per minute" would block legitimate users during a product launch. A statistical baseline that learns from actual traffic patterns is far more accurate.
I also learned that the order of operations matters in security. You must detect before you block. You must verify before you unban. You must log everything so you can audit what happened.
Most importantly I learned that security is a continuous process. ShieldDaemon runs forever, constantly learning and adapting. There is no finish line — only a daemon that never sleeps.
The Result
A fully working DDoS detection engine that:
- Watches Nginx logs in real time
- Learns normal traffic patterns automatically
- Detects attacks within seconds using z-scores
- Blocks malicious IPs with iptables
- Unbans automatically on a backoff schedule
- Alerts the team via Slack
- Shows everything on a live dashboard
You can see it running at http://13.60.224.73:8080
The full source code is at https://github.com/asanteedith/Shield-Daemon-Detection-Engine
Written by Edith Asante — Cloud & DevOps Engineer Find me on GitHub | Dev.to
Top comments (0)