{"id":13356,"date":"2026-11-05T16:00:46","date_gmt":"2026-11-05T13:00:46","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/?p=13356"},"modified":"2026-11-05T06:57:23","modified_gmt":"2026-11-05T03:57:23","slug":"http-range-resumable-downloads-206-responses","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/http-range-resumable-downloads-206-responses\/","title":{"rendered":"Downloads Restart Instead of Resuming? Check HTTP Range and 206 Responses"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><div class=\"aiw-summary\">\n<p class=\"aiw-box-title\">Quick summary<\/p>\n<p>HTTP Range lets a download program request only the bytes it still needs after an interruption. Bytes are units of data used to measure file size. If downloads restart, the server may not be handling these requests correctly. To check, request a small portion of a file and inspect the response.<\/p>\n<ul>\n<li><code>206 Partial Content<\/code> indicates that the server is returning a portion rather than the complete file.<\/li>\n<li><code>Content-Range<\/code> identifies the returned bytes. Check it alongside the size of the response body-the file data actually sent.<\/li>\n<li><code>Accept-Ranges<\/code> alone does not prove downloads can resume.<\/li>\n<li>File changes, expired download links or an intermediate cache storing responses can affect resume requests.<\/li>\n<\/ul>\n<\/div>\n<p>Restarting a large ZIP or digital product download wastes time and data. Your browser is not always responsible: the application serving the file or an intermediary forwarding requests can also cause restarts.<\/p>\n<p>You will follow a hypothetical example on a separate Ubuntu test server without changing your live site: request a small portion, resume downloading and check that the completed file matches the source. This establishes how the test server handles partial downloads. You can then compare responses along your site&#8217;s actual download path to narrow down problems in the file server, application or intermediaries.<\/p>\n<div id=\"toc_container\" role=\"navigation\" aria-label=\"Table of Contents\" data-nosnippet class=\"toc_transparent no_bullets toc_numbered toc_title_center\"><p class=\"toc_title\">\u0130\u00e7indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Why_downloads_restart_and_how_HTTP_Range_helps\"><span class=\"toc_number toc_depth_1\">1.<\/span> Why downloads restart and how HTTP Range helps<\/a><ul><li><a href=\"#What_do_206_200_and_416_responses_tell_you\"><span class=\"toc_number toc_depth_2\">1.1.<\/span> What do 206, 200 and 416 responses tell you?<\/a><\/li><li><a href=\"#Why_isn8217t_Accept-Ranges_enough_on_its_own\"><span class=\"toc_number toc_depth_2\">1.2.<\/span> Why isn&#8217;t Accept-Ranges enough on its own?<\/a><\/li><\/ul><\/li><li><a href=\"#Set_up_the_example_environment_and_verify_a_partial_response\"><span class=\"toc_number toc_depth_1\">2.<\/span> Set up the example environment and verify a partial response<\/a><ul><li><a href=\"#Install_Nginx_and_create_the_test_file\"><span class=\"toc_number toc_depth_2\">2.1.<\/span> Install Nginx and create the test file<\/a><\/li><li><a href=\"#Configure_the_local_file_server\"><span class=\"toc_number toc_depth_2\">2.2.<\/span> Configure the local file server<\/a><\/li><li><a href=\"#Verify_the_first_1024_bytes_with_a_GET_request\"><span class=\"toc_number toc_depth_2\">2.3.<\/span> Verify the first 1024 bytes with a GET request<\/a><\/li><\/ul><\/li><li><a href=\"#Download_the_remaining_bytes_and_check_file_integrity\"><span class=\"toc_number toc_depth_1\">3.<\/span> Download the remaining bytes and check file integrity<\/a><ul><li><a href=\"#Save_the_first_portion_and_resume_into_the_same_file\"><span class=\"toc_number toc_depth_2\">3.1.<\/span> Save the first portion and resume into the same file<\/a><\/li><li><a href=\"#Test_a_version_mismatch_safely\"><span class=\"toc_number toc_depth_2\">3.2.<\/span> Test a version mismatch safely<\/a><\/li><li><a href=\"#Check_a_request_beyond_the_file_boundary\"><span class=\"toc_number toc_depth_2\">3.3.<\/span> Check a request beyond the file boundary<\/a><\/li><\/ul><\/li><li><a href=\"#Isolate_problems_in_the_proxy_CDN_and_application_layers\"><span class=\"toc_number toc_depth_1\">4.<\/span> Isolate problems in the proxy, CDN and application layers<\/a><ul><li><a href=\"#Request_the_same_portion_from_the_origin_and_public_endpoint\"><span class=\"toc_number toc_depth_2\">4.1.<\/span> Request the same portion from the origin and public endpoint<\/a><\/li><li><a href=\"#Narrow_down_header_cache_and_compression_differences\"><span class=\"toc_number toc_depth_2\">4.2.<\/span> Narrow down header, cache and compression differences<\/a><\/li><li><a href=\"#Check_PHP_delivery_and_expiring_links_separately\"><span class=\"toc_number toc_depth_2\">4.3.<\/span> Check PHP delivery and expiring links separately<\/a><\/li><\/ul><\/li><li><a href=\"#Frequently_Asked_Questions\"><span class=\"toc_number toc_depth_1\">5.<\/span> Frequently Asked Questions<\/a><ul><li><a href=\"#Do_resumable_downloads_require_HTTP3\"><span class=\"toc_number toc_depth_2\">5.1.<\/span> Do resumable downloads require HTTP\/3?<\/a><\/li><li><a href=\"#Will_every_browser_resume_if_the_server_supports_it\"><span class=\"toc_number toc_depth_2\">5.2.<\/span> Will every browser resume if the server supports it?<\/a><\/li><li><a href=\"#Are_parallel_downloads_always_faster\"><span class=\"toc_number toc_depth_2\">5.3.<\/span> Are parallel downloads always faster?<\/a><\/li><li><a href=\"#Can_a_Range_request_bypass_access_controls_for_paid_files\"><span class=\"toc_number toc_depth_2\">5.4.<\/span> Can a Range request bypass access controls for paid files?<\/a><\/li><\/ul><\/li><li><a href=\"#Final_checklist_before_going_live\"><span class=\"toc_number toc_depth_1\">6.<\/span> Final checklist before going live<\/a><\/li><\/ul><\/div>\n<h2><span id=\"Why_downloads_restart_and_how_HTTP_Range_helps\">Why downloads restart and how HTTP Range helps<\/span><\/h2>\n<p>When a ZIP download is interrupted, the portion already downloaded may still be on your computer. The download can still start over if your downloading application does not request the remaining portion, or if the server responds to that request with the entire file. To distinguish these causes, you need to inspect the request and response, not just the progress bar.<\/p>\n<p><strong>HTTP Range resumable downloads<\/strong> work by requesting a specific range of bytes instead of the entire file. A byte is a unit of data used to measure file size. The client-your browser or download program-can request the bytes that follow the portion it already has. To resume safely, the client must preserve that local portion, and the server must serve the same file version and handle the range request correctly.<\/p>\n<h3><span id=\"What_do_206_200_and_416_responses_tell_you\">What do 206, 200 and 416 responses tell you?<\/span><\/h3>\n<p>HTTP headers carry information about a request or response. The <code>Range<\/code> request header specifies the portion you want to retrieve. For example, <code>Range: bytes=0-1023<\/code> requests the first 1024 bytes of a file. Positions start at zero, and both ends of the range are included. If the server fulfills this single-range request, it returns <code>206 Partial Content<\/code>, indicating a partial response.<\/p>\n<p>The <code>Content-Range<\/code> response header identifies the returned portion&#8217;s position within the file and the file&#8217;s total size. In the hypothetical example used below, requesting the first 1024 bytes of an 8388608-byte file should return <code>Content-Range: bytes 0-1023\/8388608<\/code>. The response body-the file data sent after the headers-should be 1024 bytes in this case. <strong>Check the 206 status, range information and received data size together.<\/strong> Together, these checks show whether the response covers the requested range, rather than relying solely on the server&#8217;s advertised support.<\/p>\n<p><code>200 OK<\/code> indicates that the request succeeded but the response is not partial. For a file request, this usually means the entire file is being sent. The server may have ignored the Range header. If the client appends this response to the existing portion, it can corrupt the file. It must treat the incoming data as a complete file, not as the remaining portion.<\/p>\n<p><code>416 Range Not Satisfiable<\/code> indicates that the requested range cannot be fulfilled. For example, this response is expected when the starting position is beyond the file&#8217;s last byte. For the example file, the server can report the current total size with <code>Content-Range: bytes *\/8388608<\/code>. If you receive this response, compare the reported size with the size of your local portion. The file may have changed, or your local copy may already be complete; matching sizes alone do not confirm matching contents. This response alone does not mean Range support is broken. Understanding <a href=\"https:\/\/www.dchost.com\/blog\/en\/what-http-status-codes-mean-for-seo-and-hosting\/\">HTTP status code meanings<\/a> can also help you interpret other responses you encounter.<\/p>\n<h3><span id=\"Why_isn8217t_Accept-Ranges_enough_on_its_own\">Why isn&#8217;t Accept-Ranges enough on its own?<\/span><\/h3>\n<p><code>Accept-Ranges: bytes<\/code> is a header advertising the server&#8217;s support for byte ranges; it does not prove that you can successfully resume a download. Its absence does not necessarily mean that a range request will be rejected, either. To test support, request a specific range and inspect what the server returns. Next, you will do this with a single example file, checking the response headers and downloaded data size together before attempting to resume.<\/p>\n<h2><span id=\"Set_up_the_example_environment_and_verify_a_partial_response\">Set up the example environment and verify a partial response<\/span><\/h2>\n<p>First, check whether Nginx returns only the requested portion of a file, without other services in the request path. Once this works, you can try resuming a download. Testing locally lets you examine the file server&#8217;s behavior separately from application or caching effects.<\/p>\n<figure class=\"aiw-figure\" data-aiw-visual-id=\"13352\" data-aiw-visual-key=\"2b3e4287ff17e9b70786689cc2fa33318470fa5f4028bb593b6a46cf0ba4d874\"><img decoding=\"async\" src=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-a2ea09e5f8ab5be76334.png\" alt=\"Verification flow from requesting the first 1024 bytes to checking the 206 status, Content-Range and response body size.\" width=\"840\" height=\"948\" loading=\"lazy\" class=\"aiw-article-visual wp-image-13352\" srcset=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-a2ea09e5f8ab5be76334.png 840w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-a2ea09e5f8ab5be76334-266x300.png 266w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-a2ea09e5f8ab5be76334-768x867.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption>Workflow diagram: Check the status code, Content-Range and body size together to determine whether the server returned only the requested portion of the example file.<\/figcaption><\/figure>\n<div class=\"aiw-note aiw-note-example\">\n<p class=\"aiw-box-title\">Example scenario<\/p>\n<p>In this hypothetical setup, you have an empty Ubuntu 24.04 test server and a user with sudo permission, which allows you to run commands with administrator privileges. Run every command in this server&#8217;s terminal. Once configured, Nginx will be accessible only at <code>127.0.0.1:8080<\/code>, meaning from the server itself. Your test file is not a real ZIP archive: it contains 8388608 bytes of randomly generated data.<\/p>\n<\/div>\n<h3><span id=\"Install_Nginx_and_create_the_test_file\">Install Nginx and create the test file<\/span><\/h3>\n<p>The Nginx web server will serve the file, while the <code>curl<\/code> command-line tool will send requests and save responses. Package installation may temporarily start the default site on port 80. Before installing, make sure inbound web traffic is blocked at the network firewall, but leave SSH access open so you can keep using your terminal connection.<\/p>\n<p>The following commands install and stop Nginx, back up its initial configuration and disable the default site. They then create the test file and a working directory for saving responses. Run them only in the empty test environment, not on a server hosting existing websites. Run the backup command only on your first pass through these steps; do not repeat it if its destination already exists.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo apt update\nsudo apt install -y nginx curl\nsudo systemctl stop nginx\nsudo cp -a \/etc\/nginx \/etc\/nginx.range-demo.bak\nsudo rm -f \/etc\/nginx\/sites-enabled\/default\n\nsudo install -d -m 755 \/srv\/range-demo\nsudo dd if=\/dev\/urandom of=\/srv\/range-demo\/paket-v1.bin bs=1048576 count=8 status=progress\nsudo chmod 644 \/srv\/range-demo\/paket-v1.bin\nmkdir -p \/tmp\/range-demo\ncd \/tmp\/range-demo<\/code><\/pre>\n<p>The backup at <code>\/etc\/nginx.range-demo.bak<\/code> preserves the configuration from before your changes. Keep it for the rollback steps at the end of this section, which restore those initial settings. On a fresh installation, disabling the default site removes the listener on external addresses. The file creation command should report 8388608 bytes written, and the read permission allows Nginx to serve that test data.<\/p>\n<h3><span id=\"Configure_the_local_file_server\">Configure the local file server<\/span><\/h3>\n<p>In the same terminal, run the following command to create <code>\/etc\/nginx\/conf.d\/range-demo.conf<\/code>. The <code>root<\/code> directive tells Nginx which directory contains the requested file; here, it points to the test directory you just created. Nginx can handle Range requests for static files stored on disk, so you do not need to add an <code>Accept-Ranges<\/code> header manually. Run the configuration test and start command before the final reload command, and continue only if they succeed.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo tee \/etc\/nginx\/conf.d\/range-demo.conf &gt; \/dev\/null &lt;&lt;&#039;EOF&#039;\nserver {\n    listen 127.0.0.1:8080;\n    server_name localhost;\n    root \/srv\/range-demo;\n    gzip off;\n\n    location \/ {\n        try_files $uri =404;\n    }\n}\nEOF\nsudo nginx -t &amp;&amp; sudo systemctl start nginx\nsudo systemctl reload nginx<\/code><\/pre>\n<p>The <code>nginx -t<\/code> output should report that the configuration test succeeded. If it reports an error, stop before the reload command. Correct the file and line identified in the output, then repeat the test and start command. After Nginx starts successfully, it will handle file requests at the local address. Compression is disabled here so you can inspect the file&#8217;s bytes directly during the next check.<\/p>\n<h3><span id=\"Verify_the_first_1024_bytes_with_a_GET_request\">Verify the first 1024 bytes with a GET request<\/span><\/h3>\n<p>Run the following commands in the same terminal to request only the first 1024 bytes. Curl uses GET by default to retrieve file content; do not use the <code>-I<\/code> option, which requests only headers. The commands save the response headers and body-the returned file data-to separate files. The final two commands print the headers and show the saved body&#8217;s byte count.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cd \/tmp\/range-demo\ncurl --silent --show-error --noproxy &#039;*&#039; \\\n  -H &#039;Range: bytes=0-1023&#039; \\\n  -D ilk-basliklar.txt \\\n  -o ilk-1024.bin \\\n  http:\/\/127.0.0.1:8080\/paket-v1.bin\ncat ilk-basliklar.txt\nwc -c ilk-1024.bin<\/code><\/pre>\n<p><code>--noproxy '*'<\/code> bypasses any proxy, an intermediary that forwards requests, configured in your terminal environment. This keeps the request local. For this hypothetical example, expect the following status code, header values and saved file size. Your response may include other headers:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">HTTP\/1.1 206 Partial Content\nContent-Length: 1024\nContent-Range: bytes 0-1023\/8388608\n\n1024 ilk-1024.bin<\/code><\/pre>\n<p>If you see 404, first run <code>ls -l \/srv\/range-demo\/paket-v1.bin<\/code> to check that the file exists in the expected location. If it is missing, repeat the file creation step. If it exists, compare the requested filename and the directory in the server configuration with the example values.<\/p>\n<p>If you see 200, inspect the <code>sudo nginx -T<\/code> output, which shows the configuration Nginx reads. Confirm that the listener on port 8080 belongs to the example server block, and look for a setting such as <code>max_ranges 0;<\/code> that disables range handling. Correct only the cause you identify. If you change the configuration, run the configuration test before reloading Nginx. Then repeat the same GET request: the status code, returned range and saved body size should all match the expected values above.<\/p>\n<p><strong>Do not run the cleanup commands yet.<\/strong> Keep the environment in place for the next resume test. After you finish all the tests, use the following commands to remove the example server configuration and restore the initial settings from your backup. Restoring those settings also re-enables the default site, so keep the firewall restriction on inbound web traffic in place. The final command reloads Nginx only if the restored configuration passes its syntax test:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo rm \/etc\/nginx\/conf.d\/range-demo.conf\nsudo cp -a \/etc\/nginx.range-demo.bak\/. \/etc\/nginx\/\nsudo nginx -t &amp;&amp; sudo systemctl reload nginx<\/code><\/pre>\n<h2><span id=\"Download_the_remaining_bytes_and_check_file_integrity\">Download the remaining bytes and check file integrity<\/span><\/h2>\n<h3><span id=\"Save_the_first_portion_and_resume_into_the_same_file\">Save the first portion and resume into the same file<\/span><\/h3>\n<p>Keep the example Nginx configuration running for these checks. In the same test server&#8217;s terminal, retrieve the first 1048576 bytes to represent an interrupted download. The commands below save response headers and overwrite the example download file <code>indirme.bin<\/code>; they do not change the source file. You will resume from this larger portion rather than the 1024-byte file saved in the previous check.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cd \/tmp\/range-demo\ncurl --silent --show-error --noproxy &#039;*&#039; \\\n  -H &#039;Range: bytes=0-1048575&#039; \\\n  -D parca-basliklar.txt -o indirme.bin \\\n  http:\/\/127.0.0.1:8080\/paket-v1.bin\ncat parca-basliklar.txt\nwc -c indirme.bin<\/code><\/pre>\n<p>Before continuing, look for <code>206<\/code> and <code>Content-Range: bytes 0-1048575\/8388608<\/code> in the headers, and 1048576 bytes in the size output. If these values do not match, return to the partial-response check before attempting to resume.<\/p>\n<p>The response&#8217;s <code>ETag<\/code> is a validator-a value the server provides to distinguish file versions. Sending it in the <code>If-Range<\/code> header asks the server to return the remaining portion only if the validator still matches. Extract the value from your saved headers and print it with:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">etag=$(awk &#039;tolower($1)==&quot;etag:&quot; {sub(\/\\r$\/, &quot;&quot;); print $2; exit}&#039; parca-basliklar.txt)\nprintf &#039;%s\\n&#039; &quot;$etag&quot;<\/code><\/pre>\n<p>In this hypothetical Nginx setup, expect a value enclosed in double quotes; preserve those quotes. If the value is empty or begins with <code>W\/<\/code>, which marks a weak validator that does not guarantee byte-for-byte equality, do not use it for the If-Range test. Once you have a suitable value, run the following commands. The first saves a copy of the initial portion so you can restore it if the resume attempt fails; curl then requests the remaining bytes:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cp indirme.bin ilk-parca.bin\ncurl --silent --show-error --noproxy &#039;*&#039; \\\n  -C - -H &quot;If-Range: $etag&quot; \\\n  -D devam-basliklar.txt -o indirme.bin \\\n  http:\/\/127.0.0.1:8080\/paket-v1.bin\ncat devam-basliklar.txt\nwc -c indirme.bin\nsha256sum \/srv\/range-demo\/paket-v1.bin indirme.bin<\/code><\/pre>\n<p><code>-C -<\/code> tells curl to calculate the resume position from the local file&#8217;s size. Expect <code>206<\/code> with <code>Content-Range: bytes 1048576-8388607\/8388608<\/code>, and a completed file of 8388608 bytes. These values show that the server returned the portion following your initial download.<\/p>\n<p>The final command checks file integrity: whether the completed download matches the source. A SHA-256 checksum is a value calculated from a file&#8217;s contents. The checksums at the beginning of the two output lines should match. If they differ, do not use the downloaded file. Restore the initial portion with <code>cp ilk-parca.bin indirme.bin<\/code> before retrying, and investigate the saved headers, file size and any curl error first.<\/p>\n<h3><span id=\"Test_a_version_mismatch_safely\">Test a version mismatch safely<\/span><\/h3>\n<p>When the validator no longer matches, a server handling If-Range ignores the requested range and returns the complete file with <code>200<\/code>. You can check this behavior without modifying the source file. In the same terminal, deliberately send a different ETag:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">curl --silent --show-error --noproxy &#039;*&#039; \\\n  -H &#039;Range: bytes=1048576-&#039; \\\n  -H &#039;If-Range: &quot;eslesmeyen-deneme&quot;&#039; \\\n  -D uyusmazlik-basliklar.txt -o \/dev\/null \\\n  http:\/\/127.0.0.1:8080\/paket-v1.bin\ncat uyusmazlik-basliklar.txt<\/code><\/pre>\n<p>Expect <code>200<\/code> here. The command discards the response body rather than appending it to your existing portion. If you receive this response during an actual resume attempt, do not combine the old portion with the incoming full file. Download the complete file from the beginning into a separate file.<\/p>\n<h3><span id=\"Check_a_request_beyond_the_file_boundary\">Check a request beyond the file boundary<\/span><\/h3>\n<p>The last byte is at position 8388607, so a range starting at position 8388608 falls outside the file. Run these commands in the same terminal to request that range and display the response headers:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">curl --silent --show-error --noproxy &#039;*&#039; \\\n  -H &#039;Range: bytes=8388608-&#039; \\\n  -D sinir-basliklar.txt -o \/dev\/null \\\n  http:\/\/127.0.0.1:8080\/paket-v1.bin\ncat sinir-basliklar.txt<\/code><\/pre>\n<p>Expect <code>416<\/code> and <code>Content-Range: bytes *\/8388608<\/code>. Together, they show that the server rejected a starting position outside the file and reported its current total size; this is not broken resume support. If your results match, you have checked both successful resumption and the server&#8217;s response to an invalid starting position.<\/p>\n<h2><span id=\"Isolate_problems_in_the_proxy_CDN_and_application_layers\">Isolate problems in the proxy, CDN and application layers<\/span><\/h2>\n<h3><span id=\"Request_the_same_portion_from_the_origin_and_public_endpoint\">Request the same portion from the origin and public endpoint<\/span><\/h3>\n<p>If the local test succeeds, you have confirmed that Nginx can resume downloads of the example file-not that your public download path works. The origin server is the server that supplies the file. A proxy is an intermediary server that forwards requests, while a CDN is a content delivery network that serves content from different locations. Comparing the origin&#8217;s response with the public endpoint-the download address your users access-helps you identify where range handling differs.<\/p>\n<figure class=\"aiw-figure\" data-aiw-visual-id=\"13354\" data-aiw-visual-key=\"f8c96a1347351026229511cce94d5c19f0f513666e55e11e747905f946bf7102\"><img decoding=\"async\" src=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-6118473120087cee22da.png\" alt=\"Diagnostic flow from comparing origin and public responses to isolating the cause, applying a targeted fix and checking again.\" width=\"840\" height=\"1026\" loading=\"lazy\" class=\"aiw-article-visual wp-image-13354\" srcset=\"https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-6118473120087cee22da.png 840w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-6118473120087cee22da-246x300.png 246w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-6118473120087cee22da-838x1024.png 838w, https:\/\/www.dchost.com\/blog\/wp-content\/uploads\/2026\/11\/dchost-flow-en-6118473120087cee22da-768x938.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption>Workflow diagram: Comparing the same request at the origin server and public endpoint helps narrow down where partial-response behavior changes.<\/figcaption><\/figure>\n<p>The example environment has no CDN; <code>127.0.0.1:8080<\/code> is not publicly accessible. For the following comparison, use an existing HTTPS download address you manage. You need permission to access the origin directly, and it must present a valid certificate for the domain. Choose a test file of at least 1024 bytes, with the same version available through both endpoints.<\/p>\n<p>Run the commands below in the test server&#8217;s Bash terminal. The three prompts ask, in order, for the full HTTPS download address, its domain name alone (without the scheme or path), and the origin server&#8217;s IP address. The commands save each response&#8217;s headers and file data separately, then display the headers and byte counts:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cd \/tmp\/range-demo\nread -r -p &#039;HTTPS indirme adresi: &#039; url\nread -r -p &#039;Adresteki alan ad\u0131: &#039; alan\nread -r -p &#039;Kaynak sunucunun IP adresi: &#039; kaynak_ip\ncurl --silent --show-error --noproxy &#039;*&#039; \\\n  -H &#039;Range: bytes=0-1023&#039; -H &#039;Accept-Encoding: identity&#039; \\\n  -D dis-basliklar.txt -o dis-parca.bin &quot;$url&quot;\ncurl --silent --show-error --noproxy &#039;*&#039; \\\n  --resolve &quot;${alan}:443:${kaynak_ip}&quot; \\\n  -H &#039;Range: bytes=0-1023&#039; -H &#039;Accept-Encoding: identity&#039; \\\n  -D kaynak-basliklar.txt -o kaynak-parca.bin &quot;$url&quot;\ncat dis-basliklar.txt kaynak-basliklar.txt\nwc -c dis-parca.bin kaynak-parca.bin<\/code><\/pre>\n<p>The first request goes to the public address. In the second, <code>--resolve<\/code> directs the connection to the origin IP while preserving the domain name and TLS certificate verification-the check that confirms the server&#8217;s identity over a secure connection. The commands assume the standard HTTPS port 443 and request uncompressed content. For the same file version, expect <code>206<\/code>, an identical <code>Content-Range<\/code> and a 1024-byte body from both endpoints.<\/p>\n<p>These commands do not follow redirects automatically or use your browser session. If you receive a redirect, inspect its destination in the response&#8217;s Location header before testing the destination with appropriate authorization. Do not mistake a login page for the file. If the origin is unreachable, run the comparison from a network permitted to access it rather than opening the firewall to public traffic.<\/p>\n<h3><span id=\"Narrow_down_header_cache_and_compression_differences\">Narrow down header, cache and compression differences<\/span><\/h3>\n<p>If the origin returns <code>206<\/code> but the public endpoint returns <code>200<\/code>, first confirm that both responses contain the file you expect. Then check whether the request forwarded to the origin includes <code>Range<\/code> and, during resumption, <code>If-Range<\/code>. In the Nginx proxy server&#8217;s terminal, inspect the <code>sudo nginx -T<\/code> output for <code>proxy_set_header<\/code> lines that clear these headers or a <code>proxy_pass_request_headers off<\/code> setting.<\/p>\n<p>If you find a setting that prevents forwarding, back up the configuration file containing it. Remove the header-clearing override or restore request-header forwarding only for the affected download path. If <code>nginx -t<\/code> succeeds, reload the configuration and repeat the origin and public endpoint requests. You should now receive a partial response from the public endpoint too, with the correct range and body size. If the change does not help or causes another problem, restore the backup file, test the configuration and reload it.<\/p>\n<p>If the first request and a subsequent request produce different responses, check the CDN panel for evidence that the test file is being served from cache-a stored copy used to answer later requests. Clear the cache for this file only, then repeat the same request twice. If behavior changes once the cache is populated, inspect the settings for range requests and partial responses on the affected download path. Record their current values before making a targeted change, then repeat the requests to check its effect.<\/p>\n<p><code>Content-Encoding<\/code> identifies compression applied to the response. If this header differs between the origin and public responses, you may be comparing compressed and uncompressed versions of the same file. Record the relevant compression or content-transformation settings, then disable the identified transformation only on the download path and repeat the comparison. Do not disable compression across the entire site. If the difference remains, restore the settings you recorded.<\/p>\n<h3><span id=\"Check_PHP_delivery_and_expiring_links_separately\">Check PHP delivery and expiring links separately<\/span><\/h3>\n<p>If a static file download resumes but a download through PHP restarts, send the same range request to the application using valid access permissions. Compare the response status, Content-Range and body size with the static file response. Using <code>readfile()<\/code> alone in PHP does not provide range support.<\/p>\n<p>One option is to keep authorization-the check that you are allowed to download the file-in the application while letting Nginx transfer the file. This combines an <code>internal<\/code> location, which blocks direct external access, with the <code>X-Accel-Redirect<\/code> header, which tells Nginx the path to serve. Moving a paid file into a publicly accessible directory would bypass that protection; it is not a substitute for this approach.<\/p>\n<p>If a resume request receives <code>403<\/code> or a redirect to a login page, first check the link&#8217;s expiration time and whether your login session is still valid. Investigate access permissions before changing range handling: neither response, by itself, proves that the link has expired.<\/p>\n<p>After obtaining a new authorized link, compare the file version and ETag validator with those in the response for the initial portion. Renewing the link restores access, but does not establish that the file is unchanged or that you can safely append data to the old portion.<\/p>\n<h2><span id=\"Frequently_Asked_Questions\">Frequently Asked Questions<\/span><\/h2>\n<h3><span id=\"Do_resumable_downloads_require_HTTP3\">Do resumable downloads require HTTP\/3?<\/span><\/h3>\n<p>No. HTTP Range resumable downloads also work over HTTP\/1.1 and HTTP\/2. Switching to HTTP\/3 will not fix missing range support in your application. <a href=\"https:\/\/www.dchost.com\/blog\/en\/http-2-http-3-seo-core-web-vitals-hosting\/\">HTTP\/2 and HTTP\/3 performance<\/a> can vary with connection conditions, but resuming a download still requires your download program to request the remaining bytes and the server to return that range correctly.<\/p>\n<h3><span id=\"Will_every_browser_resume_if_the_server_supports_it\">Will every browser resume if the server supports it?<\/span><\/h3>\n<p>No. Your browser&#8217;s ability to resume depends on its version, whether it still has the download record and partially downloaded file, and whether you still have access to the file. Clicking the link again may start a new download rather than resume the existing one. First try the <strong>Resume<\/strong> option in your browser&#8217;s downloads list; it may not be available for every type of interruption. Safe resumption also requires the server to keep serving the same file version.<\/p>\n<h3><span id=\"Are_parallel_downloads_always_faster\">Are parallel downloads always faster?<\/span><\/h3>\n<p>No. Parallel downloading retrieves different ranges of a file through simultaneous requests. It may help when speed is limited per connection, but may offer no improvement if all available bandwidth is already in use. Too many simultaneous requests can also exceed the server&#8217;s request limits. In your download manager, try downloading the same file as a single part first, then with a small number of simultaneous parts. Compare transfer speeds and any errors before increasing the number of parts.<\/p>\n<h3><span id=\"Can_a_Range_request_bypass_access_controls_for_paid_files\">Can a Range request bypass access controls for paid files?<\/span><\/h3>\n<p>Range does not grant access to a file; it only specifies which bytes are requested. In a correctly configured system, every request for a portion of the file also passes through access checks. In systems such as WooCommerce, keep downloads routed through the application mechanism that checks whether the buyer is entitled to download the file. If a direct file address or shared cache bypasses this check, unauthorized access may become possible. That is an access-control failure, not a consequence of Range support.<\/p>\n<h2><span id=\"Final_checklist_before_going_live\">Final checklist before going live<\/span><\/h2>\n<ul>\n<li><strong>Partial response:<\/strong> Request the first 1024 bytes and check that you receive a <code>206<\/code> response, a <code>Content-Range<\/code> identifying those bytes and a response body of exactly 1024 bytes.<\/li>\n<li><strong>File integrity:<\/strong> Compare the <code>sha256sum<\/code> values of the completed, resumed download in <code>indirme.bin<\/code> and the source file. Both values must match before you use the downloaded file.<\/li>\n<li><strong>Changed file:<\/strong> Send a mismatched <code>If-Range<\/code> value and check that the server returns the entire file with a <code>200<\/code> response. Make sure the incoming data is not appended to the old partial file.<\/li>\n<li><strong>Public endpoint:<\/strong> Compare the status code, range and body size returned by the public download address with those returned by the origin server. Both requests must target the same file version.<\/li>\n<li><strong>Authorization:<\/strong> Check that you can resume with valid access permissions. Then confirm that a request using an unauthorized or expired link cannot retrieve file data.<\/li>\n<\/ul>\n<p>After these checks, deliberately stop and resume a test file download through the actual download address your users access. Use a file you are authorized to download, then compare the completed file&#8217;s checksum with the source file&#8217;s checksum. Matching checksums confirm that this resumed download produced the correct file through the public download path, not just in the local example.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Downloads that restart may point to missing range handling, changed files or expired access. Use an isolated Nginx example to verify partial responses and file integrity, then compare the origin server with your public download endpoint.<\/p>\n","protected":false},"author":4,"featured_media":13345,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[184],"tags":[236,704,708,503,707,612],"class_list":["post-13356","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technology","tag-cdn","tag-http-range","tag-http-status-codes","tag-nginx","tag-resumable-downloads","tag-troubleshooting"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/13356","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=13356"}],"version-history":[{"count":1,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/13356\/revisions"}],"predecessor-version":[{"id":13358,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/13356\/revisions\/13358"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/13345"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=13356"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=13356"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=13356"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}