PHP versions
The versions this build pins. A patch can move without a rexenv release — see Patch updates — but only upwards, and only when you press the button.
| Version | Xdebug | PostgreSQL | Built by | Status |
|---|---|---|---|---|
8.5.8 |
✅ 3.5.3 | ✅ | rexenv | Current |
8.4.23 |
✅ 3.5.3 | ✅ | rexenv | Current |
8.3.32 |
✅ 3.5.3 | ✅ | rexenv | Current |
8.2.32 |
✅ 3.5.3 | ✅ | rexenv | Current |
8.1.34 |
✅ 3.5.3 | ✅ | rexenv | Past its php.net security-end date |
8.0.30 |
❌ | ❌ | static-php.dev | Past its php.net security-end date |
7.4.33 |
❌ | ❌ | rexenv | Past its php.net security-end date |
The PostgreSQL column is about the pinned build, not the minor: it means a working pdo_pgsql,
which only rexenv’s own 8.x builds carry. See
rexenv builds its own PHP.
Install and switch:
rex php list # installed, default, pool port, and the NOTE columnrex php install 8.1rex php default 8.3 # for new sitesrex site php myshop.rex 8.1 # for one existing sitePatch updates are a button, never a background download
Section titled “Patch updates are a button, never a background download”8.3.32 is what this build pins. When PHP ships 8.3.33, rexenv can install it without
waiting for a rexenv release — from Settings → PHP versions, one button, one minor at a
time.
What the row can say, and each is a different fact:
| The row says | Means |
|---|---|
8.3.32 |
The patch this minor is set to — the pin, or a newer one you chose |
serving 8.3.31 |
The running pool is still on the old build. It moves on the next start |
8.3.34 exists |
php.net lists it. rexenv may have no verified build of it yet |
[Update to 8.3.33] |
A signed manifest offers that patch, and this minor is installed |
costs PostgreSQL |
The offered build has no PostgreSQL driver — see below |
8.3.34 exists and Update to 8.3.33 are not the same claim, and the gap between them is
real. The first comes from php.net; the second needs a build rexenv can verify, and in the
app’s own words those “are published some weeks after php.net announces a release — so a
version can exist upstream with nothing here to install yet.” So the row says a version
exists where it cannot honestly say update available, and says nothing at all about being
“up to date”, which is unprovable before the first successful check.
The php.net check is read-only by construction. It fetches one document and derives one string per minor. It never reads a URL and never reads a digest, so nothing it says can make the app run anything.
What an update does to your sites
Section titled “What an update does to your sites”Structurally, nothing — with the one exception in the next section. A site stores a minor —
8.3 — never a patch. No config is regenerated, no certificate is touched, no edge reload
happens.
What does happen is a pool restart for that one minor, and the button says so before you press it. Then:
- The download runs first, before anything is written. A failed or cancelled download leaves the minor exactly where it was.
- If the pool does not come back, the selection returns to the previous patch and the pool is restarted onto it — a bad patch costs a restart, not a broken stack.
- If the revert also fails, the message says so plainly, because that is the state where PHP is down: “…and putting it back on 8.3.32 ALSO failed — the pool is DOWN.”
- If nothing was running for that minor, the row says
PHP 8.3 will use 8.3.33 — nothing was running to restartrather than naming a process that does not exist.
The downloaded tree is kept either way. It is verified and valid, and deleting it would make a retry re-fetch ~100MB.
An update can cost PostgreSQL
Section titled “An update can cost PostgreSQL”A signed manifest can offer rexenv’s own build of a patch, or upstream’s static-php.dev build.
Only rexenv’s carry a working pdo_pgsql. When the offered build would take the driver away,
the row carries a costs PostgreSQL chip, and the button’s explanation opens with what it
costs — naming the sites:
PHP 8.3.33 is upstream’s build and cannot reach PostgreSQL. api.rex uses a PostgreSQL database and would stop reaching it.
With no PostgreSQL site on that minor, the sentence says instead that PostgreSQL will stop being offered for new sites on that version.
Why this is safe to have at all
Section titled “Why this is safe to have at all”Every binary rexenv runs is checksum-pinned into the app, and that pin is what makes compromising a download host unable to reach an installed user. An in-app update needs a digest for bytes the app was built before, so the anchor moves — and it moves exactly one step: from a constant in the binary to a signed document whose public key is a constant in the binary.
Five limits, each a construction rather than a note:
- Only
php,php-fpm,php-licensesandadminermay be named. Nevercaddy— that one is a root LaunchDaemon. An entry naming anything else is dropped, not fatal, so one bad row cannot deny you every other update.php-licensesis on the list because the manifest can carry rexenv’s own builds, and a build rexenv distributes ships with its licences — one whose licences cannot be named is refused rather than installed without them. https://only, from hosts rexenv already downloads from —dl.static-php.dev,github.com/rexenv/, and Adminer’s own release path.- Re-verified on every read. The cached document lives in a file you can write; a “verified” flag in it would not be verification, so the signature is checked again each time, never remembered.
- A patch of a minor this build already ships. Not new minors:
eol_sinceand Xdebug support are compile-time facts, so a runtime-delivered 8.6 would render with no EOL date and Xdebug silently missing. - The pin is the floor. A manifest can only move you above the version your app ships, never below it — a comparison, not a flag, so with no manifest the answer is today’s pin byte for byte.
An older manifest is refused as a replay, because holding a machine on a superseded patch is what a compromised host would try. Offline, everything the row says about your installed patch is local and still true.
EOL versions are labelled, not hidden
Section titled “EOL versions are labelled, not hidden”7.4, 8.0 and 8.1 are past their upstream security-end dates. rexenv ships them anyway — plenty of real work happens on legacy codebases, and a local environment that refuses to run what your client runs is not useful.
What it does instead is badge them: in Settings, in the create dialog, and on the site’s own Environment card. Choosing one stays a decision rather than becoming an accident.
The badge is computed against today’s date and each version’s published php.net security-end date, not read from a list somebody remembered to update. A version crossing its date starts saying so on its own, which is the only version of this feature that stays true.
rexenv builds its own PHP
Section titled “rexenv builds its own PHP”Every pinned PHP except 8.0.30 is rexenv’s own build, published from the public
rexenv/runtimes repository and pinned to an immutable
release, so a pin can 404 but can never silently change under you.
The 8.1–8.5 builds used to be static-php.dev’s, and they had a hole rather than a gap: they
ship pgsql and no pdo_pgsql, while their PDO advertises pgsql anyway. A PDO connection
was accepted and then stalled for about a minute until PostgreSQL closed it with
SQLSTATE[08006] — and Laravel’s pgsql driver is exactly that call. php -m and
PDO::getAvailableDrivers() both reported support the build did not have, so rexenv’s builds
are checked with a real connection, not a module list.
Parity with the builds they replaced is checked three ways, because each layer missed something
the next one caught: module names; function names — an mbstring of the same name was 16
functions short, and every artisan command died on mb_split behind a perfect module list;
and the configure flags read out of both binaries.
One difference is deliberate. static-php-cli’s Swoole PostgreSQL hook and the real pdo_pgsql
are mutually exclusive — upstream’s driver only works inside a Swoole coroutine, which is why
it advertises pgsql and then hangs. rexenv ships the real driver and loses Swoole’s PDO
PostgreSQL and SQLite coroutine hooks.
8.0.30 is still static-php.dev’s. rexenv’s build of it passes every check on Apple
Silicon and aborts on Intel inside the build toolchain’s own sanity check, reproducibly and
unexplained, and 8.0 is past its security end. So 8.0 has no PostgreSQL driver.
Because rexenv builds these, it is also their distributor — so the licence texts (PHP License 3.01 and every statically linked dependency) install beside each interpreter rather than being left as somebody else’s paperwork.
PHP 7.4 is rexenv’s own build
Section titled “PHP 7.4 is rexenv’s own build”7.4 was the first: static-php.dev publishes no 7.4 at all. It is a first-class version, not a
compatibility shim: same picker, same pool model, its own php-fpm pool on 9774. 60
extensions, including gd, intl, mysqli and redis.
Four differences from the 8.x rows, all structural rather than oversights:
- No Xdebug (see below).
- No PostgreSQL. Its build has no working
pdo_pgsql, so a PostgreSQL site is refused on it. See PostgreSQL. - No OPcache. The static-build toolchain’s OPcache patches start at 8.0, so there is
nothing to enable. Stated here rather than left to be discovered from a
phpinfo(). - PCRE JIT is compiled out. The bundled PCRE2 predates Apple Silicon, and with JIT enabled
Composer died on
Allocation of JIT memory failed. Regex throughput on 7.4 is therefore lower than on the 8.x versions. A working 7.4 that is slower at regex beats a fast one that cannot run Composer.
A 7.4 site also cannot use the FrankenPHP override, which embeds PHP 8.5 and is refused rather than allowed to serve the site with the wrong runtime. See Web servers.
WordPress will show its own “outdated PHP” notice on a 7.4 site. rexenv warns you first — in the create dialog and on the site’s Environment card.
Why 7.4 and 8.0 get no Xdebug
Section titled “Why 7.4 and 8.0 get no Xdebug”Xdebug is a shared object that has to dlopen into the running PHP. Those two static builds
do not export the Zend symbols it needs — measured, not assumed: rexenv’s 7.4 exports around
22,400 symbols and _OnUpdateBool is not among them.
No external .so can load into them, so the per-site Xdebug toggle is not offered there
rather than being offered and then failing. See Xdebug.
One pool per version, not per site
Section titled “One pool per version, not per site”rexenv runs one php-fpm pool per installed PHP version, shared by every site using it — not one pool per site. Ten sites on 8.3 are ten server blocks in front of one pool.
That is why switching a site’s PHP version is instant and costs nothing: it re-points the site’s server block at a different pool. Nothing is rebuilt, and the docroot, certificate and database are untouched.