2

Around 2017 I wrote a SSE (PHP) script and the JavaScript code. It worked fine with PHP-5 + HTTP/1 and the Firefox of that era.

The same code now does not work with Firefox-140 esr or latest Chrome with PHP-8.2 + HTTP/2. I have a time-out after 60 seconds.

Also tried the simplest SSE example from lucidar.me, zip. If I remove the while loop something that I think it's bad, it's working. In that case the JavaScript makes requests every 5 seconds. I think this defeats the purpose of SSE.

The code with while works fine with curl but not with browsers.

My implementation and the rationale of SSE is that the server decides when to send the data, not the client. Right?

It changed something in JavaScript I guess, in browsers, or did I miss something? It's the HTTP/2 the problem?

The code works with curl like that (while version)

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache, no-transform');
header('X-Accel-Buffering: no');
header('Access-Control-Allow-Origin: *');
set_time_limit(0);
@ini_set('zlib.output_compression', 0);
@ob_implicit_flush(true);

// Loop until the client close the stream
while (true) {

  // Echo time
  $time = date('r');
  echo "data: The server time is: {$time}\n\n";
 
  // Flush buffer (force sending data to client)
  @ob_flush();
  flush();

  // Wait 2 seconds for the next message / event
  sleep(2);
}
?>

Added info after @Hybrid gave me an idea.

Forgot to mention that I use apache. I switched to HTTP/1 and the result is bizarre. curl now responds after 2 minutes. With HTTP/2 the response was instant. Now Firefox responds like curl.

Testing with HTTP/1 only server

export LC_ALL=C; date; curl https://XXX/sse.php
Fri Jul 25 23:17:48 EEST 2025 <-- date invoked
data: The server time is: Sat, 26 Jul 2025 00:19:18 +0300 <-- received after 1 minute and 30 seconds.

data: The server time is: Sat, 26 Jul 2025 00:19:20 +0300

data: The server time is: Sat, 26 Jul 2025 00:19:22 +0300

... total 58 lines of data: received.

Here testing with HTTP/2 again. Only curl works, not browsers.

curl -v URL by-default uses HTTP/2:

* ALPN: curl offers h2,http/1.1
* ALPN: server accepted h2

So browsers, can't handle SSE with HTTP/2?

5
  • Can you show the curl and JS code that you're using with this, so we've got a minimal reproducible example of the issue? Please edit your question. See also How to Ask. Thanks Commented Jul 25 at 15:07
  • 1
    @ADyson: If these are tags you follow, please keep an eye out for [server-sent-events] questions mis-tagged as x86 SIMD [sse] when editing. Commented Jul 25 at 15:14
  • 1
    @Boom: Please don't use tags which don't apply at all. sse is x86 SIMD instructions like addps (add packed single-precision floats). server-sent-events is the one you want. When you find two tags which you think might apply, read their mouseovers. Really not cool to remove the correct tag and add the wrong tag, especially when there's already a comment about those tags. I already fixed the tags on this question once today. Commented Jul 25 at 21:47
  • @PeterCordes I made the mistake about sse vs server-sent-events. Commented Jul 25 at 21:51
  • krg: Yeah, happens to many new users when they don't read the tag mouseovers. That's understandable, you probably never saw that [server-sent-events] existed as a tag, so had no reason to wonder why both exist. I fixed it, then Boom submitted an edit which changed the tags back. But looking at the timeline, I see they submitted their edit only a minute after I fixed the tags, so probably they started editing on the old version. And Stack Overflow didn't detect the conflict or merge the tag change :/. (So @Boom, probably wasn't your fault about reverting the tags.) Commented Jul 25 at 22:00

2 Answers 2

1

It helps if you get sleep :) The solution was to disable buffering but since I use php-fpm with apache mod_proxy_fcgi I cannot disable output buffering script wise (maybe I am wrong here?).

So one solution is for apache conf is here: stackoverflow link

Flush packets every 10ms.

# max=10 = 10ms
<Proxy "fcgi://localhost/" enablereuse=on flushpackets=on max=10>
</Proxy>

I don't know the implications of that. But for SSE it works both for http:// and https://.

So maybe a better solution is here

echo 'SetEnv no-gzip 1' > .htaccess

Problem is that works only with https:// not with http://

It seems that those in .php script are useless

@ini_set('zlib.output_compression', 0);
@ini_set('output_buffering', 'off');

because they run AFTER apache?

Now it works with HTTP/2 and HTTP/1.

Thanks @hybrid for the inspiration.

Sign up to request clarification or add additional context in comments.

Comments

0

alright so I think there can be some issues why you php sse script it worked and not working as you stated for that it worked before, but no longer works in browsers (Chrome/Firefox) with PHP 8.2 + HTTP/2 browser times out after 60 seconds, but it works fine with curl.
What I think is that it can maybe due to HTTP/2 breaks streaming and and buffers the output or the PHP 8+ and proxies like nginx add ouput buffering as well and the browser need and expects regular data else it will timeout.
You can force the http/1.1 for the sse endpoint that may help.
in .htaccess or conf do like ( Will be something like this customize based on your specifics )

<Files "sse.php">
    Protocols h2 http/1.1
</Files>

disable the php output bufferings by adding this to sse.php top. ( Will be something like this customize based on your specifics )

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // Important for nginx
header('Access-Control-Allow-Origin: *');

@ini_set('output_buffering', 'off');
@ini_set('zlib.output_compression', false);
@ob_implicit_flush(true);
while (ob_get_level() > 0) ob_end_flush();
set_time_limit(0);

// Loop
while (true) {
    echo "data: The server time is: " . date('r') . "\n\n";
    @ob_flush();
    flush();
    sleep(2);
}

Also do disable the buffering for nginx in the config like ( Will be something like this customize based on your specifics )

location /sse.php {
    proxy_pass http://127.0.0.1:9000; # your PHP backend
    proxy_buffering off;
    proxy_cache off;
    chunked_transfer_encoding on;
    proxy_set_header X-Accel-Buffering no;
    proxy_http_version 1.1;
}

And at last just to avoid the browsers timeout send alive poing every 15 secs like stated below

echo "event: ping\ndata: keep-alive\n\n";

I will be happy to assist more. If you got questions on this. Let me know if these don't fit your file or server specifics.

2 Comments

Thanks. I needed that support :) Well I failed with .htaccess but I made HTTP/1 server wide. Bizarre. On my server only curl is working BUT after 1 minutes and 30 secs with 58 lines of data. Same result on an AWS server (Amazon Linux) with browser. On my local server browser fails. So it must be some caching. I will investigate again in 8-10 hours. Thank you for the ideas. I edited my question.
No worries, pleasure is all mine

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.