Skip to content

Header Location redirects to 200 instead of 302 #18806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
engageub opened this issue Jun 9, 2025 · 7 comments
Closed

Header Location redirects to 200 instead of 302 #18806

engageub opened this issue Jun 9, 2025 · 7 comments

Comments

@engageub
Copy link

engageub commented Jun 9, 2025

Description

Hi,

The following code has been part of the system for several years. However, a recent issue prevented the browser from redirecting as expected.

Typically, the code performs a redirection to a new URL with an HTTP status code of 302. Unfortunately, for one specific website, the redirection failed, and instead of a 302 response, the server returned an HTTP 200 status code.

When the URL is pasted directly into the browser, the website works fine. However, the issue arises when the redirection is triggered through the code.

This issue has now been resolved, but we would like to understand the circumstances under which this behaviour occurs and how to replicate it.

Could this be related to the website’s response time? If so, why does the PHP backend need to account for this particular website and return an HTTP 200 status code instead of the expected 302?


     header('Referrer-Policy: unsafe-url');

     header('Location: ' . $result['shortenedUrl']);

     exit;

Resulted in this output:

HTTP response was 200 and the web page was not redirected.

But I expected this output instead:

HTTP response code should be 302 similar to other links.

PHP Version

PHP 8.2.28 (cli) (built: Apr  3 2025 13:03:28) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.28, Copyright (c) Zend Technologies
    with the ionCube PHP Loader v14.4.0, Copyright (c) 2002-2025, by ionCube Ltd.
    with Zend OPcache v8.2.28, Copyright (c), by Zend Technologies

Operating System

Linux vda6400.is.cc 4.18.0-425.19.2.lve.el8.x86_64 #1 SMP Thu Apr 6 12:07:52 EDT 2023 x86_64 x86_64 x86_64 GNU/Linux

@iluuu1994
Copy link
Member

Setting the Location header sets the http status code for you. Can you please post a reproducer? We cannot understand the problem without it. Potentially, your code somewhere overwrites the status code again. Check for usages of http_response_code() in your code.

@engageub
Copy link
Author

engageub commented Jun 9, 2025

Hi,

Thank you for the response.
I don't see http_response_code being used in the code.
Please find below the following snippet of code where the data is fetched from a remote URL.
Is it possible that the PHP headers somehow get overwritten by the response from the Remote URL after the Curl command.
I can't replicate the issue now as it is redirecting with 302 status code.

    // get shortlink data
        $api_token = $val['apikey'];
        $api_url = $val['apilink'];

        // Check protocol
        if (is_ssl()) {
            $protocol = "https://";
        } else {
            $protocol = "http://";
        }
        //
        $return_url = $protocol . $_SERVER['HTTP_HOST'] . strtok($_SERVER['REQUEST_URI'], '?') . '?sl=' . $_SESSION['shortlink' . $session_prefix]['hash'];
        $return_url_encoded = urlencode($return_url);

        $ch = curl_init();
        if ($ch === false) {
            // curl not enabled
            header('X-Error: Curl not enabled.');
            header('Location: .');
            exit;
        }

        // no links generated today
        $api_url .= '?api=' . $api_token . '&url=' . $return_url_encoded . '&alias=FBU' . randHash(9) . '&expiry=' . (time() + 10800); //.'&type=1'
        // get the shortlink
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_URL, $api_url);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        $shortlink_response = @curl_exec($ch);
        curl_close($ch);
        // not compatible with http: api - retry with https:
        if (empty($shortlink_response)) {
            $http_pos = stripos($api_url, 'http://');
            if (($http_pos !== false) && ($http_pos == 0)) {
                if ($ch = curl_init()) {
                    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
                    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
                    curl_setopt($ch, CURLOPT_URL, 'https://' . substr($api_url, 7));
                    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0');
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
                    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
                    $shortlink_response = @curl_exec($ch);
                    curl_close($ch);
                }
            }
        }
        $result = @json_decode($shortlink_response, true);
        if ((!empty($result['status'])) && ($result['status'] == 'success')) {
            // valid shortlink - go
            $_SESSION['shortlink' . $session_prefix]['used'] = $key;
            $_SESSION['shortlink' . $session_prefix]['used_url'] = $result['shortenedUrl'];
            $qq = $sql->prepare("INSERT INTO " . $dbtable_prefix . "Shortlinks(time, shortlink, link, hash, users_sent) VALUES(?,?,?,?,?);");
            $qq->execute(array(time(), $key, $result['shortenedUrl'], $_SESSION['shortlink' . $session_prefix]['hash'], 1));

            $_SESSION['shortlink' . $session_prefix]['id'] = $sql->lastInsertId();
            header('Referrer-Policy: unsafe-url');
            header('Location: ' . $result['shortenedUrl']);
            exit;
        }

@iluuu1994
Copy link
Member

We need an executable, reproducible example, and configuration of web-servers/proxies. This code does not reproduce for me:

header('Referrer-Policy: unsafe-url');
header('Location: https://www.google.com');
php -S 127.0.0.1:8080 test.php
$ curl http://127.0.0.1:8080 -v
*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.12.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 302 Found
< Host: 127.0.0.1:8080
< Date: Mon, 09 Jun 2025 20:46:40 GMT
< Connection: close
< X-Powered-By: PHP/8.4.7
< Referrer-Policy: unsafe-url
< Location: https://www.google.com
< Content-type: text/html; charset=UTF-8
< 
* shutting down connection #0

It's very likely this is an issue on your end. If you're using a proxy, try without it, or check whether it may discard your http status code.

@engageub
Copy link
Author

engageub commented Jun 9, 2025

Hi,

The website is on a hosting platform with direct connection and proxies are not being used.
The code has not been modified for long time.

Test Server Link: https://preproduction.shortlinksfaucet.xyz/
Full PHP File: https://pastebin.com/raw/iZ76ZpHX

If you click on any of the buttons, it gets redirected to a new location with 302 status code

I am unable to reproduce the issue now. Please note that the issue was related to specific website and not other links when this happened. So I thought it might be related to the response being received using the curl command.
When I printed the URL using echo command for variable $result['shortenedUrl'] before the exit statement , the URL was displayed in the browser but the http response code was 200, so it was not being redirected to the URL present in $result['shortenedUrl'].
I tried this using different browsers with direct connection and different Proxy locations to confirm if this is not specific to ISP or IP address. This happened only for a particular shortlink and was returning status code 200 , while others were working with 302 status code.

The issue happened for few hours for a particular shortlink website and was resolved automatically.

I tried to replicate later using the blank values and invalid URLs but it still showed 302 response code.

Thank you

@engageub
Copy link
Author

engageub commented Jun 10, 2025

Adding to the above, the server is behind cloudflare. I have also raised a concern in cloudflare community.

https://community.cloudflare.com/t/does-cloudflare-cache-http-status-codes/806059

Based on the following information from cloudflare cache status codes , Cloudflare caches certain HTTP response codes by default.
I am uncertain whether the issue is related to the following statement.

By default, Cloudflare caches certain HTTP response codes with the following Edge Cache TTL when a cache-control directive or expires response header are not present.

Thank you

@engageub
Copy link
Author

engageub commented Jun 10, 2025

Based on the above and following statements from cloudflare community, the issue seems to be due to caching.
I have created a rule now in cloudflare to bypass caching for Responses.

If a resource returns a 200, Cloudflare will return a cached 200 for future requests until cache expires. 120 minutes by default.

If a resource returns a 302, Cloudflare will return a cached 302 for future requests until cache expires. 20 minutes by default.

https://community.cloudflare.com/t/does-cloudflare-cache-http-status-codes/806059
https://developers.cloudflare.com/cache/how-to/configure-cache-status-code/

Thank you

@iluuu1994
Copy link
Member

Thanks for letting us know. Closing this accordingly.

@iluuu1994 iluuu1994 closed this as not planned Won't fix, can't repro, duplicate, stale Jun 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants