/commands/http/follow-redirects-curl
curl follow redirects and show the redirect chain (curl -L)
- tool
- HTTP
- versions
- curl 7.x / 8.x
- os
- linux, macos, windows
curl -L URL follows HTTP redirects: when the response is a 3xx with a Location header, curl sends a new request to that location and repeats until it gets a non-redirect response. To see every hop, combine it with -I (headers only) or -i, and filter the status lines and Location headers. curl follows at most 50 redirects by default (--max-redirs).
See the redirect chain
cat > server.js <<'EOF'
const http = require("http");
http.createServer((req, res) => {
if (req.url === "/old") { res.writeHead(301, { Location: "/new" }); return res.end(); }
if (req.url === "/new") { res.writeHead(302, { Location: "/final" }); return res.end(); }
res.end("final page\n");
}).listen(5206, "127.0.0.1");
setTimeout(() => process.exit(0), 10000);
EOF
node server.js &
sleep 1
echo "== without -L"
curl -s -i http://127.0.0.1:5206/old | tr -d '\r' | grep -iE '^(HTTP|location)'
echo "== with -L, every hop"
curl -s -I -L http://127.0.0.1:5206/old | tr -d '\r' | grep -iE '^(HTTP|location)'
echo "== summary"
curl -s -L -o /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' http://127.0.0.1:5206/old
echo "== limit"
curl -s -S -L --max-redirs 1 http://127.0.0.1:5206/old; echo "exit status: $?"
waitOutput
== without -L HTTP/1.1 301 Moved Permanently Location: /new == with -L, every hop HTTP/1.1 301 Moved Permanently Location: /new HTTP/1.1 302 Found Location: /final HTTP/1.1 200 OK == summary 200 2 http://127.0.0.1:5206/final == limit curl: (47) Maximum (1) redirects followed exit status: 47
The summary line uses write-out variables: the final status, the number of redirects followed and the URL of the last request. When the limit is reached, curl stops with exit code 47 and the message "Maximum (1) redirects followed".
Options
| Option | Effect |
|---|---|
| -L, --location | Follow 3xx responses that carry a Location header |
| --max-redirs N | Stop after N redirects (default 50; -1 means no limit) |
| -w '%{redirect_url}' | Without -L: print where the redirect points, without following it |
| -w '%{num_redirects}' | With -L: how many redirects were followed |
| -w '%{url_effective}' | The URL of the last request made |
| --post301 / --post302 / --post303 | Keep POST instead of switching to GET on these codes |
| --location-trusted | Send credentials to the redirected host too (security risk) |
Which status codes redirect
RFC 9110 defines 301 (Moved Permanently), 302 (Found), 303 (See Other), 307 (Temporary Redirect) and 308 (Permanent Redirect). 307 and 308 require the client to repeat the same method and body; for 301 and 302 clients historically switch POST to GET, and 303 always means "fetch the result with GET". 304 Not Modified is a 3xx code but not a redirect: it has no Location and curl does not follow it.
Pitfalls
- A loop (
/aredirects to/b,/bback to/a) ends with exit code 47 after 50 hops. Use-I -L --max-redirs 5to print the loop and fix the server rule. -Isends HEAD. A server that redirects GET but answers HEAD differently shows a different chain; use-s -o /dev/null -D - -Lto dump the headers of real GET requests instead.- An HTTP-to-HTTPS redirect followed by a certificate problem fails on the second hop, so the TLS error appears even though you typed an
http://URL.
FAQ
Why does curl print nothing for a URL that works in the browser?
The server probably answers with a redirect (301, 302, 307 or 308) and an empty body. curl does not follow redirects unless you add -L, so it prints that empty body. Add -i to see the status line and the Location header.
Does curl -L keep the POST method on a redirect?
For 307 and 308 it repeats the same method and body. For 301, 302 and 303 it switches to GET, as browsers do; --post301, --post302 and --post303 keep POST for those codes.
Does curl send my Authorization header to the redirected host?
Not to a different host. curl drops credentials and custom Authorization and Cookie headers when a redirect goes to another host, unless you add --location-trusted. Use that only for hosts you trust.