Repository navigation
Releases
v5.1.1
🔐 Security update: FrankenPHP 1.13.5
Important
Pull and redeploy if you use frankenphp. FrankenPHP 1.13.5 fixes 17 vulnerabilities. Several affect the default php_server setup our images use, including one that runs an uploaded non-PHP file as PHP. It also moves to Caddy 2.11.9, which has security fixes of its own.
Note
These issues are in php/frankenphp and caddyserver/caddy upstream, not in this repo. cli, fpm, fpm-nginx, and fpm-apache are not affected.
🐘 FrankenPHP v1.13.5
| Advisory | Issue |
|---|---|
| GHSA-j99q-3wr8-xj43 | High. php_server could run a file as PHP even when its name didn't end in .php, such as /uploads/photos.php.php/avatar.jpg. Anyone who could upload a file under the document root could execute it. |
| GHSA-jvmv-5538-pqpg | The trailing-slash redirect for directories could send visitors to another host |
| GHSA-2725-rh2w-9j9w | Over HTTP/2, a host header sent next to :authority set $_SERVER['HTTP_HOST'] to a different host than the one Caddy routed |
| GHSA-j45x-h238-h4xx | One slow request body per PHP thread could block every thread, even with request_body_timeout set |
| GHSA-5359-vg53-5464 | A request with more parameters than max_input_vars, sent repeatedly, crashed the process |
| GHSA-q6vv-3p75-53wr | exit() or die() with a negative status, such as die(-1), stopped FrankenPHP |
| GHSA-2x4j-r638-wxff | Dot segments after a PHP script, such as /admin/tool.php/../../missing.php, could get past a Caddy path rule meant to deny it |
| GHSA-388m-835f-7723 | A client's Authorization: Basic header, and four other variables, could replace values set with env |
| GHSA-g7pw-whv8-97j7 | A php_server or php directive in a Caddy named route could run with another site's root, env values, and workers |
| GHSA-v7hf-4gqv-hf8p | A php_server with a worker match rule could serve PHP source code |
| GHSA-8qcf-v46r-g2mc | In worker mode, destructors from the previous request, including session objects, could send output and cookies to the next client |
| GHSA-j2f3-wxf2-8qgv | Embedded apps: a config reload deleted the extracted app directory while it was still being served |
| GHSA-xh92-4p94-ch7x | php_server served a Caddyfile stored in the document root |
| GHSA-hfxc-999w-mp67 | FrankenPHP crashed when the OS refused to create a new PHP thread |
| GHSA-g9v4-rp77-rv76 | Clients could probe whether files under the document root exist through PATH_INFO |
| GHSA-cp2q-fc46-38f5, GHSA-28vm-87fh-3px5 | Custom builds with a Go extension that uses the experimental worker APIs only |
Our images already returned 403 for any .php path under /storage, which covers Laravel's public uploads. Uploads anywhere else in your document root were exposed to the first one.
Our Caddyfile lives in /etc/frankenphp, not the document root. Embedded apps, named routes, and custom Go extensions need setups our images don't use by default.
It also upgrades Caddy to 2.11.9 and Mercure to 1.1.6. Request bodies over Caddy's request_body max_size now get a 413, missing scripts return 404 No input file specified. like PHP-FPM, and $_SERVER['SERVER_ADDR'] is now set.
Worker mode keeps a little more state between requests
If you run Laravel Octane or another worker, error_get_last(), strtok(), http_get_last_response_headers(), and libxml errors now carry over between requests, like the rest of your app's state. FrankenPHP still resets superglobals, the request body, headers, output, and the session. Call error_clear_last(), libxml_clear_errors(), or http_clear_last_response_headers() if your code expects a clean slate. See php/frankenphp#2790.
FrankenPHP images now match the official runtime image
- Fonts, video, and other static files get the right
Content-Type. The image had no/etc/mime.types, so Caddy fell back to Go's short built-in list and guessed the rest. CSS, JavaScript, and common images were fine, but a.woff2font could go out astext/plain. We now installmailcaplike the official FrankenPHP images. GODEBUGdefaults tocgocheck=0, like the official images, so Go skips its pointer checks on every call into PHP. If you setGODEBUGyourself, it replaces this default, so includecgocheck=0in your value:GODEBUG=cgocheck=0,http2debug=1. See the environment variable reference.
How to update
| If you use | Example | Do this |
|---|---|---|
| A floating tag | 8.5-frankenphp |
Pull and redeploy |
| A release tag | 8.5-frankenphp-v5.1.0 |
Change it to 8.5-frankenphp-v5.1.1 |
| A patch version tag | 8.5.12-frankenphp |
Pull and redeploy |
If you build your own image FROM one of these, rebuild it too.
Then confirm the version:
docker compose exec php frankenphp versionWhat's Changed
- Security: Update FrankenPHP to v1.13.5 by @jaydrogers
- Install
mailcapand setGODEBUG=cgocheck=0in thefrankenphpimages to match the official FrankenPHP images by @jaydrogers
Full Changelog: v5.1.0...v5.1.1
v5.1.0
🤩 What's new
PHP security releases land much faster (#739, #742)
During the last critical PHP release (#721), we waited hours for Docker Hub's php images, then PECL went down. We now compile PHP ourselves with the same Dockerfiles as the official images, so neither one can hold up a release. A new hourly check starts a build as soon as php.net lists a patch, on any day of the week.
| From php.net to your image | |
|---|---|
| Before | php.net → wait ~4 hours for Docker Hub's php images → PECL → wait for our next release or Tuesday rebuild |
| After | php.net → about an hour later, we compile PHP and publish |
Every build also starts from current Debian and Alpine packages, so OS security fixes reach your image on the weekly rebuild (Tuesday 08:00 UTC). PHP 8.1 benefits most: its official images haven't been rebuilt since it reached end of life.
Verify that an image came from us (#742)
Every image is now signed with cosign and ships with an SBOM (every package inside it) and build provenance (how and where it was built). Check an image before you run it:
cosign verify serversideup/php:8.5-cli \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github\.com/serversideup/docker-php/\.github/workflows/service_publish-images\.yml@refs/(heads/main|tags/v.+)$'Images published before 5.1 have provenance but no signature or SBOM. See Verifying images.
Every image is scanned for vulnerabilities (#742)
After every production build, Grype scans each published image. The results are on each run's summary page. You can see the known vulnerabilities in any image without running a scanner yourself. See Vulnerability scanning.
PHP 8.6 release candidates (#698, #739)
Test your app on PHP 8.6 before it ships. Every rebuild uses the newest release candidate on php.net, so a new RC arrives with the next weekly rebuild. These tags are for testing only.
serversideup/php:8.6-rc-fpm-nginx
New docs
The docs have been optimized from top to bottom. Every environment variable and helper command is now in a searchable reference, generated from the same data our CI checks against the code. Upgrading, Security, and Troubleshooting each have their own section. Every old URL redirects to its new page.
⬆️ Upgrading
Nothing to change. Every existing tag is still published and the images behave the same. Three things you may notice:
php -vnow saysBuilt by Server Side Upinstead ofBuilt by https://github.com/docker-library/phpinstall-php-extensionsis upgraded from 2.12.0 to 2.12.6- If you run
docker-php-serversideup-install-php-ext-installerwithout a version in your own Dockerfile, it now installs the latest release instead of 2.12.0. Pass a version to pin it.
🐛 Bug fixes
docker-php-serversideup-downloadprinted every shell option to stdout before the file when you didn't give it an output file (#739)- FrankenPHP exited at start-up, logging no error, when
CADDY_AUTO_HTTPSwasdisable_redirects,disable_certs, orignore_loaded_certsandSSL_MODEwasmixedorfull docker-php-serversideup-set-idfailed the build with "UID already in use" when another user in the image already had the UID you passed, because it moved the wrong user. It now moves the other user or group to99<id>and gives yours the ID you asked for
🏗️ For contributors
- Every pinned version lives in
scripts/conf/php-versions-base-config.yml, and Renovate replaces Dependabot to keep them current (#739) scripts/dev.sh --build-php-basebuilds exactly what CI builds. See Build an image- Every GitHub Action is pinned to a commit SHA, so a moved tag can't change what runs in CI. A new zizmor lint checks the workflows on every PR, and an OpenSSF Scorecard badge tracks our supply chain practices (#742)
- Image tests are split into one file per feature area in
scripts/tests/image/, and every failure shows up in one run. New tests cover Laravel automations, startup scripts, SSL modes, trusted proxies, PHP-FPM pool variables, file permissions, and shutdown (#741) - Failed CI runs now actually retry once on their own (#739)
- Building
src/variations/*/Dockerfiledirectly now needs the dependency build args fromscripts/conf/php-versions-base-config.yml, so usescripts/dev.sh
Full Changelog: v5.0.5...v5.1.0
v5.0.5
🔐 Security update: file permissions in the fpm and frankenphp images
Important
Pull and redeploy if you use fpm or frankenphp. Releases v4.3.1 through v5.0.4 shipped with bad default permissions. This release patches those permissions. See GHSA-xx74-jh2r-mgm3 for more details.
Note
cli, fpm-nginx, and fpm-apache are not affected. This applies to Docker Hub and GitHub Container Registry.
What we fixed
Two COPY instructions in the fpm and frankenphp Dockerfiles didn't set permissions, so the images picked up directory modes from the build environment. Both now set them explicitly.
We also added a check to CI. Every image is now tested for world-writable files and directories before it's published, so this can't ship again.
Thanks to @lance-compuco for reporting this responsibly.
How to update
| If you use | Example | Do this |
|---|---|---|
| A floating tag | 8.4-fpm |
Pull and redeploy |
| A release tag | 8.4-fpm-v5.0.4 |
Change it to 8.4-fpm-v5.0.5 |
| A patch version tag | 8.4.27-fpm |
Pull and redeploy |
If you build your own image FROM one of these, rebuild it too.
What's Changed
- Security: Set explicit permissions on copied configs in the fpm and frankenphp images by @jaydrogers
- Fail CI when an image contains world-writable files or directories by @jaydrogers
Full Changelog: v5.0.4...v5.0.5
v5.0.4
🔐 Security update: Brotli CVE-2026-111497 and FrankenPHP 1.13.4
Important
Pull and redeploy. The official PHP images ship a version of the Brotli library with an out-of-bounds write that can lead to remote code execution (CVE-2026-111497, also called Node2Shell). PHP's curl extension uses it, so every variation is affected. This release upgrades Brotli in every image. The FrankenPHP images also move to FrankenPHP 1.13.4, which fixes a crash that 1.13.2 (shipped in v5.0.3) introduced.
Note
These issues are in google/brotli and php/frankenphp upstream, not in this repo. PHP versions are the same as v5.0.3.
Brotli is patched in every image
| CVE | Issue | Fix |
|---|---|---|
| CVE-2026-111497 | Out-of-bounds write in the Brotli decoder that permits remote code execution | Brotli 1.1.1 and 1.2.1, plus distribution backports |
PHP reaches the Brotli decoder through curl when it decodes a Brotli-compressed response. A server your app calls could send one.
The official PHP images haven't been rebuilt with the fix yet. Our images now upgrade Brotli during the build:
| Base OS | Before | Now |
|---|---|---|
| Debian Bookworm | 1.0.9-2+b6 |
1.0.9-2+deb12u1 |
| Debian Trixie | 1.1.0-2+b7 |
1.1.0-2+deb13u1 |
| Alpine 3.23 and 3.24 | 1.2.0-r1 |
1.2.1-r0 |
| Alpine 3.22 (PHP 8.1) | 1.1.0-r2 |
1.1.1-r0 |
Alpine fixed 3.22 with Brotli 1.1.1. It hasn't added the CVE to that branch's security database yet, so vulnerability scanners may still flag the PHP 8.1 Alpine images until it does.
We'll drop this workaround once the official PHP images ship the fix.
🐘 FrankenPHP v1.13.4
FrankenPHP 1.13.4 is a security release. It fixes these issues in FrankenPHP itself:
| Advisory | Issue |
|---|---|
| GHSA-5f4m-ff4w-g458 | High. In worker mode, a single unauthenticated request could crash the whole process. Only 1.13.2 and 1.13.3 are affected. |
| GHSA-rgpj-f2qx-xwhf | Calling putenv() from a shutdown function, destructor, or session handler could crash the process or leak the previous request's value |
| GHSA-cm3q-jw2q-9rh5 | In worker mode, an SplTempFileObject kept across requests had its stream freed |
| GHSA-8386-64gm-3m8q | In worker mode, per-request environment variables leaked into later requests |
| GHSA-pmvg-8483-mw84 | In worker mode, http_get_last_response_headers() returned headers from a previous request |
| GHSA-h764-4wh7-m7j2 | In worker mode, ending the worker loop after a JSON POST freed the same pointer twice |
| GHSA-x6j6-qv7w-fq3c | In worker mode, a fatal error during request startup left the request waiting forever |
If you run Laravel Octane or another worker mode setup on v5.0.3, the first one affects you. FrankenPHP says the others need specific app code or non-default configuration. They don't affect common Laravel and Symfony setups.
It also upgrades Mercure to 1.1.5.
Only the frankenphp images include FrankenPHP.
How to update
| If you use | Example | Do this |
|---|---|---|
| A floating tag | 8.4-fpm-nginx |
Pull and redeploy |
| A release tag | 8.4-fpm-nginx-v5.0.3 |
Change it to 8.4-fpm-nginx-v5.0.4 |
| A patch version tag | 8.4.27-fpm-nginx |
Pull and redeploy |
Then confirm Brotli is patched. On Debian:
docker compose exec php dpkg-query -W libbrotli1On Alpine:
docker compose exec php sh -c "apk list -I | grep brotli-libs"On FrankenPHP, confirm the version too:
docker compose exec php frankenphp versionFrankenPHP 1.13.4 needs a newer Brotli than Debian Bookworm ships. The Bookworm FrankenPHP images bundle Brotli 1.2.1 in /usr/local/lib, and both FrankenPHP and curl use it.
What's Changed
- Security: Upgrade Brotli past CVE-2026-111497 in every variation by @jaydrogers
- Security: Update FrankenPHP to v1.13.4 by @jaydrogers
Full Changelog: v5.0.3...v5.0.4
v5.0.3
🔐 Security update: PHP session upload progress and FrankenPHP 1.13.2
Important
Pull and redeploy. PHP has announced a new remote code execution vulnerability (CVE-2026-115560) that isn't fixed yet. It's separate from the two CVEs that v5.0.2 patched, so v5.0.2 doesn't protect you from it. This release turns off session upload progress, which blocks it. The FrankenPHP images also move to FrankenPHP 1.13.2.
Note
These issues are in php/php-src and php/frankenphp upstream, not in this repo. PHP versions are the same as v5.0.2.
A new PHP vulnerability, not yet fixed
| CVE | Issue | PHP fix |
|---|---|---|
| CVE-2026-115560 | Remote code execution through session upload progress | Not released yet. PHP will publish the details and the fix in its next regular release. |
This is a different vulnerability from CVE-2026-109618 and CVE-2026-109619, which PHP fixed on October 21. Updating to PHP 8.5.12, 8.4.27, 8.3.36, or 8.2.35 doesn't fix it.
Until PHP ships the fix, they recommend turning off session upload progress. That's what this release does. The same setting was already a suggested workaround for CVE-2026-109619. If you turned it off yourself on October 21, keep it off.
Your app is only exposed if all of these are true:
session.serialize_handleris set tophp_binaryorphp_serializein your config- Your code switches it to
phpwithini_set()before callingsession_start() - Session upload progress is enabled (PHP's default)
Our images default session.serialize_handler to php. That makes this unlikely. We'd still rather ship the safe default than have you check every app by hand.
Who this affects
Only apps that read upload progress from $_SESSION.
If you need it and never change session.serialize_handler at runtime, turn it back on:
services:
php:
image: serversideup/php:8.4-fpm-nginx
environment:
PHP_SESSION_UPLOAD_PROGRESS_ENABLED: "1"We'll revisit this default once PHP ships its fix.
🐘 FrankenPHP v1.13.2
FrankenPHP 1.13.2 came out after v5.0.2 was built. It fixes these issues in FrankenPHP itself:
| Advisory | Issue |
|---|---|
| GHSA-fq24-r53r-xcx3 | An unauthenticated client could keep every PHP thread busy by queuing requests and disconnecting |
| GHSA-4gfw-9v29-gp9h | In worker mode, error_get_last() and libxml_get_errors() could leak a previous request's input and server paths |
| GHSA-ccgp-ch9h-533f | A self-referencing array passed to frankenphp_log() or a worker callback crashed the whole process |
It also makes frankenphp_log() available in php-cli. Hot reload no longer serves stale pages.
Only the frankenphp images include FrankenPHP.
How to update
| If you use | Example | Do this |
|---|---|---|
| A floating tag | 8.4-fpm-nginx |
Pull and redeploy |
| A release tag | 8.4-fpm-nginx-v5.0.2 |
Change it to 8.4-fpm-nginx-v5.0.3 |
| A patch version tag | 8.4.27-fpm-nginx |
Pull and redeploy |
Then confirm the setting is off:
docker compose exec php php -i | grep session.upload_progress.enabledOn FrankenPHP, confirm the version too:
docker compose exec php frankenphp versionWhat's Changed
- Security: Add
PHP_SESSION_UPLOAD_PROGRESS_ENABLEDand default it to0by @jaydrogers - Security: Update FrankenPHP to v1.13.2 by @jaydrogers
Full Changelog: v5.0.2...v5.0.3
v5.0.2
🚨 Critical PHP security update
Important
Update now. Patched images for PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35 are live on Docker Hub and GitHub Packages. Every image was built and tested before anything was published. See #721 for how today's release unfolded.
Note
The CVEs below are in php/php-src upstream, not in this repo. This release rebuilds every image on the patched official Docker PHP images. We didn't need to change any code for it.
Upstream CVEs (PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35)
| CVE | Severity | Issue | PHP advisory |
|---|---|---|---|
| CVE-2026-109618 | Critical (9.2) | Remote code execution through out-of-memory corruption | GHSA-w8hw-w9fx-j79w |
| CVE-2026-109619 | Critical (9.2) | Remote code execution through stale global state | GHSA-6wmf-4rv5-p3rp |
Both can be exploited without logging in. See php.net's advisory for who is at risk.
| PHP | Version in this release |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
| 8.1 | 8.1.34 (not patched, end of life) |
PHP 8.1 is end of life, so php.net didn't release a fix for it. If you're on 8.1, move to 8.2 or newer.
How to update
| If you use | Example | Do this |
|---|---|---|
| A floating tag | 8.4-fpm-nginx |
Pull and redeploy |
| A release tag | 8.4-fpm-nginx-v5.0.1 |
Change it to 8.4-fpm-nginx-v5.0.2 |
| A patch version tag | 8.4.26-fpm-nginx |
Change it to 8.4.27-fpm-nginx |
Then confirm you're on the patched version:
docker compose exec php php -v🐘 FrankenPHP v1.13.1
FrankenPHP 1.13.0, which shipped in v5.0.0 and v5.0.1, had several regressions. v1.13.1 fixes them, including:
- Laravel and Symfony apps that use
fastcgi_finish_request()over HTTP/2 could crash the process (php/frankenphp#2694) - Workers declared in the Caddyfile gave up after the first failed boot instead of retrying (php/frankenphp#2704)
php-clipassed the wrong$argv, which broke Symfony Console (php/frankenphp#2690)- Requests served during a graceful shutdown returned HTTP 500 (php/frankenphp#2644)
- The process segfaulted when FrankenPHP restarted an unhealthy thread (php/frankenphp#2688)
We also made two fixes of our own in the FrankenPHP image:
- The Mercure hub is no longer compressed. Each subscriber held its own compression encoder (a few MiB each with zstd). Compressing private updates also opens the door to BREACH-style attacks. Everything else is still compressed.
- A failed extension install now fails the build. Before, if pecl.php.net timed out mid-build, the FrankenPHP image could publish without extensions like
redisandzip. The other variations already worked this way.
Full Changelog: v5.0.1...v5.0.2
v4.5.3
🚨 Critical PHP and NGINX security update for v4 users
Important
This release is only for people who can't move to v5 yet. It rebuilds v4.5.1 on PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, and updates NGINX to 1.30.6. If you're already on v5, use v5.0.2 instead. See #721 for the full story.
Warning
v4.5.3 is published to release tags only (like 8.4-fpm-nginx-v4.5.3). Floating tags like 8.4-fpm-nginx and latest stay on v5, so you need to update your pin to get this fix.
v4.5.2 was tagged but never published. Go straight from v4.5.1 to v4.5.3.
Note
The CVEs below are in php/php-src and nginx/nginx upstream, not in this repo. This release rebuilds the v4 images on the patched official Docker PHP images and bumps the NGINX we install.
Upstream CVEs (PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35)
| CVE | Severity | Issue | PHP advisory |
|---|---|---|---|
| CVE-2026-109618 | Critical (9.2) | Remote code execution through out-of-memory corruption | GHSA-w8hw-w9fx-j79w |
| CVE-2026-109619 | Critical (9.2) | Remote code execution through stale global state | GHSA-6wmf-4rv5-p3rp |
Both can be exploited without logging in. See php.net's advisory for who is at risk.
| PHP | Version in this release |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
| 8.1 and older | Not included (no official fix) |
php.net didn't release a fix for 8.1 or older, so this release doesn't rebuild them. If you're on 8.1 or older, move to 8.2 or newer.
Upstream CVEs (NGINX 1.30.6)
These were fixed for v5 in v5.0.1. This brings the same fixes to v4.
| CVE | nginx | Issue | F5 advisory |
|---|---|---|---|
| CVE-2026-15176 | Med | Buffer overflow when using alias with rewrite |
K000162416 |
| CVE-2026-93479 | Med | Buffer overflow in script evaluation (string expressions with variables) | K000162607 |
| CVE-2026-95101 | Med | Request body injection in HTTP/3 | K000162613 |
| CVE-2026-97629 | Med | Session forgery with TLS session ticket key rotation | K000162606 |
| CVE-2026-97205 | Med | Use-after-free in ngx_http_ssi_module with AIO |
K000162608 |
Only the fpm-nginx images include NGINX.
🐘 FrankenPHP build pinned
FrankenPHP stays on 1.12.6. Its build used to pull the latest Caddy and Caddy modules, which no longer build with FrankenPHP 1.12.6. Caddy is now pinned to 2.11.4, with Mercure, Vulcain, and Brotli on the versions FrankenPHP 1.12.6 ships with. This also keeps out the Caddy 2.11.6 HTTP/2 crash from #712.
How to update
Change v4.5.1 (or whichever v4 release you pin) to v4.5.3:
| Before | After |
|---|---|
8.4-fpm-nginx-v4.5.1 |
8.4-fpm-nginx-v4.5.3 |
8.4-fpm-nginx-alpine-v4.5.1 |
8.4-fpm-nginx-alpine-v4.5.3 |
8.3-cli-bookworm-v4.5.1 |
8.3-cli-bookworm-v4.5.3 |
Then confirm you're on the patched version:
docker compose exec php php -vPlan your move to v5
v4 will not get regular updates. This was a one-off because of how severe these CVEs are. The upgrade guide walks through the v5 changes.
Full Changelog: v4.5.1...v4.5.3
v4.5.2
Superseded by v4.5.3. No images were published for v4.5.2.
v5.0.1
🔐 Security update for NGINX users
Note
CVEs below are in nginx/nginx upstream, not in this repo. This release only bumps the NGINX we install.
Upstream CVEs (NGINX 1.30.6)
| CVE | nginx | Issue | F5 advisory |
|---|---|---|---|
| CVE-2026-15176 | Med | Buffer overflow when using alias with rewrite |
K000162416 |
| CVE-2026-93479 | Med | Buffer overflow in script evaluation (string expressions with variables) | K000162607 |
| CVE-2026-95101 | Med | Request body injection in HTTP/3 | K000162613 |
| CVE-2026-97629 | Med | Session forgery with TLS session ticket key rotation | K000162606 |
| CVE-2026-97205 | Med | Use-after-free in ngx_http_ssi_module with AIO |
K000162608 |
Fixed in 1.30.6+ stable (1.31.7+ mainline) per nginx.org advisories.
What's Changed
- Security: Update NGINX to 1.30.6 for Alpine and Debian by @jaydrogers
- Updated
SECURITY.mdwith clearer reporting instructions, including where to report vulnerabilities in PHP itself - Updated CI/CD Actions versions to latest versions
Full Changelog: v5.0.0...v5.0.1
v5.0.0
🤩 What's new
Short-lived & IP address certificates for FrankenPHP
FrankenPHP can now request Let's Encrypt's new short-lived (~6-day) certificate profile through the new `CADDY_ACME_PROFILE` variable. Short-lived certificates skip revocation entirely, and the same profile is what lets you issue certificates for raw IP addresses, which is handy for services that don't have a domain in front of them.
Configure short-lived & IP certificates →
Native Laravel Octane support with a hardened and optimized Caddyfile (#604)
Add --caddyfile=/etc/frankenphp/Caddyfile to octane:start and FrankenPHP switches into worker mode with the same trusted proxies, security headers, asset caching, SSL modes, and health check as classic mode. Octane's --workers, --watch, --max-requests, octane:status, and octane:reload all work. No wrapper script, no second Caddyfile.
command: ["php", "artisan", "octane:start", "--server=frankenphp", "--port=8080", "--caddyfile=/etc/frankenphp/Caddyfile"]Thanks to @aSeriousDeveloper for opening the original PR and coolio85 on Discord for the workaround that started it.
Expanded OPcache support and better defaults (#704)
OPcache is still off by default to save your hair when you're in development. When you set PHP_OPCACHE_ENABLE=1, you now get the settings from Symfony's performance guide instead of PHP's stock values: more memory, room for more files, and no filesystem checks per request. Preloading is available with PHP_OPCACHE_PRELOAD. This is a breaking change if you mount your code as a volume with OPcache enabled (see below).
Read the production performance tuning guide →
Trusted proxy support for every variation (#643)
TRUSTED_PROXY now works the same way across all variations (FrankenPHP, NGINX, and Apache), so your app sees the real client IP whether it's behind Cloudflare, a load balancer, or your own reverse proxy. The version in the v4.6.0 betas returned the wrong IP in multi-hop Docker setups (#703). That's fixed, and FrankenPHP's REMOTE_ADDR now matches what Caddy resolves. FrankenPHP users also get /etc/frankenphp/caddyfile-global.d/ for global Caddy config without escaping CADDY_GLOBAL_OPTIONS.
Native Laravel Nightwatch support (#572)
Added a native health check for Laravel Nightwatch, complete with documentation to wire it up.
Laravel Nightwatch with Docker →
CI improvements: Every image is tested before it's published (#700)
Build and publish used to be one step. Now every image is built for amd64 and arm64, pulled on native runners, and checked before anything ships: it starts, runs unprivileged, loads the default extensions, passes its health check, and serves PHP through its web server. If one image fails, nothing from that run is published. Read more about how this works in #700.
New environment variables
All of them are documented in the environment variable specification.
| Variable | Default | Applies to | What it does | Learn more |
|---|---|---|---|---|
TRUSTED_PROXY |
cloudflare |
fpm-nginx, fpm-apache, frankenphp | Which proxy IPs are trusted to pass the real client IP. Options: cloudflare, sucuri, local, or off. |
#643 |
CADDY_ACME_PROFILE |
off |
frankenphp | Select a Let's Encrypt certificate profile: shortlived (~6-day certs, required for IP-address certificates), tlsserver, classic, or off. |
Configuring SSL |
LARAVEL_OCTANE |
unset | frankenphp | Set by Octane, not by you. The Caddyfile uses it to switch into worker mode. | #604 |
AUTORUN_LARAVEL_SKIP_IF_NOT_FOUND |
false |
all | When true, Laravel automations exit gracefully if Laravel is not found in APP_BASE_DIR instead of failing the container. Requires AUTORUN_ENABLED=true. |
Laravel automations |
PHP_OPCACHE_ENABLE_CLI |
1 |
all | Whether CLI commands use OPcache when PHP_OPCACHE_ENABLE=1. 1 is what v4 did. Set to 0 to keep OPcache on for the web server only. |
#704 |
PHP_OPCACHE_PRELOAD |
"" |
all | Path to a preload script. | #704 |
PHP_OPCACHE_PRELOAD_USER |
"" |
all | The user to preload as when the container runs as root. | #704 |
PHP_FILE_UPLOADS |
On |
all | Whether to allow HTTP file uploads. Set to Off to harden apps that never accept uploads. |
#697 |
PHP_MAX_FILE_UPLOADS |
20 |
all | Maximum number of files that can be uploaded in a single request. | #697 |
PHP_DISABLE_FUNCTIONS |
"" |
all | Comma-separated list of PHP functions to disable, such as exec,shell_exec. Empty because Laravel, Composer, and Symfony Process rely on proc_open. |
#692 |
PHP_HTML_ERRORS |
On |
all | Format on-screen errors as HTML when PHP_DISPLAY_ERRORS is on. Never affects logged errors, and PHP forces it off for the CLI. |
#692 |
PHP_REALPATH_CACHE_SIZE |
4096K |
all | Size of PHP's realpath cache. Apps with a large vendor/ directory may benefit from raising it. |
#692 |
PHP_SESSION_COOKIE_HTTPONLY |
On |
all | Adds the HttpOnly flag to the native PHP session cookie so browser scripts cannot read it. See the note below. |
#692 |
Changed defaults
The PHP_OPCACHE_* values only apply when PHP_OPCACHE_ENABLE=1. CADDY_* applies to FrankenPHP only.
| Variable | v4 | v5 |
|---|---|---|
PHP_OPCACHE_VALIDATE_TIMESTAMPS |
1 |
0 |
PHP_OPCACHE_MEMORY_CONSUMPTION |
128 |
256 |
PHP_OPCACHE_INTERNED_STRINGS_BUFFER |
8 |
32 |
PHP_OPCACHE_MAX_ACCELERATED_FILES |
10000 |
32531 |
PHP_REALPATH_CACHE_TTL |
120 |
600 |
CADDY_LOG_OUTPUT |
stdout |
stderr |
CADDY_LOG_FORMAT |
console |
auto |
Add your own Caddyfile rules from a folder
FrankenPHP users can now drop a .caddyfile into /etc/frankenphp/caddyfile-server.d/ to add headers, redirects, or request matchers to their app's site. Before, the only way to do that was CADDY_SERVER_EXTRA_DIRECTIVES, which gets awkward past one line and is replaced when Laravel Octane starts FrankenPHP. The folder works in classic mode and with Octane.
# embed-cache.caddyfile
@embed path /embed.js
header @embed >Cache-Control "public, max-age=3600"volumes:
- ./embed-cache.caddyfile:/etc/frankenphp/caddyfile-server.d/embed-cache.caddyfileEach level of the Caddyfile now has an environment variable for one-liners and a folder for files:
| Level | Environment variable | Folder |
|---|---|---|
| Global options | CADDY_GLOBAL_OPTIONS |
/etc/frankenphp/caddyfile-global.d/ |
| Your app's site | CADDY_SERVER_EXTRA_DIRECTIVES |
/etc/frankenphp/caddyfile-server.d/ (new) |
| Extra sites | none | /etc/frankenphp/caddyfile.d/ |
Rules in caddyfile-server.d/ apply on every listener SSL_MODE creates, and Docker's health check goes through them too. If you add a catch-all rule like basic_auth, leave /healthcheck out of it. The docs show how.
Add your own Caddyfile rules →
⚠️ Breaking changes
The v4 → v5 migration guide walks through each of these with a checklist. To stay on v4 while you review, pin your tag to v4.5.1 (for example serversideup/php:8.4-fpm-nginx-v4.5.1).
Dropped PHP 7.4 and 8.0 (#699)
Debian 11 (Bullseye) LTS ended on 2026-08-31, and the following week Debian removed the Bullseye packages from its mirrors, so we can no longer install anything inside the build. PHP 7.4 and 8.0 only ever shipped on Bullseye and Alpine 3.16, which reached EOL in 2024, so both go with it. PHP 8.1 is EOL too, but stays built (pinned to 8.1.34) on bookworm, trixie, and alpine3.22 because those bases still receive security updates.
PHP 8.1 also drops alpine3.21. Alpine 3.21 is supported until 2026-11-01, but 8.1 is EOL and only needs one Alpine base, so 8.1 on Alpine now means alpine3.22. Change 8.1-*-alpine3.21 tags to 8.1-*-alpine3.22.
Your existing tags still work. Every 7.4, 8.0, and 8.1 alpine3.21 tag stays pullable, frozen at its last successful build. If you need to install a package on one while you migrate, the migration guide has an unsupported recipe.
Our supported matrix is now PHP 8.2 – 8.5 on bookworm, trixie, alpine3.23, and alpine3.24, plus PHP 8.1 on bookworm, trixie, and alpine3.22. SECURITY.md now states the rule: we build an image while its official base image exists and the operating system still receives security updates.