How to DoS A server

Published: (January 17, 2026 at 03:17 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Disclaimer

This experiment was performed in a controlled home lab on systems I own. No real‑world systems were harmed. It is for educational purposes only.

How It Started

Like many people learning cybersecurity, I used to think Denial of Service (DoS) attacks required massive botnets, insane bandwidth, and Hollywood‑level hacking. Turns out they don’t. All you need is a vulnerable server and a way to exploit that vulnerability—no malware, no exotic exploits, just bad application design and a can‑do attitude.

DoS vs. DDoS

FeatureDoS (Denial of Service)DDoS (Distributed Denial of Service)
SourceSingle source (one‑to‑one)Many compromised devices (many‑to‑one)
ExecutionSimpler to launch and blockHarder to detect, mitigate, and more impactful
TrafficLimited volumeMassive, distributed traffic
Typical useSmall‑scale testing or internal abuseLarge‑scale attacks on public services

In this lab I am using one machine to attack a target, so it is a DoS scenario.

The Lab I Built

I set up a small, isolated lab using Oracle VirtualBox:

  • Kali Linux → attacker machine
  • Ubuntu Linux → target machine

Both VMs were connected via a Host‑only network, so they could communicate with each other while staying completely isolated from the Internet. This kept the experiment legal, safe, and contained.

The Vulnerable Server

Can you spot what is wrong with the Python code below?

from http.server import BaseHTTPRequestHandler, HTTPServer
import time

a = 0

class VulnerableHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        global a
        time.sleep(3)                     # <-- intentional delay
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(bytes(f"Request processed. Count: {a}", "utf-8"))
        a += 1

def run(server_class=HTTPServer, handler_class=VulnerableHTTPRequestHandler, port=8080):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting vulnerable server on port {port}...")
    httpd.serve_forever()

if __name__ == "__main__":
    run()

Did you see it? time.sleep(3)

The line was added to simulate “heavy processing,” and it becomes the entire vulnerability because:

  • The server is single‑threaded.
  • Each request blocks for 3 seconds.
  • No rate limiting or concurrency handling is present.

This makes it an ideal target for an application‑layer DoS attack.

Starting the Server

On the Ubuntu VM, run:

python3 vulnerable_server1.py

The server starts on port 8080. From the Kali VM you can verify it works:

curl http://192.168.56.103:8080

Each request should return a response and increment the counter.

The Attack

From Kali, I launched ApacheBench:

ab -n 50 -c 10 http://192.168.56.103:8080/
  • 50 total requests
  • 10 concurrent requests

Although these numbers look modest, the server quickly became “stuck.” Responses slowed, and requests began queuing. I hadn’t flooded the network or crashed the OS—I simply asked the server to do more than it was designed to handle.

ApacheBench results

What Actually Happened

Because the server is single‑threaded, each request blocks for 3 seconds. Only one request can be processed at a time, so when 10 requests arrive simultaneously:

  1. One request is handled.
  2. The remaining nine wait in the queue.
  3. New requests keep piling up.

This is an application‑layer DoS, not a bandwidth attack.

Watching the Attack in Wireshark

As a bonus, I captured traffic on Kali with Wireshark using the filter:

ip.addr == 192.168.56.103 && tcp.port == 8080

The capture shows:

  • Repeated HTTP GET requests
  • Delayed responses
  • TCP retransmissions
  • Growing congestion

Wireshark capture

Real‑World Relevance

Vulnerabilities like this can exist in:

  • Internal tools
  • APIs
  • Microservices
  • Custom dashboards

Attackers don’t always need to “break” something; they can simply wait for poorly written code to break itself. In production such a flaw would (hopefully) be patched quickly, but it makes for an excellent learning exercise.

Mitigations

  • Use an asynchronous or multi‑threaded server (e.g., aiohttp, gunicorn, uvicorn).
  • Add rate limiting.
  • Place a reverse proxy (e.g., Nginx) in front of the application.

Conclusion

Breaking my own server was one of the most valuable lessons I’ve learned so far. It demonstrated how an application‑layer DoS works, why design choices matter, and what attack traffic actually looks like.

If you’re learning cybersecurity, I highly recommend building labs like this: they’re safe, legal, and incredibly eye‑opening.

Add‑on: I wrote this blog post while experimenting with the setup above. Feel free to reproduce the lab and explore further!

Without a plan on how to finish it, I also tested out an HTTP spray DoS attack, but the blog was getting too long for that. If you want to check out the tool, it’s on my GitHub. I’m planning on improving it later in the coming days if I ever get the time to do that.

Back to Blog

Related posts

Read more »

Refactoring Your Resume

Resume Builders - FlowCV – My personal favorite. It’s free, modern, and very hard to “break” the layout. - Standard Resume – Minimalist, high‑readability layou...