PHP: How do you redirect to another page?

A redirect in PHP is one line of code. There are two mistakes around it that happen so often, though, that they make up the larger part of this article.

The right way

<?php
  header('Location: /target.php');
  exit;
?>

That is all. header() sets the HTTP Location header and the browser follows it. Let us look at the server’s actual response:

HTTP/1.1 302 Found
Content-Type: text/html; charset=UTF-8
Location: /ziel.php

Status code 302, a Location header, no content. That is exactly how it should look.

Mistake 1: the forgotten exit

Calling header() does not end your script. Everything after it still runs and still gets delivered:

<?php
  header('Location: /target.php');
  echo "SECRET: this text is delivered anyway\n";
?>

The server’s response then looks like this:

HTTP/1.1 302 Found
Location: /ziel.php

GEHEIM: dieser Text wird trotzdem ausgeliefert

In a browser you will not notice, because it follows the redirect and never displays the text. The text is part of the response all the same. Any tool that does not follow the redirect gets to see it. A plain curl without -L is enough.

Now imagine that instead of SECRET there is the content of a page that is only meant for logged-in users. That is exactly how you end up with an access check that works in a browser and nowhere else:

<?php
  // Please do not do this!
  if (!$userIsLoggedIn) {
    header('Location: /login.php');
    // the exit is missing here
  }

  echo $secretData;
?>

So: an exit after every header('Location: ...').

Mistake 2: something has already been output

HTTP headers have to be sent before the content. As soon as PHP has output its first character, the headers are on their way and can no longer be changed:

<?php
  echo "Output before the redirect\n";
  header('Location: /target.php');
  exit;
?>

The result is remarkable:

HTTP/1.1 200 OK

Ausgabe vor dem Redirect
Warning: Cannot modify header information - headers already sent by
(output started at /var/www/html/nach-ausgabe.php:3) in ... on line 4

The status code is 200 and there is no Location header. So the redirect does not happen at all. And on a production system, where display_errors is off, you will not even see the warning — only a page that simply refuses to move on.

The most common causes are:

  • an echo or print before the header()
  • a blank line before the opening <?php
  • a blank line after a closing ?> in an included file
  • a BOM at the start of the file, left there by your editor

The last one is particularly nasty because you cannot see the character. Best leave the closing ?> out of pure PHP files entirely, then nothing can end up after your code.

The right status code

Without further instruction PHP sends a 302 Found, that is a temporary redirect. If the page has moved permanently, you want a 301:

<?php
  header('Location: /target.php', true, 301);
  exit;
?>
HTTP/1.1 301 Moved Permanently
Location: /ziel.php

The difference is not cosmetic. Search engines take the new address into their index on a 301 but not on a 302. Browsers also remember a 301 and stop requesting the old address at all — which reliably causes confusion while testing. So only send a 301 when you are sure.

If you redirect after a submitted form, 303 See Other is the clean choice. It makes sure the browser requests the target page with GET, so a reload does not submit the data again.

Relative or absolute address?

HTTP/1.0 used to require a full URI. Since RFC 7231 relative references are explicitly allowed and work in every current browser. So /target.php is fine.

Summary

  • header('Location: ...') followed by exit.
  • Without the exit the remaining content is delivered too, even if you do not see it in a browser.
  • Nothing may have been output before the header(), otherwise there is no redirect at all.
  • 302 is temporary, 301 permanent, 303 after a form.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).