phphttp-redirecthttp-headersfpm

Return 200 status code with Location header


PHP documentation for the header() function tells us:

There are two special-case header calls... The second special case is the "Location:" header. Not only does it send this header back to the browser, but it also returns a REDIRECT (302) status code to the browser unless the 201 or a 3xx status code has already been set.

However, I want to return a 200 status code with a Location header. Testing on PHP's built-in web server with this code worked fine:

header('Location: https://example.com/foo', true, 200);
header('Content-Type: application/json');
echo '{"test":"testing"}';

But once I moved to the production PHP-FPM server it stopped working. I tried many variations on this code with no luck:

header('Location: https://example.com/foo', true, 200);
http_response_code(200);
header('HTTP/1.1 200 OK', true, 200);
header('Content-Type: application/json');
echo '{"test":"testing"}';

How can I do this, if at all?


According to my understanding of the PHP source, the first line should do it because of the explicitly specified status code:

if ((SG(sapi_headers).http_response_code < 300 ||
    SG(sapi_headers).http_response_code > 399) &&
    SG(sapi_headers).http_response_code != 201) {
    /* Return a Found Redirect if one is not already specified */
    if (http_response_code) { /* user specified redirect code */
        sapi_update_response_code(http_response_code);
    } else if (SG(request_info).proto_num > 1000 &&
       SG(request_info).request_method &&
       strcmp(SG(request_info).request_method, "HEAD") &&
       strcmp(SG(request_info).request_method, "GET")) {
        sapi_update_response_code(303);
    } else {
        sapi_update_response_code(302);
    }
}

Solution

  • By default, the CGI SAPI doesn't send the status code when it is equal to 200.

    Test script:

    <?php
    header('Location: https://example.com/foo', true, 200);
    ?>
    

    Execution from the command line:

    php-cgi.exe test.php
    

    Output:

    X-Powered-By: PHP/8.4.12
    Location: https://example.com/foo
    Content-type: text/html; charset=UTF-8
    

    Now we enable the cgi.nph flag to always send the status:

    <?php
    ini_set('cgi.nph', '1');
    header('Location: https://example.com/foo', true, 200);
    ?>
    

    Output:

    Status: 200 OK
    X-Powered-By: PHP/8.4.12
    Location: https://example.com/foo
    Content-type: text/html; charset=UTF-8
    

    The Status header is now sent.