Ddos Protection Nginx

October 22, 2026 ARPHost Uncategorized

A sudden run of 429 Too Many Requests can mean your limit is catching an abusive client, or that a shared office or mobile network is being throttled with it. If 502 and 504 responses are climbing, the application behind NGINX may already be under pressure. First identify which layer is saturated, preserve access and error logs, and avoid applying a blanket per-IP rule. Then contain expensive HTTP paths with a targeted NGINX policy, after confirming the active server block and the identity used as its limit key.

NGINX rate limiting can slow or reject requests that reach the web server. It can't restore an uplink or provider network that's already saturated. Treat it as an application-layer control within a broader defense, not as a substitute for upstream mitigation.

Table of Contents

Recognize DDoS Symptoms and Apply the First Fix

Operators often first see a sudden rise in 429, 499, 502, or 504 responses, longer response times, or unstable connections to an upstream application. On the host, rising ESTAB or SYN-RECV socket counts and file-descriptor exhaustion can point to connection pressure. None of these symptoms alone proves that a DDoS attack is underway. A deployment issue, a failing backend, or a legitimate traffic surge can look similar.

Start by recording the evidence before changing limits. Check NGINX's active configuration and recent logs, compare error rates across affected paths, and inspect socket state and host resource pressure. Don't delete logs, restart services unnecessarily, or introduce a broad deny rule while you're still determining whether traffic reaches NGINX at all.

Observed symptomLikely exhaustion pointFirst diagnostic action
429 responses concentrated on one pathA request limit or application policyCheck the matching location block and access logs
499 responses with rising latencyClients are closing slow requestsCompare upstream response times and NGINX error logs
502 or 504 responsesUpstream health, capacity, or connection pressureInspect upstream errors and backend health
Rising ESTAB or SYN-RECV countsSocket or connection pressureRun ss -s and inspect worker and file-descriptor limits
High bandwidth with little request visibilityNetwork link or upstream saturationCheck provider and edge telemetry before tuning NGINX

If the requests are reaching a costly endpoint, contain that path rather than imposing the same limit across the site. NGINX's ngx_http_limit_req_module uses a leaky-bucket algorithm and can delay requests, then reject excess requests after configured burst capacity is exhausted. Its documentation describes rate limiting as a way to help prevent DDoS attacks and protect proxied upstream servers from overload. NGINX request rate limiting

First-response rule: Keep the site's working paths available while you gather evidence. A blanket rule may suppress attack traffic, but it may also lock out legitimate users behind a shared address.

If you can't tell whether the bottleneck is at the host, edge, or provider, use an incident checklist such as ARPHost's DDoS attack detection guide. Escalate upstream if the network link, load balancer, or TLS terminator is saturated before requests can be evaluated by NGINX.

Prepare NGINX and Establish a Safe Baseline

Before editing the edge, confirm what NGINX is running and how its service is configured. You'll need root or sudo access, the active configuration tree, access to current logs, a way to check upstream health, and a maintenance window for reloads. Commands below target a Linux host using systemd; package and service names can differ by distribution.

Capture the current build and configuration paths:

nginx -v
nginx -V 2>&1
nginx -T

nginx -V reports build options and compiled modules. nginx -T tests and prints the loaded configuration, including included files. Treat its output as sensitive if it contains credentials or internal details, and don't paste it into a public ticket.

Inspect capacity and current connections before changing worker settings:

nginx -T 2>&1 | grep -E 'worker_processes|worker_connections|worker_rlimit_nofile'
ulimit -n
ss -s

The official core documentation lists a default of 512 simultaneous connections per worker, and clarifies that the count includes connections to proxied upstream servers. The effective ceiling can also be constrained by the process file-descriptor limit. NGINX core module documentation Raising worker_connections alone won't overcome a lower service file-descriptor limit, and neither setting prevents an attack that saturates the network link before packets reach the host.

Back up the configuration before editing. On Debian or Ubuntu, a common configuration root is /etc/nginx; verify yours with nginx -T first.

sudo cp -a /etc/nginx "/etc/nginx.backup-$(date +%Y%m%d%H%M%S)"
sudo nginx -t
CheckCommandInformation captured
NGINX buildnginx -V 2>&1Version and compiled modules
Loaded configurationnginx -TActive directives and included files
Worker and file limitsnginx -T and ulimit -nConfigured connection ceiling and shell limit
Socket statess -sCurrent connection summary
Baseline logstail -n 100 /var/log/nginx/access.logRecent requests and response codes

A directive can be valid in principle and still fail on a particular build if its module isn't available. A rate limit also needs a shared-memory zone declared in the http context before a server or location can use it. Keep the pre-change backup until the modified configuration has passed syntax checks and the service has returned to its normal state.

For every edit, run sudo nginx -t before reloading. If the check passes, use sudo systemctl reload nginx; if the check fails, don't reload. To roll back, restore the saved configuration, run the syntax check again, and reload only after it passes.

Configure Rate and Connection Limits Safely

A limit is only as useful as its key. Limiting by $binary_remote_addr is simple, but it assumes each address represents one client. That assumption breaks behind corporate NAT, mobile carrier gateways, and other shared IPv4 networks. On IPv6, the choice of address granularity also affects whether a user's changing address or a group of addresses is treated as one identity. A strict per-address policy can block legitimate customers while attackers spread requests across many addresses.

The configuration below demonstrates the mechanism, not a universal production threshold. NGINX's documentation gives 10 requests per second as an example rate, corresponding operationally to about one request every 100 milliseconds because the module tracks requests at millisecond granularity. The rate and burst values below are starting values to evaluate against your own endpoint traffic, not a promise that they're safe for every application.

A server rack in a data center with blue network cables and glowing LED indicator lights.

Declare zones inside the http block. The rate zone tracks request activity per key. The connection zone provides a separate per-key connection control.

http {
    limit_req_zone $binary_remote_addr zone=per_ip_requests:10m rate=10r/s;
    limit_conn_zone $binary_remote_addr zone=per_ip_connections:10m;

    log_format limits '$remote_addr $status $request_method $uri '
                      'request_time=$request_time upstream_time=$upstream_response_time';

    server {
        listen 80;
        server_name example.invalid;

        access_log /var/log/nginx/access.log limits;

        location = /login {
            limit_req zone=per_ip_requests burst=10;
            limit_req_status 429;

            limit_conn per_ip_connections 10;
            limit_conn_status 429;

            proxy_pass 
        }

        location / {
            proxy_pass 
        }
    }
}

The burst setting allows a short queue of requests above the sustained rate. By default, NGINX can delay excess requests within the configured burst; adding nodelay changes that behavior by passing requests in the burst immediately, while still enforcing the limit afterward. Once the permitted burst is exhausted, excess requests are rejected. A delayed response can smooth a brief spike, but it also consumes time and may not be appropriate for latency-sensitive endpoints. Rejection protects the upstream sooner, but can affect legitimate bursts.

Choose a key that matches the traffic

Use separate policies for anonymous pages, login, search, checkout, API requests, webhooks, and authenticated users. A search endpoint may do expensive work that ordinary static pages don't. A webhook sender may retry after a slow response, so an indiscriminate policy can amplify delivery failures.

Traffic classRecommended identity sourceControl to tuneMain false-positive risk
Anonymous pagesClient address, only when the address is trustworthyRequest rate and burstShared networks and carrier NAT
LoginClient address plus application-level account controlsRequest rate and rejection behaviorShared offices and mobile gateways
Authenticated APIA validated account or token identity where availablePer-identity quota and endpoint costMany users sharing one credential
WebhooksKnown sender identity and application verificationRate and retry handlingLegitimate retries grouped as abuse
HTTP/2 or HTTP/3 clientsRequest identity, not a simple connection countRequest rate plus endpoint policyMultiplexed requests misread as one-to-one connections

If NGINX sits behind a proxy or CDN, $remote_addr may identify that proxy instead of the visitor. Configure real client address handling only for proxy addresses you trust. Never trust arbitrary forwarded headers from the public internet, because a client could forge them and evade an address-based limit.

http {
    set_real_ip_from 192.0.2.0/24;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    limit_req_zone $binary_remote_addr zone=per_ip_requests:10m rate=10r/s;
}

Replace the example proxy range with the actual trusted proxy ranges supplied by your provider. Don't copy the documentation range as a live setting. Confirm the resulting $remote_addr in logs before enforcing limits. For identity-aware limits, use a validated application identity only when the application can reliably set it; an untrusted request header isn't an identity.

HTTP/2 and HTTP/3 can multiplex requests over a connection, so a connection count isn't a request count. Don't assume that limit_conn alone controls request floods. Likewise, don't set limits so aggressively that one user's parallel page assets or API calls are treated as abuse.

This is what the policy looks like operationally: one noisy tenant or endpoint can drive upstream pressure while other sites on shared hardware remain healthy. A per-IP rule can make the symptoms disappear for one address and still miss a distributed flood. Watch the affected endpoint, upstream latency, and client complaints together.

For a practical walkthrough of the request-to-upstream path, this video provides additional context:

Validate the completed file and observe the behavior before adjusting it:

sudo nginx -t
sudo systemctl reload nginx
sudo tail -f /var/log/nginx/access.log

If legitimate requests start receiving 429, reduce enforcement or scope the rule to the expensive location, then retest. Roll back by restoring the saved configuration and reloading after nginx -t succeeds.

Filter Requests and Integrate Fail2ban

Rate limits control request frequency. Request filtering can reject methods or paths your application doesn't serve, but a deny rule must be narrow enough not to catch legitimate clients. Encoded paths, application-specific routes, and clients that rotate addresses all make simplistic pattern matching risky.

If the application only accepts a known set of methods, reject other methods in the relevant location rather than across unrelated virtual hosts:

location /api/ {
    limit_except GET POST {
        deny all;
    }

    proxy_pass 
}

Check the application's method requirements before deploying this. For example, a client may use PUT or DELETE legitimately. A broad regular expression against every URI can also block valid encoded paths or API routes. Prefer exact locations for known unwanted paths, and review access logs before adding a pattern.

Fail2ban can apply temporary firewall bans to repeated matching behavior, but it's not a substitute for upstream filtering. Use a dedicated filter and jail so you can disable only the new rule if it catches legitimate users. The example below matches repeated 404 responses. Confirm that your access log format places the status code where the expression expects it.

Create /etc/fail2ban/filter.d/nginx-repeated-404.conf:

[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD) .*" 404
ignoreregex =

Create /etc/fail2ban/jail.d/nginx-repeated-404.local:

[nginx-repeated-404]
enabled = true
filter = nginx-repeated-404
logpath = /var/log/nginx/access.log
backend = auto
findtime = 10m
maxretry = 20
bantime = 10m
action = nftables-multiport[name=nginx-repeated-404, port="http,https", protocol=tcp]

The threshold and durations are sample jail settings, not a universal policy. Verify the installed Fail2ban version and available action files on your distribution. Confirm the log path, then test the filter against a representative log entry before enabling the jail.

sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-repeated-404.conf
sudo fail2ban-client -t
sudo systemctl reload fail2ban
sudo fail2ban-client status nginx-repeated-404
sudo tail -n 50 /var/log/fail2ban.log

To remove a test ban, identify the address in jail status and unban it:

sudo fail2ban-client set nginx-repeated-404 unbanip 192.0.2.10

Replace the example address with the actual test address. For rollback, disable only nginx-repeated-404 in its jail file and reload Fail2ban. If the action created firewall rules that remain, inspect the active ruleset and remove only the rules associated with that jail. For host-level filtering and rule management, see ARPHost's Linux firewall guidance.

BehaviorNGINX responseFail2ban actionRollback
Unsupported methodReject in the scoped locationNone requiredRemove the method restriction
Repeated matching 404 responsesLog each responseTemporarily ban the matching addressDisable the dedicated jail and unban test clients
Valid route that resembles a blocked patternMay reject a legitimate requestCould extend the false positive into a banNarrow the match and remove the ban
Requests spread across many addressesPer-key limits may not catch the aggregatePer-address bans may have limited effectEscalate to edge or upstream controls

Choose Caching CDN and Upstream Protection

NGINX should sit at the application layer of a defense, not at the front of every decision. Cloudflare reported mitigating 6.9 million DDoS attacks in the fourth quarter of 2024; 49% were Layer 3 or Layer 4 attacks and 51% were HTTP DDoS attacks. Those categories matter because NGINX can regulate HTTP requests that reach it, but it can't stop network congestion that prevents traffic from reaching the server. Cloudflare's fourth-quarter 2024 DDoS report

Choose a mitigation layer based on where the pressure occurs and what traffic it can safely handle:

LayerBest-fit trafficPrimary protectionEscalate when
NGINX request limitsHTTP requests reaching the originEndpoint-specific rate and connection controlsDistributed requests remain above safe application thresholds
Reverse-proxy cacheRepeated, cacheable contentReduces repeated origin workAttack traffic bypasses cache or targets dynamic paths
CDN filteringPublic web traffic and distributed HTTP requestsEdge request filtering and cachingNetwork or provider capacity is still under pressure
Provider filtering or scrubbingNetwork floods before the hostFilters traffic upstream of the originProvider cannot preserve service or route clean traffic
Load balancer controlsConnection and TLS pressure at the edgeConnection handling and TLS terminationThe balancer itself reaches its capacity
Application authenticationLogin, account, and mutation endpointsRestricts costly actions to validated identitiesAbuse uses valid identities or overwhelms shared services

A cache helps only when responses can be safely reused. Static assets and public pages are often better cache candidates than login, search, checkout, personalized pages, or API mutations. Caching a response that varies by user without correct cache controls can expose private content. Set cache behavior deliberately, and verify that the origin receives fewer requests for the content you intended to cache.

Escalate beyond NGINX when bandwidth rises while application logs show little corresponding request activity, or when the load balancer and TLS terminator are struggling before requests reach the origin. For HTTP floods, compare request patterns and upstream latency, then coordinate edge filtering with endpoint-specific NGINX controls. A load-balancing design also affects where connections and TLS work accumulate; see ARPHost's load balancing setup guide.

The trade-off is straightforward: controls closer to the origin can be precise about application paths, but they act after traffic has traversed the network and any upstream infrastructure. Edge and provider controls can discard traffic earlier, but need enough visibility to avoid blocking legitimate users.

Test Monitoring and Roll Back the Protection

A successful nginx -t proves syntax, not resilience. Test the policy in a staging environment or against a controlled endpoint first. Don't generate disruptive traffic against a production service to simulate an attack.

Use a client you control to request the protected path repeatedly, then inspect response codes and timing:

sudo nginx -t
curl -i 
sudo tail -f /var/log/nginx/access.log
ss -s

Replace the example hostname with your test endpoint. Watch NGINX access and error logs, upstream latency, CPU, memory, bandwidth, socket counts, file descriptors, and TLS or worker pressure. A rising 429 count can show that a policy is working, but also check for legitimate-user complaints and failed application transactions.

Verification targetTest or commandHealthy resultRollback or escalation
Configuration syntaxsudo nginx -tTest succeedsRestore backup if invalid
Request policycurl -i and access-log reviewExpected requests pass or receive the intended responseNarrow or remove the affected location policy
Socket pressuress -sCounts remain consistent with service behaviorCheck connection sources and host capacity
Worker and file limitsnginx -T and ulimit -nEffective limits support expected proxy and client connectionsAdjust service limits only after measuring demand
Upstream healthApplication health check and NGINX error logBackend remains responsiveInvestigate backend capacity or connectivity
Network saturationHost and provider bandwidth telemetryTraffic reaches the host without link pressureEscalate to provider or upstream filtering

On shared infrastructure, a faulty tenant identity rule can create a real availability problem without crashing NGINX. 502 and 504 responses may rise as upstream work backs up, while one site's 429 responses reflect a false positive rather than a successful defense.

Practice rollback before an incident. Restore the backup, run sudo nginx -t, reload only if it passes, and confirm that health checks and normal requests recover. If the limit module is missing, remove unsupported directives or install a build that includes the required module. If clients appear as the proxy address, correct trusted proxy handling before applying per-address rules. If Fail2ban bans persist, inspect the jail's firewall action and remove only its rules. If CDN behavior differs from origin behavior, check cache status and ensure the origin is receiving the expected request headers.

Maintain DDoS Protection After Deployment

Assign an owner to the NGINX policy and record why each location uses its chosen identity and limit. Review normal traffic before changing thresholds, periodically test Fail2ban, and retain configuration backups that can be restored and validated. Keep escalation contacts for the application owner, network provider, CDN, and load balancer operator.

Recurring taskOwnerEvidence to retainEscalation trigger
Review limits against traffic patternsNGINX operatorConfiguration and access-log reviewLegitimate clients receive repeated rejections
Test bans and removalSecurity or systems operatorJail status and test resultBans persist after the jail is disabled
Verify backups and rollbackService ownerBackup and syntax-check resultRestore fails or service health does not recover
Review network and upstream signalsNetwork and application operatorsBandwidth, socket, and upstream observationsLink, TLS, or backend capacity is exhausted

In multi-tenant operations, a quiet NGINX process doesn't prove the service is healthy; an upstream can be failing while requests continue to arrive. NGINX is useful for HTTP controls after traffic reaches the server. Bandwidth saturation, connection exhaustion, and TLS pressure need upstream filtering, edge capacity, and a clear escalation path.


For teams that need help operating the full stack around NGINX, ARPHost provides managed IT and infrastructure services alongside VPS, bare metal, colocation, and private cloud options. Review ARPHost, LLC to discuss where application controls end and managed or upstream protection should begin.

Tags: , , , ,

Leave a Reply