← News
/PLAYBOOK · 2026-09-09

Nginx Request Limits in Practice: Body Size and Timeout Anti-Abuse Config

Nginx with no body limits gets disks filled by large-file POSTs; timeouts that are too wide let slow attacks fill workers. Covers tightening client_max_body_size at three scopes, precise relax on upload endpoints, timeout tuning, PHP param sync, and custom 413 pages. Includes verify commands and common error triage.

Applicable scenarios

When the disk fills with large uploads, workers fill with slow requests, or PHP processes die on oversized POSTs, close the gap with Nginx request limits. Body-size limits and timeouts are Nginx's first anti-abuse gate: an unprotected server lets any visitor POST several GB at once and exhaust disk, or hold connections at a crawl and starve resources. This article gives a complete body-size and timeout config.

Prerequisites

How it works

Nginx segments client-request control into two parameter groups. Body limits: `client_max_body_size` sets the body cap; over limit returns 413 immediately. `client_body_buffer_size` controls in-memory body buffering; overflow spills to a temp file on disk. Timeouts: `client_body_timeout` and `client_header_timeout` cap the interval between two client read events; `keepalive_timeout` controls idle connection hold time; `send_timeout` caps the interval when sending a response to the client. Slow attacks (e.g. Slowloris variants) abuse the default generous no-timeout behavior, sending incomplete requests and occupying workers—tighten timeouts to the 10-second range and attack payoff drops sharply. Note PHP-side `upload_max_filesize`/`post_max_size` must stay in sync with Nginx, or 413 and app errors diverge.

Procedure

Step 1: Global limit config

Write baseline limits in the `http {}` layer (edit `/etc/nginx/nginx.conf`):

`http {
    # Global request-body cap 1MB (tighten by default on sites with no upload need)
    client_max_body_size 1m;

    # Request-body memory buffer 128K; overflow spills to disk
    client_body_buffer_size 128k;

    # Max interval between two read events 10s; prevent slow-attack occupation
    client_body_timeout 10s;
    client_header_timeout 10s;

    # Idle keepalive connections disconnect after 15s
    keepalive_timeout 15s;

    # Response send interval cap 30s
    send_timeout 30s;

    # ... remaining http config
}`

Step 2: Relax only the upload location

Businesses that need uploads override in the matching `location` so the global limit stays tight:

`server {
    listen 80;
    server_name example.com;

    # Ordinary pages inherit the global 1m limit

    # Upload endpoint relaxed separately to 50m
    location /api/upload {
        client_max_body_size 50m;
        client_body_timeout 60s;   # Large-file transfers allow a longer interval
        proxy_pass http://127.0.0.1:8080;
    }
}`

Key point: client_max_body_size can be set at http/server/location; inner overrides outer. Timeouts work the same. Relaxation must be the smallest scope—only the upload endpoint, not the whole site.

Step 3: Sync PHP-side parameters

On php-fpm, edit `php.ini` to match Nginx:

`upload_max_filesize = 50M
post_max_size = 52M
memory_limit = 128M`

Note post_max_size must be slightly larger than upload_max_filesize (body includes multipart overhead). After change run `systemctl reload php-fpm`. If reverse-proxying, also add backend read timeouts:

`location / {
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
    proxy_pass http://127.0.0.1:8080;
}`

Step 4: Custom 413 error page

The default 413 page leaks the Nginx version, and some browsers render 413 poorly. Replace it:

`server {
    error_page 413 /413.html;
    location = /413.html {
        internal;
        return 200 '{"code":413,"msg":"Request body exceeds size limit"}';
        default_type application/json;
    }
}`

Config verification

`# 1. syntax check and reload
nginx -t && nginx -s reload

# 2. Over-limit request should return 413
dd if=/dev/zero of=/tmp/test10m bs=1M count=10
curl -s -o /dev/null -w "%{http_code}\n" -X POST --data-binary @/tmp/test10m http://example.com/

# 3. Normal requests are unaffected
curl -s -o /dev/null -w "%{http_code}\n" http://example.com/

# 4. Confirm PHP-side parameters match
php -i | grep -E "upload_max_filesize|post_max_size"`

Step 2 should output 413; step 3 should output 200. If step 2 returns 200, the location-layer relax is too broad—recheck config scope.

FAQ

FAQ 1: Large uploads return 413, but the business really needs big attachments

Do not just raise global `client_max_body_size`. Locate the upload endpoint location (e.g. `location /api/upload`), relax only there, sync php.ini `upload_max_filesize` and `post_max_size`, then reload php-fpm. Keep global at 1m tight; relax only the smallest path.

FAQ 2: Client $_POST is empty but Nginx did not return 413

Typical case: PHP-side limit is smaller than Nginx. Body exceeded `post_max_size`; PHP drops POST data silently. Check `php -i | grep post_max_size`, raise it to match `client_max_body_size` (about 2MB larger) and it recovers.

FAQ 3: When the 413 arrives the connection is reset; custom error page never shows

After Nginx sees Content-Length over limit it first finishes reading already-arrived buffers then responds. Client disconnects early so the error page cannot be delivered. Normal behavior; block still works. If the frontend depends on a JSON error code, validate size on the client first; server 413 is the backstop.

Summary

Nginx request limits are the lowest-cost, clearest-payoff anti-abuse config: body caps stop large-packet disk floods and memory exhaustion; timeouts shrink slow-attack payoff; three-layer scope lets you tighten globally and relax per endpoint. Execution: global default tight (1m/10s), upload path relaxed precisely, PHP params synced with Nginx, custom error page to avoid info leak. Review each new business's body and timeout needs at launch so the limit policy keeps converging as the product evolves.