A download job can begin at an HTTPS URL and still be redirected somewhere its owner did not intend. The URL in the script tells only part of the story when curl --location follows a server response. The redirect target is another input, and it deserves an explicit protocol policy.
That review is worth doing before updating a transfer dependency. curl 8.22.0 was released on September 2, 2026, and a version update is a useful time to turn an assumed redirect policy into a tested one. The control is --proto-redir: it limits the schemes curl will accept after a redirect. For a job that is meant to retrieve web content, an explicit HTTP-and-HTTPS policy prevents curl from following an FTP or FTPS redirect just because those schemes are in curl’s default redirect allowlist.
This guide uses a disposable local redirect fixture and curl 8.14.1 on Linux. The successful result is a web redirect that completes under the policy and an FTP redirect that curl rejects before it tries the FTP target. The test is intentionally local. It does not download from a production endpoint or expose a real service.
Treat redirect destinations as part of the transfer contract
--location tells curl to act on HTTP redirect responses such as 301, 302, 307, and 308. That is often required for release assets, package mirrors, and object-storage download links. A content host may redirect to a CDN, or an artifact URL may move after a release process changes storage providers.
Following redirects does not mean accepting every scheme curl supports. curl’s documented default redirect set includes HTTP, HTTPS, FTP, and FTPS. That is broader than many automation jobs need. A release fetcher that expects an HTTPS landing page and an HTTPS artifact has no operational reason to follow an FTP URL supplied in a Location header.
Make the intended boundary visible next to --location:
curl --fail --location \
--proto-redir '=http,https' \
--remote-name \
'https://downloads.example.net/releases/tool.tar.gz'
The leading = resets the redirect protocol selection to exactly the listed schemes. Without it, a protocol expression can add to or subtract from curl’s existing selection. In this command, curl may follow an HTTP or HTTPS redirect, but it must refuse any other redirect scheme.
--proto-redir governs redirect targets. It is not a replacement for checking the initial URL. Keep the initial URL explicit and HTTPS when the job needs authenticated transport. If a script accepts a URL from configuration or a command-line argument, use --proto '=https' as a separate initial-URL restriction when HTTPS is the only acceptable starting scheme:
curl --fail --location \
--proto '=https' \
--proto-redir '=http,https' \
--output tool.tar.gz \
"$artifact_url"
The two options answer different questions. --proto limits schemes curl can use generally, including the supplied URL. --proto-redir states the narrower redirect policy in a form a reviewer can recognize immediately. A job that genuinely needs only HTTPS at every step can use --proto-redir '=https'; do not copy that policy into a workflow that intentionally redirects from HTTPS to a documented HTTP endpoint.
Build a disposable redirect fixture
Use a local fixture rather than experimenting with a vendor’s redirect chain. The fixture needs one route that redirects to another HTTP route and another route that names an FTP destination. Substitute an unused local test address if your system already uses this example address.
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/to-http":
self.send_response(302)
self.send_header("Location", "/ok")
self.end_headers()
elif self.path == "/to-ftp":
self.send_response(302)
self.send_header("Location", "ftp://<test-host>/fixture.txt")
self.end_headers()
elif self.path == "/ok":
body = b"redirect policy fixture\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass
HTTPServer(("<fixture-bind-address>", <fixture-port>), Handler).serve_forever()
Save the file outside an application checkout and start it in one terminal:
python3 redirect_fixture.py
The fixture is not an FTP service. Its FTP location is deliberately unreachable. That keeps the negative test bounded: the test should prove curl’s protocol decision, not transfer a file using a second protocol. In a CI test, start the fixture as a background process, record its process identifier, and stop it in a cleanup handler.
Verify that an allowed web redirect still works
In another terminal, follow the route that redirects to /ok while setting the policy:
curl --silent --show-error --fail --location \
--noproxy '*' \
--proto-redir '=http,https' \
'http://<fixture-url>/to-http'
--noproxy '*' is useful only for this local test. It prevents an environment proxy setting from changing the fixture’s behavior. Do not add it to an ordinary external download command unless bypassing the configured proxy is intended and approved.
The validation run printed the fixture body and exited zero:
redirect policy fixture
This positive check matters. A policy can be strict enough to break an expected redirect just as easily as it can be too broad. A passing direct request is not sufficient; the request must traverse a redirect and still reach the expected response.
If this check fails, first inspect the response headers with --verbose or --dump-header <file> in the disposable test. Look for an unexpected scheme, a malformed relative location, or a proxy that is rewriting requests. Do not weaken the policy before identifying which hop needs to be allowed. If the production endpoint redirects to a different HTTPS hostname, hostname changes do not require adding another protocol; they are a separate TLS and trust decision.
Capture the denied redirect case
Next, request the route that returns the FTP location. The policy should stop curl at the redirect boundary:
curl --silent --show-error --location \
--noproxy '*' \
--proto-redir '=http,https' \
'http://<fixture-url>/to-ftp'
printf 'curl-exit=%s\n' "$?"
In the Linux validation, curl 8.14.1 returned exit status 1 and reported that FTP was disabled in a redirect. The important observation is where it stopped: curl rejected the Location scheme instead of attempting an FTP connection. That makes this a reliable policy test even when the FTP address is deliberately unreachable.
For comparison, the same fixture without --proto-redir proceeded far enough to attempt the FTP destination and then failed because no FTP server was listening. That is not a desirable success path. It demonstrates why a web-only job should set the option rather than relying on curl’s default redirect behavior.
Keep the negative assertion in automated tests. A shell test can require a nonzero exit status and search its captured stderr for disabled (in redirect). Avoid making an exact error string the only assertion across every platform or future curl release; error wording can change. The durable expectation is that the command does not fetch a non-web redirect target.
Apply the policy to real artifact jobs
Add the option where a script crosses a trust boundary: release downloads, bootstrap installers, scheduled feeds, and configuration retrieval jobs. Preserve existing integrity checks after the transfer. Restricting redirect protocols limits where curl can go; it does not prove that the final bytes are the expected artifact.
A typical artifact sequence keeps the controls separate:
curl --fail --location \
--proto '=https' \
--proto-redir '=http,https' \
--output tool.tar.gz \
"$artifact_url"
sha256sum -c tool.tar.gz.sha256
Use a checksum or signature supplied through a trusted release process. Do not fetch both an artifact and its expected digest from an unauthenticated redirect chain and describe that as independent verification. For long-lived automation, record the curl version in build logs and run the local redirect test when the client image changes.
curl updates can include transport, protocol, and security changes, but a version number alone does not define the redirect policy your job needs. --proto-redir '=http,https' makes a web-only expectation explicit. The local fixture proves both sides of the rule: an expected HTTP redirect works, while an FTP redirect is rejected before curl leaves the intended protocol boundary.