Skip to content

setcookie should overwrite cookie header with same info #18169

Closed
@kkmuffme

Description

@kkmuffme

Description

setcookie() currently just adds another HTTP header (= like calling header( 'Set-Cookie: ...', false ); in all cases.

This means if the same cookie (= functionaly identical, e.g. same name+path+domain+...) is set multiple times (e.g. different value/expiration or also for whatever reason same value/expiration) it will just add another Set-Cookie header.

Since HTTP headers cannot be compressed* or compression happens upstream, this causes significant performance downsides:

  • upstream needs to allocate larger buffers (nginx fastcgi_buffer) that exceed 1 or 2 memory pages for the odd request that has this issue, leading to significantly higher memory consumption just for the rare request that has this issue. e.g. only 0.0001% of requests send headers that exceed 4k (one memory page). Those requests send headers with an average size of 12k and a maximum of 16k.
    This means that a buffer size of 16k has to be allocated, which means that if we can serve up to 100000 requests simultaneously (= 2000 pages that each contain ~50 js/css/images) that's more than 1GB of memory unnecessarily allocated/wasted just to ensure that the 1 request that has this issue to not cause upstream sent too big header while reading response header from upstream

  • the overall page load can take significantly longer - e.g. HTTP headers are 6k and compressed page is 6k (assuming the page is served from a PHP page cache to ignore any other effects). If the set-cookie headers are deduplicated it's HTTP headers 0.5k + 6k effectively reducing the overall page load time by almost 50% in this example:

Date: Fri, 28 Mar 2025 08:44:58 GMT
Content-Type: application/json; charset=UTF-8
X-Content-Type-Options: nosniff
Access-Control-Expose-Headers:
Access-Control-Allow-Headers:
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=at; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=be; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=bn; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=cf; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=ch; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=da; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=de; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=en; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=es; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=fi; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=fr; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=ie; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=it; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=pt; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=sv; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Set-Cookie: some_test_foo_bar_cookie_d41d8cd98f00b204e9870998ecf8421e=nl; expires=Sat, 29-Mar-2025 08:44:59 GMT; Max-Age=86400; path=/; secure
Allow: GET, POST, PUT, PATCH, DELETE
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private
X-Accel-Buffering: no
Content-Encoding: br
Vary: Accept-Encoding

While it's true that in many cases, these duplicate setcookie calls indicate an issue in the application, it's often outside of the scope (= 3rd party dependencies causing it or people not being aware of this issue at all in the first place) of the application.

This change could result in a significant performance gain (= time until the first part of the document is loaded) for all PHP applications on pages where they set cookies.

*in http2/3 they can, but it's practically not supported or implemented anywhere and that's unlikely to change

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions