Layer 7 DDoS Attack: How to Detect and Stop an HTTP Request Flood
A practical response plan for finding the host, path and traffic pattern behind an HTTP flood, containing it safely, and proving the server recovered.
A Layer 7 DDoS attack can leave the network reachable while the website times out, PHP workers fill up and database load climbs. Requests arrive on normal HTTP or HTTPS ports and may look valid individually. Before blocking anything, prove that request processing is the bottleneck, identify the host and URL absorbing the traffic, and distinguish an attack from a genuine surge.
Confirm that it is an HTTP request flood
Start at the server, not at the blocklist. A Layer 7 attack forces the application stack to handle HTTP requests, leaving evidence in access logs, worker activity and application latency. A volumetric attack instead fills the network connection before the Web server can meaningfully respond. For an HTTP flood, request context matters: compare rate with the targeted host, path, method, status code, user agent and upstream response time.
date -u
uptime
vmstat 1 5
ss -s
ps -eo pid,ppid,user,stat,%cpu,%mem,comm,args --sort=-%cpu | head -n 25
Keep this first sample. You will repeat it after containment to prove whether the server recovered.
| What you observe | Most likely explanation | Next check |
|---|---|---|
| High Web worker or application CPU, rising latency, requests visible in access logs | Layer 7 HTTP flood or an expensive legitimate traffic spike | Compare hosts, paths, sources and status codes |
| Origin is unreachable and inbound bandwidth is saturated | Volumetric network attack | Contact the provider, CDN or upstream DDoS service |
| Mostly 404 responses for unrelated sensitive paths | Automated vulnerability scanning | Inspect request rate and WAF matches before calling it DDoS |
| Traffic rises across many normal pages with healthy conversion and error rates | Legitimate demand | Scale and cache before applying restrictive controls |
Find the targeted host, path and traffic pattern
On a server hosting several sites, locate the active virtual hosts and their access logs. NGINX and Apache configurations vary between distributions and control panels, so discover the paths rather than assuming one. If traffic passes through a reverse proxy, analyse the log written by the layer that records the trusted client address.
nginx -T 2>/dev/null | grep -E '^[[:space:]]*(server_name|access_log)[[:space:]]'
apachectl -S 2>/dev/null
find /var/log/nginx /var/log/apache2 /var/log/httpd -maxdepth 2 -type f -name '*access*log' 2>/dev/null
Use the commands relevant to the installed Web server. Control panels may store per-domain logs elsewhere.
If there is one access log per site, compare recent line counts to find the outlier. This is a quick triage measurement, not a precise requests-per-second calculation, but a flooded host is often obvious.
find /var/log/nginx -type f -name '*access*.log' -print0 | while IFS= read -r -d '' log; do
printf '%8s %s\n' "$(tail -n 5000 "$log" 2>/dev/null | wc -l)" "$log"
done | sort -rn | head -n 20
On a very busy server, reduce the tail size. On a low-traffic server, compare a longer period.
Set log to the affected access log, then rank client addresses, paths, status codes, methods and user agents. The field positions below assume a standard combined log format. Check the configured log_format or Apache LogFormat before trusting the results, especially when a proxy adds fields.
log=/var/log/nginx/example.com.access.log
tail -n 50000 "$log" | awk '{print $1}' | sort | uniq -c | sort -rn | head -n 20
tail -n 50000 "$log" | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 20
tail -n 50000 "$log" | awk '{print $9}' | sort | uniq -c | sort -rn
tail -n 50000 "$log" | awk -F'"' '{print $2}' | awk '{print $1}' | sort | uniq -c | sort -rn
tail -n 50000 "$log" | awk -F'"' '{print $6}' | sort | uniq -c | sort -rn | head -n 20
Replace the log path. Keep the output with the initial system snapshot so you can compare the attack before and after mitigation.
One source making thousands of requests is straightforward. Hundreds of addresses requesting the same expensive route a few times each may never trigger a per-IP threshold. Check whether traffic concentrates on uncached dynamic work such as login, search, checkout, API, XML-RPC or password reset routes. A cached asset and a request that starts PHP and a database query do not have the same cost.
Choose a control that matches the attack
| Observed pattern | Useful immediate control | Main limitation |
|---|---|---|
| A few direct source IPs dominate | Temporary firewall or WAF blocks after ownership checks | The campaign can move to new addresses |
| One expensive URI dominates | A route-specific request-rate rule or browser challenge | A low threshold can block legitimate bursts |
| Many IPs repeat the same behaviour | WAF rules using path, method, headers, rate and identity signals | Per-IP limits alone miss coordinated low-rate sources |
| The network link is saturated | Provider or edge-network DDoS mitigation | Origin controls run after the bandwidth has already been consumed |
For NGINX, the built-in request limiting module uses a leaky-bucket method. Its official documentation also provides limit_req_dry_run, which counts excessive requests without rejecting them. Define a shared zone in the http context, then apply it only to the affected route inside the existing server configuration. The example rate is deliberately illustrative. Derive the production value from normal peak traffic for that route.
limit_req_zone $binary_remote_addr zone=http_flood_per_ip:20m rate=10r/s;
limit_req zone=http_flood_per_ip burst=30 nodelay;
limit_req_dry_run on;
limit_req_log_level notice;
limit_req_status 429;
Merge these directives into the existing location for the attacked route. Do not create a duplicate location block around them.
nginx -t && systemctl reload nginx
grep 'limiting requests' /var/log/nginx/error.log | tail -n 50
Review the dry-run matches. When the affected requests are genuinely abusive, change limit_req_dry_run to off, validate again and reload.
Rate rules work best when layered. NGINX supports multiple limit_req directives, so a per-client limit can protect capacity while a separate zone gives costly routes such as login, search or password reset a stricter threshold. Set both from normal traffic, because one copied limit rarely fits every route.
Verify that the server recovered
Repeat the same measurements taken before the change. Request volume to the attacked route should fall or be rejected before expensive application work begins. Web worker CPU, run queue and application latency should move toward their normal baseline. Watch status codes carefully: a wall of 429 responses may show that the control is active, but it can also reveal a threshold set below ordinary customer traffic.
uptime
vmstat 1 5
ss -s
tail -n 20000 "$log" | awk '{print $9}' | sort | uniq -c | sort -rn
curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}s\n' https://example.com/
Run the HTTP check from outside the affected server and replace example.com with the real site. Test a normal page and the protected application route.
- Request evidence: the targeted path is no longer reaching the application at the attack rate.
- Resource evidence: worker CPU, load, memory pressure and database activity are returning to baseline.
- Customer evidence: normal browsing, login, checkout, API or other essential journeys still succeed.
- Control evidence: rejected or challenged requests match the hostile pattern rather than a broad slice of legitimate users.
Where BashEdge fits when the sources keep changing
The manual method works while the pattern is clear and the server has headroom to investigate. It becomes fragile when sources rotate or several hosted domains are hit together. BashEdge connects request rate, paths, source networks, WAF evidence and targeted hosts across the server. It can monitor rule impact, verify suspicious visitors and use Lockdown Mode for one site or all hosted domains. The interactive demo shows these controls without connecting to a live server.

Reduce the impact of the next HTTP flood
- Record normal peak request rates per host and expensive route. A useful limit starts with a baseline, not a copied number.
- Cache anonymous responses where the application allows it, especially pages that otherwise start PHP and database work for every request.
- Keep per-host logs with the real client address, request time, upstream time, status, host and URI so incident triage does not begin blind.
- Test WAF and rate rules in monitor or dry-run mode, then review false positives before switching to challenge or block.
- Arrange upstream mitigation for attacks that can saturate the server's network connection. Origin-side controls cannot recover bandwidth already consumed.
