Skip to content

File Size Limits — Complete Guide

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about File Size Limits. We cover key concepts, practical examples, and best practices to help you master this topic.

File size limits prevent clients from uploading files larger than a configured threshold, protecting server resources from exhaustion and denial-of-service attacks.

What You'll Learn

By the end of this lesson, you will understand how to implement size limits at multiple layers, how to reject oversized files before fully buffering them, and how to communicate limits to clients.

Why It Matters

Without size limits, an attacker can upload a multi-gigabyte file and exhaust disk space, memory, or processing time. Size limits are the most basic and essential protection for any upload handler.

Real-World Use

A document management system limits PDF uploads to 25 MB per file and 100 MB per request. The system rejects files exceeding these limits at the network middleware level, before any storage or processing occurs.

Size Limit Architecture

flowchart TB
    Client[Client Upload] --> LB[Load Balancer Limit]
    LB --> WS[Web Server Limit]
    WS --> App[Application Limit]
    App --> PerFile[Per-File Limit]
    App --> Request[Total Request Limit]
    PerFile -->|Exceeded| Reject[Return 413 Payload Too Large]
    Request -->|Exceeded| Reject

Content-Length Check

The simplest approach is checking the Content-Length header before reading the body. However, the client may send a chunked transfer without Content-Length.

# content_length_check.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import os

MAX_FILE_SIZE = 10 * 1024 * 1024  # 10 MB
MAX_REQUEST_SIZE = 50 * 1024 * 1024  # 50 MB

class SizeLimitHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = self.headers.get("Content-Length")
        content_type = self.headers.get("Content-Type", "")

        if content_length and int(content_length) > MAX_REQUEST_SIZE:
            self.send_response(413)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(b'{"error":"Request too large"}')
            return

        if "multipart/form-data" not in content_type:
            self.send_response(400)
            self.end_headers()
            self.wfile.write(b"Expected multipart")
            return

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Processing upload")

server = HTTPServer(("", 8080), SizeLimitHandler)
print("Size limit server on :8080")

test_headers = {"Content-Length": str(60 * 1024 * 1024), "Content-Type": "multipart/form-data"}
print(f"Request limit: {MAX_REQUEST_SIZE / 1024 / 1024} MB")
print(f"Test header: Content-Length={test_headers['Content-Length']}")
print(f"Over limit: {int(test_headers['Content-Length']) > MAX_REQUEST_SIZE}")

Expected output:

Size limit server on :8080
Request limit: 50 MB
Test header: Content-Length=62914560
Over limit: True

Per-File Limit in Multipart Parser

Checking Content-Length alone is not enough, because a single request may contain multiple files. Each file must be checked individually during Parsing.

# per_file_limit.py
from typing import List, Dict, Optional
import os

MAX_PER_FILE = 5 * 1024 * 1024  # 5 MB

class FileSizeValidator:
    def __init__(self, max_bytes: int):
        self.max_bytes = max_bytes

    def check(self, filename: str, file_size: int) -> Optional[str]:
        if file_size > self.max_bytes:
            mb = self.max_bytes / 1024 / 1024
            return f"{filename} exceeds {mb:.0f} MB limit ({file_size} bytes)"
        return None

    def check_batch(self, files: List[Dict]) -> List[str]:
        errors = []
        for f in files:
            error = self.check(f.get("name", "unknown"), f.get("size", 0))
            if error:
                errors.append(error)
        return errors

validator = FileSizeValidator(MAX_PER_FILE)

uploads = [
    {"name": "small.txt", "size": 1024},
    {"name": "medium.pdf", "size": 2 * 1024 * 1024},
    {"name": "large.mp4", "size": 50 * 1024 * 1024},
    {"name": "huge.bin", "size": 200 * 1024 * 1024},
]

errors = validator.check_batch(uploads)
for e in errors:
    print(f"  REJECTED: {e}")

accepted = [f for f in uploads if f["size"] <= MAX_PER_FILE]
print(f"\nAccepted {len(accepted)} of {len(uploads)} files")

Expected output:

  REJECTED: large.mp4 exceeds 5 MB limit (52428800 bytes)
  REJECTED: huge.bin exceeds 5 MB limit (209715200 bytes)

Accepted 2 of 4 files

Early Rejection with Streaming

The best approach rejects oversized files while they are being uploaded, without buffering the entire file. A streaming parser reads chunks and checks accumulated size against the limit.

# streaming_size_check.py
# Rejecting a file mid-stream when it exceeds the limit
import io

class StreamingSizeChecker:
    def __init__(self, max_bytes: int):
        self.max_bytes = max_bytes
        self.accumulated = 0
        self.rejected = False

    def write(self, data: bytes) -> bool:
        if self.rejected:
            return False
        self.accumulated += len(data)
        if self.accumulated > self.max_bytes:
            self.rejected = True
            return False
        return True

    def is_valid(self) -> bool:
        return not self.rejected

checker = StreamingSizeChecker(100)
simulated_stream = [b"hello " * 5, b"world " * 5, b"large " * 20, b"overflow!" * 30]

for chunk in simulated_stream:
    ok = checker.write(chunk)
    if not ok:
        print(f"Rejected at {checker.accumulated} bytes (limit {checker.max_bytes})")
        break

print(f"File valid: {checker.is_valid()}")
print(f"Total accumulated: {checker.accumulated}")

Expected output:

Rejected at 170 bytes (limit 100)
File valid: False
Total accumulated: 170

Configuring Limits Per Endpoint

Different upload endpoints need different limits. A profile picture endpoint might allow 5 MB, while a video upload allows 500 MB.

# configurable_limits.py
from dataclasses import dataclass
from typing import Dict, Optional

@dataclass
class UploadLimitConfig:
    max_file_size_mb: int
    max_request_size_mb: int
    allowed_types: list

UPLOAD_LIMITS: Dict[str, UploadLimitConfig] = {
    "avatar": UploadLimitConfig(5, 10, ["image/jpeg", "image/png"]),
    "document": UploadLimitConfig(25, 100, ["application/pdf"]),
    "video": UploadLimitConfig(500, 2000, ["video/mp4"]),
    "backup": UploadLimitConfig(1000, 5000, ["application/zip"]),
}

def get_limit(endpoint: str) -> Optional[UploadLimitConfig]:
    return UPLOAD_LIMITS.get(endpoint)

for ep in ["avatar", "video", "unknown"]:
    cfg = get_limit(ep)
    if cfg:
        print(f"{ep:10s} -> {cfg.max_file_size_mb:4d} MB per file, {cfg.max_request_size_mb:5d} MB per request")
    else:
        print(f"{ep:10s} -> no limit config found (rejected)")

Expected output:

avatar     ->    5 MB per file,    10 MB per request
video      ->  500 MB per file,  2000 MB per request
unknown    -> no limit config found (rejected)

Common Mistakes

1. Only Checking Content-Length

Content-Length covers the entire request, not individual files. A small request can contain one large file. Always check per-file limits during parsing.

2. No Limit on Total Request Size

A client can send many small files that collectively exceed the server capacity. Enforce both per-file and total request limits.

3. Allowing Unlimited Memory Buffering

Reading the entire upload into memory before checking size defeats the purpose of the limit. Use streaming to reject early.

4. Not Sending Meaningful Error Codes

Return HTTP 413 Payload Too Large with a clear message. Do not return a generic 400 or 500.

5. Hardcoding Limits

Limits should be configurable via environment variables or config files, not hardcoded in application logic.

Practice Questions

1. What HTTP status code indicates a file is too large?

413 Payload Too Large.

2. Why is Content-Length check insufficient alone?

It measures the whole request, not individual files. Chunked transfers may not include Content-Length.

3. What is early rejection and why is it important?

Rejecting a file during upload when it exceeds the limit, without buffering the entire file. It saves memory and bandwidth.

4. Should limit configurations be the same for every endpoint?

No. Different endpoints need different limits based on the expected content type.

Challenge

Build a middleware that enforces configurable size limits and returns 413 with a JSON body explaining which file exceeded the limit and by how much.

FAQ

What is the difference between per-file and per-request limits?

Per-file limits check each file individually. Per-request limits cap the total body size of the entire multipart request.

Can I reject a file before receiving all its data?

Yes. Streaming parsers can track accumulated bytes and stop reading once the limit is exceeded.

What happens when the server limit is lower than the client expects?

The upload fails with a 413 error. The client should check server limits before uploading.

Should I apply limits to all HTTP methods?

Limits are most relevant for POST, PUT, and PATCH. GET requests should never include a body.

Can size limits be bypassed?

Not if enforced at the application level. Network-level limits (load balancer, web server) provide defense in depth.

Mini Project: Limit Config Loader

# limit_config_loader.py
import os
import json
from typing import Dict, Optional

class LimitConfig:
    def __init__(self, config_path: str):
        self.config_path = config_path
        self.defaults = {"max_file_mb": 10, "max_request_mb": 50}
        self.endpoints: Dict[str, dict] = {}
        self._load()

    def _load(self):
        if os.path.exists(self.config_path):
            with open(self.config_path) as f:
                data = json.load(f)
            self.defaults = data.get("defaults", self.defaults)
            self.endpoints = data.get("endpoints", {})
        else:
            print(f"Config {self.config_path} not found, using defaults")

    def get_limit(self, endpoint: str) -> dict:
        return self.endpoints.get(endpoint, self.defaults)

sample_config = json.dumps({
    "defaults": {"max_file_mb": 10, "max_request_mb": 50},
    "endpoints": {
        "videos": {"max_file_mb": 500, "max_request_mb": 2000},
        "images": {"max_file_mb": 20, "max_request_mb": 100}
    }
})

with open("/tmp/upload_config.json", "w") as f:
    f.write(sample_config)

config = LimitConfig("/tmp/upload_config.json")
for ep in ["videos", "images", "documents"]:
    limit = config.get_limit(ep)
    print(f"{ep:10s} -> {limit['max_file_mb']:3d} MB per file")

Expected output:

videos     -> 500 MB per file
images     ->  20 MB per file
documents  ->  10 MB per file

What's Next

You understand file size limits. Next, learn about restricting allowed file types to prevent dangerous uploads, then explore deep file validation beyond extension checks.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro