Uploading a two-gigabyte video over coffee shop WiFi is an exercise in optimism. You watch the progress bar crawl toward seventy percent, and then the connection flickers. The upload fails. When you try again, the server expects a brand new file. You start from zero. For anyone building an application that handles user-generated video, this is not an edge case. It is the norm. The tus protocol was built specifically to kill this problem. It is an open standard that runs over ordinary HTTP and gives uploads a memory. When the network recovers, the file transfer picks up exactly where it left off.
Why Standard Uploads Break
Standard multipart file uploads treat the entire payload as a single transaction. The browser or app opens a TCP connection, streams the bytes, and expects a 200 OK at the end. If the connection resets because the user switched from LTE to WiFi, or because the laptop went to sleep, the server has no way to know which bytes arrived safely. Most frameworks simply discard the partial data. The user is left with a failed form and a bad mood. Video files amplify the pain because they are large, often uploaded from mobile devices on unstable networks, and frequently encoded in formats that cannot be processed until the entire file is intact.
How Tus Changes the Game
Tus rethinks the upload as a stateful conversation rather than a one-shot delivery. Instead of sending a file in one continuous spray of bytes, the client breaks the payload into chunks. More importantly, the server remembers the offset, the exact count of bytes it has received so far. This state persistence is what makes resumption possible. The protocol is intentionally simple. It does not require WebSockets, gRPC, or proprietary SDKs. It uses the HTTP methods you already know.
The Three Requests That Power Resumption
Every tus upload follows a predictable dance.
First, the client sends a POST request to a known tus endpoint. The headers describe the file. The Upload-Length header states the final size in bytes, and optional metadata such as the filename or content type travels in the Upload-Metadata header as a comma-separated list of base64-encoded key-value pairs. The server creates an empty upload resource and responds with a 201 Created status plus a Location header pointing to a unique URL. This URL is the upload's address for the rest of its life.
Next comes the PATCH request. The client sends chunks of raw binary data to that unique URL. Two critical headers accompany the payload. Content-Type must be application/offset+octet-stream, and Upload-Offset must match the byte position where this chunk begins. The server verifies that the offset matches its own records. If it does not, the server returns a 409 Conflict, guarding against corrupted state. If everything aligns, the server appends the bytes, updates its internal offset, and returns a 204 No Content response alongside the new Upload-Offset. The chunk size is configurable, but common practice lands somewhere between a few hundred kilobytes and a couple of megabytes. Smaller chunks create more HTTP overhead but recover faster from errors. Larger chunks reduce round trips but waste more bandwidth if they fail mid-flight.
Finally, the HEAD request. This is the safety net. If a PATCH fails midway through a chunk, say after three megabytes of a five-megabyte slice, the connection drops. When the client regains network access, it sends a HEAD request to the upload URL. The server replies with the current Upload-Offset. The client compares
