spyderproxy

cURL Follow Redirects: -L Flag Explained (2026)

D

Daniel K.

|
Published date

Sun May 10 2026

Quick verdict: cURL stops at the first 3xx response by default. Add -L (or --location) to follow redirects. Three gotchas: (1) POST changes to GET on 301/302/303 redirects unless you use --post301 / --post302 / --post303; (2) Authorization headers strip on cross-host redirects unless you set --location-trusted (do NOT do this on untrusted destinations); (3) the default redirect cap is 50, control with --max-redirs N.

The Default: No Follow

$ curl https://bit.ly/some-link
<html>
<head><title>301 Moved Permanently</title></head>
<body><a href="https://final-destination.com">moved</a></body>
</html>

cURL got the 301 response and stopped — printing the body the server returned (often a tiny HTML page) and exiting with status 0.

Add -L to Follow

$ curl -L https://bit.ly/some-link
# now gets the real page from final-destination.com

cURL follows up to 50 redirects by default. With -v you can see each hop:

curl -v -L https://bit.ly/some-link 2>&1 | grep -E "> (GET|Host)|< HTTP|< Location"

That shows each request line, response code, and Location header in the chain.

Cap the Redirect Count

curl -L --max-redirs 5 https://example.com/r

Bail out if the chain exceeds 5 redirects (defends against malicious redirect loops). Default is 50. Set --max-redirs 0 to disable following entirely (same as omitting -L).

Setting --max-redirs -1 means unlimited — do not use this on untrusted URLs.

POST Redirects: The 301/302/303 Method Switch

Per RFC 7231, when a server returns 301, 302, or 303 to a POST, the redirected request should be a GET. cURL respects this. So:

# Submit a form, server 302s to /thanks
curl -L -X POST https://example.com/submit -d "name=alice"

# cURL POSTs to /submit, gets 302, then GETs /thanks (body discarded)

If you actually want the POST to retry as POST after the redirect:

curl -L --post301 -X POST https://example.com/submit -d "name=alice"
# or --post302, --post303 depending on the response code

For 307 and 308, cURL preserves the method automatically — that is the whole point of those status codes (RFC 7538).

Auth Headers Across Hosts

By default, cURL strips the Authorization header on redirects to a different host:

$ curl -L -H "Authorization: Bearer SECRET" https://api.example.com/data
# If /data 302s to https://attacker.com, cURL does NOT forward the Bearer header.

This is a security feature. Without it, a malicious redirect could exfiltrate your token to a third-party host.

To explicitly trust the redirect chain (use sparingly):

curl -L --location-trusted -H "Authorization: Bearer SECRET" https://api.example.com/data

Only use --location-trusted when you trust EVERY URL in the chain. For commercial APIs that legitimately redirect across subdomains, use --location-trusted — but never on user-supplied URLs.

See the Full Redirect Chain

-w with %{redirect_url} shows where cURL would go next without actually following:

curl -s -o /dev/null -w "Redirect: %{redirect_url}\nStatus: %{http_code}\n" https://bit.ly/link

To see EVERY hop's URL with -L:

curl -sIL https://bit.ly/link | grep -i "^Location"

-I uses HEAD requests (so cURL does not download bodies), -L follows, and the Location headers print as the chain unfolds.

Redirects + SSL

If a redirect goes HTTP → HTTPS or vice-versa, cURL handles it transparently. But if either hop fails SSL verification, the whole chain fails. The fix is the same as for any SSL issue: --cacert for self-signed targets, NOT -k (see why -k is dangerous).

cURL follows cookies across redirects when you pass -b and -c:

curl -L -b cookies.txt -c cookies.txt https://example.com/login-flow

The server sets a session cookie on /login, redirects to /dashboard, cURL forwards the cookie. Without the cookie jar, the destination would not know you logged in.

Redirects Through a Proxy

Proxy is set once and used for every hop:

curl -L --proxy http://USER:[email protected]:8000 https://bit.ly/short

Every hop in the redirect chain routes through the proxy. Useful when the final destination is geo-locked or rate-limited.

Common Errors

  • "curl: (47) Maximum (50) redirects followed" — you hit the max-redirs cap. Either the URL has a redirect loop, or genuinely needs more hops. Bump with --max-redirs 100 only if you trust the chain.
  • "curl: (52) Empty reply from server" — the destination accepted the connection then closed without responding. Often Cloudflare blocking your IP.
  • Token disappears after redirect — cross-host strip behavior. Use --location-trusted if appropriate, OR have the API team fix the redirect to stay on the same host.
  • POST becomes GET unexpectedly — 301/302/303 method-switch behavior. Use --post301/--post302/--post303, or have the API use 307/308 instead.

Best Practices

  • Always add -L for downloads — CDN endpoints almost always redirect to the actual file URL.
  • Cap --max-redirs at a sensible value (5-10) for user-facing scripts.
  • Never use --location-trusted on URLs from untrusted sources (user input, third-party APIs).
  • Use 307/308 redirects in your own APIs if clients need to preserve POST. 301/302 are legacy.
  • For debugging, curl -sIL URL | grep -i location shows the full chain without downloading bodies.

Related: cURL GET, cURL download files, cURL auth.