A download is working when four things are true: every byte arrives, no byte is changed, it arrives in reasonable time, and a broken connection can be recovered from. Each can be tested with a file whose size and checksum you know beforehand. Every sample on this site publishes both.
1. Is the file complete?
Compare sizes. Download a sample and print its byte count:
curl -sS -O https://your-cdn.example/sample-bin-10mb.bin
wc -c < sample-bin-10mb.binA 10 MB sample must report exactly 10485760. A smaller number means the transfer stopped early; a different number altogether usually means something rewrote the content.
You can ask the server what it intends to send without downloading anything:
curl -sSI https://your-cdn.example/sample-bin-10mb.binLook for Content-Length: 10485760 in the response. If the header is missing, the server is streaming with chunked encoding, and clients cannot show accurate progress.
2. Is the file unmodified?
Size alone does not prove integrity. Compute a SHA-256 checksum and compare it with the value on the sample's download page.
sha256sum sample-bin-10mb.bin # Linux
shasum -a 256 sample-bin-10mb.bin # macOSOn Windows, in PowerShell:
Get-FileHash .\sample-bin-10mb.bin -Algorithm SHA256A mismatch with a correct size points to content being altered in transit. The usual culprits are proxies that rewrite text, and line-ending conversion when a text file passes through a tool running in text mode.
3. The compression trap
Servers and CDNs compress text responses. That is good for users and misleading for testers, in two ways.
First, speed measurements taken with a text file are inflated. A 10 MB text file may travel as 2 MB. Use a BIN sample, whose random bytes cannot be compressed, whenever you are measuring the network.
Second, a file that is already gzip-compressed can be mangled by a misconfigured server. If a .gz file is sent with Content-Encoding: gzip, the client decompresses it automatically and saves decompressed content under a .gz name. To see what a server really sends, request without accepting any encoding and inspect the headers:
curl -sS -D - -o /dev/null -H 'Accept-Encoding: identity' \
https://your-cdn.example/sample-gz-1mb.gzA correctly configured server answers with Content-Type: application/gzip and no Content-Encoding header.
4. How fast is it?
curl can report transfer statistics. This discards the body and prints the status, size, average speed and total time:
curl -sS -o /dev/null \
-w '%{http_code} %{size_download} bytes %{speed_download} B/s %{time_total} s\n' \
https://your-cdn.example/sample-bin-10mb.binFor a fair measurement:
- Use an incompressible file large enough to run for several seconds.
- Run the command at least five times and take the median.
- Treat the first request separately. It may be a cache miss at the CDN, which is worth measuring in its own right.
- Add
%{time_starttransfer}to see the time to first byte, which separates server delay from transfer time.
5. Range requests
Range requests let a client ask for part of a file. Video seeking, resumable downloads and parallel download managers all depend on them. Ask for the first 1,024 bytes:
curl -sS -D - -o part.bin -r 0-1023 \
https://your-cdn.example/sample-mp4-10mb.mp4A server that supports ranges replies with:
- status
206 Partial Content Content-Range: bytes 0-1023/10485760Content-Length: 1024
If it answers 200 OK with the whole file, ranges are not supported. In a browser the symptom is a video that plays from the start but cannot be dragged to a new position.
Also request a range that starts beyond the end of the file, for example -r 99999999-. The correct response is 416 Range Not Satisfiable.
6. Resuming
Interrupt a download with Ctrl+C partway through, then continue it:
curl -C - -O https://your-cdn.example/sample-bin-10mb.binThe -C - option tells curl to look at the partial file and request only the remainder. When it finishes, verify the checksum. A resumed file with the right size and the wrong checksum means the server sent different content the second time, which happens when a file is replaced during the download and the client does not check ETag or Last-Modified.
7. Headers and file names
When a download is triggered from a web page, the response headers decide what the browser does.
Content-Disposition: attachment; filename="report.pdf"forces a download and suggests a name.Content-Typeshould match the real type. WithX-Content-Type-Options: nosniff, browsers will not guess.Cache-ControlandETagdetermine whether repeat downloads are served from cache.
File names with spaces or non-ASCII characters need the extended filename*=UTF-8''... form. Test with the samples that have spaces and non-ASCII characters in their names, and check the name the browser actually saves.
8. In the browser
Finish with a manual pass in each browser you support, including one phone:
- Click the download link and confirm the file is saved with the expected name.
- Throttle the network in developer tools and confirm that progress is shown.
- Go offline during the download and confirm that the failure is visible and that retrying works.
- Download the same file twice and check whether the second request is answered from cache when it should be.
For automated browser checks, the Playwright guide shows how to capture a download and assert on its size.