Skip to content

/commands/curl/curl-follow-redirects

curl -L: follow redirects (301, 302) with curl

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · curl 8.21.0 (x86_64-w64-mingw32) libcurl/8.21.0 Schannel zlib/1.3.2updated
tool
curl
versions
curl 7.x, curl 8.x
os
linux, macos, windows

curl -L URL follows HTTP redirects: when the server answers with a 3xx status and a Location header, curl requests the new URL and repeats until it gets a final response. Without -L, curl prints the body of the redirect response (often empty) and stops, which is why downloads from GitHub releases, short links and http:// to https:// upgrades seem to return nothing. curl follows at most 50 redirects by default; change it with --max-redirs N.

Example

A Node.js server on 127.0.0.1 redirects /old to /new with a 301, and /loop to itself:

bash
cat > server.js <<'EOF'
const s = require("http").createServer((req, res) => {
  if (req.url === "/old") { res.writeHead(301, { Location: "/new" }); return res.end(); }
  if (req.url === "/loop") { res.writeHead(302, { Location: "/loop" }); return res.end(); }
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end(`final page, method ${req.method}\n`);
});
s.listen(5142, "127.0.0.1");
setTimeout(() => process.exit(0), 15000);
EOF
node server.js &
sleep 1
U=http://127.0.0.1:5142
echo "without -L:"; curl -s -w 'status %{http_code}, location %{redirect_url}\n' "$U/old"
echo "with -L:"; curl -sL "$U/old"
curl -sL -o /dev/null -w '%{url_effective} after %{num_redirects} redirect(s)\n' "$U/old"
echo "POST:"; curl -sL -d x=1 "$U/old"
curl -sL --post301 -d x=1 "$U/old"
curl -sSL --max-redirs 3 "$U/loop"; echo "exit $?"

Output

without -L:
status 301, location http://127.0.0.1:5142/new
with -L:
final page, method GET
http://127.0.0.1:5142/new after 1 redirect(s)
POST:
final page, method GET
final page, method POST
curl: (47) Maximum (3) redirects followed
exit 47

Without -L curl reports the 301 and the Location it did not follow. With -L it reaches /new. A POST became a GET after the 301 unless --post301 was given. The loop stopped after three redirects with exit code 47.

Options

OptionEffect
-L, --locationFollow 3xx redirects
--max-redirs NStop after N redirects (default 50; -1 means no limit)
--post301 / --post302 / --post303Keep POST instead of switching to GET for that status
--location-trustedAlso send credentials and Authorization to other hosts (use with care)
-I -LHeaders of each step of the chain
-w '%{url_effective}'Final URL
-w '%{num_redirects}'Number of redirects followed
-w '%{redirect_url}'Where the response would redirect to, when -L is not used

Which status codes

StatusMeaningMethod when curl follows
301 Moved PermanentlyPermanent movePOST becomes GET unless --post301
302 FoundTemporary movePOST becomes GET unless --post302
303 See OtherLook at another URL, usually after a POSTGET unless --post303
307 Temporary RedirectTemporary, method keptSame method and body
308 Permanent RedirectPermanent, method keptSame method and body

Errors

  • curl: (47) Maximum (N) redirects followed: a redirect loop or a very long chain. Look at it with curl -sIL URL | grep -i '^location'; loops often come from an HTTP to HTTPS rule combined with a proxy that forwards plain HTTP.
  • Empty output and exit 0 without -L: the response was a redirect with no body. Add -L or check the status with -w '%{http_code}'.
  • Redirects to another scheme are limited: curl follows HTTP and HTTPS by default and refuses to switch to protocols such as file://.

Platform notes

The same flags work on Linux, macOS and with curl.exe on Windows 10 and later. In Windows PowerShell 5.1 curl is an alias of Invoke-WebRequest, which follows redirects on its own and takes different parameters; type curl.exe.

FAQ

Does curl -L send my Authorization header to the new host?

No. Since curl 7.58 credentials and custom Authorization and Cookie headers are only sent to the host of the original URL. Add --location-trusted to send them to other hosts too, which is only safe when you trust every host in the chain.

Why does a POST turn into a GET after a redirect?

For 301, 302 and 303 responses curl switches to GET when following, as browsers do. Keep the method with --post301, --post302 or --post303, or have the server answer 307 or 308, which never change the method.

How do I see the final URL after redirects?

curl -sL -o /dev/null -w '%{url_effective}\n' URL prints it. %{num_redirects} gives the number of redirects followed.

Sources