How to Build Resumable File Uploads That Survive Flaky Connections

Close-up of a network switch with patch cables plugged into organized ports

A user on a hotel Wi-Fi connection uploads a 400MB video. At 94 percent, the connection blips for two seconds. Most upload implementations respond to that blip by throwing away all 94 percent and starting over. The user tries twice more, gives up, and either abandons the task or emails the file some other way. None of that had to happen. The data that already made it to the server was fine. The upload form just wasn't built to know that.

Resumable uploads solve exactly this problem, and they're not exotic to build once you understand the three pieces involved: splitting the file into addressable chunks, tracking which chunks have landed, and giving the client a way to ask "where do I pick back up." Most teams skip this because a plain single-request upload works fine in the demo, on office Wi-Fi, with small test files. It stops working the moment real users hit it with real networks and real file sizes.

Why a Single PUT or POST Request Isn't Enough

A standard file upload is one HTTP request carrying the entire file as its body. That works fine for a 2MB profile photo. It becomes a liability past roughly 20 to 50MB, for a few compounding reasons:

  • All-or-nothing failure. If the connection drops at any point during transfer, the whole request fails and none of the bytes that already arrived are usable.
  • No progress recovery. Browsers don't expose a way to resume a single in-flight request after a network interruption; the client has to start a fresh request from byte zero.
  • Timeout pressure. Reverse proxies and load balancers often cap request duration. A single request carrying a large file can hit that ceiling even on a healthy connection if the file is big enough.
  • Memory pressure server-side. Buffering an entire large file in memory before writing it to storage doesn't scale well under concurrent uploads.

Chunking sidesteps all four. Instead of one request carrying the whole file, the client sends the file as a sequence of smaller requests, each carrying one chunk, each independently retryable.

What "Resumable" Actually Requires

Close-up of a terminal window showing monospace text scrolling
Photo by Ec lipse on Pexels

Chunking alone doesn't make an upload resumable. Plenty of chunked upload implementations still restart from chunk one after a failure, because nothing on the server remembers which chunks already succeeded. Resumability needs three things working together:

  1. A stable identifier for the upload session. Something the client can hold onto (an upload ID returned when the session starts) that ties every subsequent chunk request back to the same in-progress file.
  2. Server-side tracking of received chunks. A lightweight record, keyed by upload ID, of which byte ranges or chunk indexes have already been written to storage.
  3. A status endpoint the client can query. Before resuming, the client asks the server "what have you got so far," gets back an offset or a set of missing chunk indexes, and only sends what's actually missing.

Skip any one of these and you get chunked uploads that are more resilient to a single dropped chunk, but not truly resumable across a full session interruption, like closing the browser tab and coming back an hour later.

Designing the Chunk Protocol

Rows of server rack cabling organized and labeled in a data center
Photo by Josh Sorenson on Pexels

The specifics vary by implementation, but a workable chunk protocol needs to answer four questions up front, before writing any code:

Chunk size. Too small and you pay request overhead on every chunk; too large and you lose the granularity that makes resuming worthwhile. 5 to 10MB per chunk is a reasonable default for most web uploads, small enough to retry cheaply, large enough to keep request counts sane for multi-gigabit files.

Ordering. Chunks can be sent strictly in order, or in parallel with the server reassembling them by index. Parallel chunking finishes faster on fast connections but adds complexity to the reassembly logic and needs the server to track out-of-order arrivals correctly.

Integrity checking. Each chunk should carry a checksum (an MD5 or SHA-256 hash of that chunk's bytes) that the server verifies before acknowledging receipt. Without this, a chunk can arrive silently corrupted and get treated as successful.

Finalization. The client needs an explicit "I'm done, assemble the file" signal once all chunks have landed, rather than the server guessing completion from chunk count alone, since a chunk can be legitimately retried and arrive twice.

The IETF's HTTP Range Requests specification documents the underlying Range and Content-Range headers that many resumable upload implementations build on, and it's worth reading even if you end up rolling a custom chunk index instead of literal byte ranges, because the resume semantics it describes (ask what's missing, send only that) are the same pattern either way.

Tracking Upload State on the Server

The server-side tracking record doesn't need to be complicated. For most applications it's a single row per in-progress upload: an upload ID, the total expected size or chunk count, a bitmap or set of received chunk indexes, a storage path for the assembled parts, and a timestamp for cleanup. A simple table beats an elaborate state machine here.

That last field matters more than it looks. Abandoned uploads accumulate indefinitely in both your tracking table and temporary storage otherwise. A scheduled cleanup job that expires sessions after 24 to 48 hours of inactivity, deleting the record and any partial chunks on disk, keeps this from becoming a slow storage leak nobody notices until a disk fills up.

Resuming After a Dropped Connection

When the client reconnects after a failure, the flow is straightforward if the tracking pieces above are in place:

  1. Client sends the stored upload ID to a status endpoint.
  2. Server responds with the highest confirmed chunk index (or a full bitmap of received chunks, if parallel upload is supported).
  3. Client resumes sending from the first missing chunk, skipping everything already confirmed.
  4. Once all chunks are confirmed, the client sends the finalize request and the server assembles the file.

This is also where idempotency earns its keep. A chunk can arrive twice, once from the original attempt and once from the resume, if the acknowledgment for the first attempt got lost even though the data landed fine. The server needs to treat re-sending an already-received chunk as a safe no-op rather than a duplicate-write error, keyed on chunk index rather than arrival order.

Handling the Backend: Custom Server vs Managed Storage

Strands of fiber optic cable glowing with transmitted light
Photo by Guillaume Meurice on Pexels

Two broad approaches cover most real-world implementations:

Build it yourself. A custom endpoint that accepts chunks, tracks state in your own database, and assembles the final file on your own storage. Full control, but you own every edge case: partial writes, concurrent arrival, cleanup, storage integration.

Use the resumable upload support your storage provider already has. Major cloud storage platforms support multi-part or resumable uploads natively. Amazon S3's multipart upload feature lets a client upload a large object as a set of independently retryable parts, with S3 handling reassembly once everything is confirmed. That avoids reinventing chunk tracking for the common case of "upload a file, store it in object storage."

If you'd rather not build the client protocol from scratch, tus is an open, well-specified resumable upload protocol with server and client implementations across most major languages and frameworks. It solves the offset-tracking problem described above and is worth evaluating before writing a custom protocol.

The Browser Side: Splitting Files Without Loading Them Fully Into Memory

Client-side chunking needs to slice a File object into pieces without reading the entire file into memory first, which matters on memory-constrained devices. The browser's File and Blob APIs support slicing a File into smaller Blob ranges lazily, so each chunk is only read into memory as it's about to be sent.

Combined with the Fetch API's support for streaming request bodies, a client can walk through a multi-gigabit file chunk by chunk without ever holding more than one chunk's worth of data in memory. The same principle applies server-side: stream each incoming chunk directly to storage rather than buffering the full request body first.

Testing This Under Real Network Conditions

The bug that resumable uploads exist to fix only shows up under conditions most local development environments never hit. Testing needs to simulate the failure modes deliberately:

  • Throttle bandwidth to something closer to real mobile or hotel Wi-Fi speeds, not gigabit fiber.
  • Kill the connection mid-upload (not just close the tab, but actually drop the network) and confirm the client detects the failure and can resume.
  • Send a chunk twice on purpose to confirm the server treats the duplicate as a no-op instead of an error or a double-write.
  • Let an upload session sit idle for longer than your cleanup window and confirm the client gets a clear "session expired, start over" response instead of a silent failure.

"The failure mode nobody tests for is the upload that succeeds on the server but never gets acknowledged back to the client. The bytes are safe, but if the client doesn't know that, it'll resend them anyway, and your server needs to shrug and say 'already got it' instead of choking on a duplicate." - Dennis Traina, founder of 137Foundry

Common Mistakes Worth Naming Directly

  • Treating chunking and resumability as the same feature. Splitting a file into pieces doesn't automatically make an upload resumable if nothing tracks which pieces already succeeded.
  • No expiration on abandoned upload sessions. Partial chunks and tracking rows accumulate forever without a cleanup job.
  • Buffering full chunks in memory unnecessarily, especially server-side, defeating the memory benefit chunking was supposed to provide.
  • Skipping checksum verification. A silently corrupted chunk that gets acknowledged as successful is worse than one that visibly fails, because it surfaces later as a corrupted file with no clear cause.
  • Building a custom protocol when a managed one would do. Rolling your own chunk tracking is worthwhile when you have unusual requirements, but for a standard "let users upload large files reliably" need, an existing protocol or your storage provider's native support usually gets there faster with fewer edge cases to own.

Building This Into an Existing Upload Flow

Retrofitting resumability into an upload feature that was built as a single request is more approachable than it sounds, because the client-facing change is additive rather than a rewrite. You add a session-start endpoint, a status endpoint, and a chunk endpoint, and the existing single-request path can stay as a fallback for small files that don't need chunking at all.

If your team is planning this kind of reliability work, it's worth folding into a broader web development engagement rather than patching it in isolation, since upload reliability tends to surface the same patterns (retry logic, idempotency, state tracking) that show up elsewhere in a product. 137Foundry's web development team builds this kind of infrastructure directly for client products, and the about page has background on the team doing the work. More on how 137Foundry approaches this kind of build is at 137foundry.com.

The Short Version

A single request carrying an entire file is fine for small uploads and a liability for large ones. Chunking fixes the failure blast radius, but only tracking state server-side and giving the client a way to ask "what's missing" makes an upload actually resumable. Add checksum verification, session expiration, and idempotent chunk handling, and an upload flow stops punishing users for a network blip that had nothing to do with them.

Need help with Web Development?

137Foundry builds custom software, AI integrations, and automation systems for businesses that need real solutions.

Book a Free Consultation View Services