Security 8 min read

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.

By BashPilot Team

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.

Capture the server state before changing anything
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 observeMost likely explanationNext check
High Web worker or application CPU, rising latency, requests visible in access logsLayer 7 HTTP flood or an expensive legitimate traffic spikeCompare hosts, paths, sources and status codes
Origin is unreachable and inbound bandwidth is saturatedVolumetric network attackContact the provider, CDN or upstream DDoS service
Mostly 404 responses for unrelated sensitive pathsAutomated vulnerability scanningInspect request rate and WAF matches before calling it DDoS
Traffic rises across many normal pages with healthy conversion and error ratesLegitimate demandScale 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.

Locate virtual hosts and access logs
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.

Compare recent activity across NGINX access logs
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.

Profile the last 50,000 requests
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 patternUseful immediate controlMain limitation
A few direct source IPs dominateTemporary firewall or WAF blocks after ownership checksThe campaign can move to new addresses
One expensive URI dominatesA route-specific request-rate rule or browser challengeA low threshold can block legitimate bursts
Many IPs repeat the same behaviourWAF rules using path, method, headers, rate and identity signalsPer-IP limits alone miss coordinated low-rate sources
The network link is saturatedProvider or edge-network DDoS mitigationOrigin 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.

Define a per-client request zone in the http context
limit_req_zone $binary_remote_addr zone=http_flood_per_ip:20m rate=10r/s;
Measure the effect inside the existing location
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.

Validate and reload NGINX
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.

Recheck system pressure, status codes and user experience
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.

BashEdge dashboard showing active Layer 7 DDoS protection, challenged requests and blocked attack sources
BashEdge attack overview. Click the image to enlarge.

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.
Share LinkedIn X
Questions

Frequently asked questions

What is a Layer 7 DDoS attack?

A Layer 7 DDoS attack targets the application layer by sending HTTP or HTTPS requests that the Web stack must inspect and process. Individual requests may be valid, but their rate, coordination or choice of expensive routes consumes Web workers, application capacity or database resources. This differs from a volumetric attack whose primary goal is to saturate network bandwidth.

Can a firewall stop an HTTP request flood?

A network firewall can block known source addresses and restrict ports, but HTTP floods use ports that a public website must leave open. Application-aware controls can make decisions using the host, URI, method, headers, rate and behaviour. If the attack saturates the network link, mitigation must also happen upstream before traffic reaches the origin.

How do I distinguish a Layer 7 attack from a legitimate traffic spike?

Compare hosts, paths, source distribution, user agents, status codes, application latency and normal business signals. Legitimate demand usually spans expected journeys and keeps error rates within a familiar range. An attack often concentrates on a small set of costly paths, repeats the same behaviour across many sources, rotates identities or produces an unusual rise in timeouts and server errors.

Why does blocking the top IP addresses sometimes fail?

A distributed campaign can use hundreds or thousands of addresses, each staying below a simple per-IP threshold. Removing the busiest sources may provide temporary relief but does not identify the shared behaviour. Effective containment may also need route-specific limits, WAF rules, source-network context or browser verification that connects low-rate participants acting as one campaign.

Should an HTTP rate limit return 429 or 503?

HTTP 429 clearly indicates that a request was rejected because of rate limiting. NGINX uses 503 by default for requests rejected by `limit_req`, but `limit_req_status` can change this to 429. Choose deliberately, monitor the response count and make sure upstream health checks or client retry behaviour will not turn the new status into another operational problem.

Put your servers on autopilot.

Connect a server in about a minute. The first week is on us.