Pre-built PHP embed SAPI static libraries (libphp.a / php8embed.lib) plus headers, for the OS/arch/PHP-version combinations ePHPm supports.
Built with static-php-cli — specifically the luthermonson/static-php-cli fork, pinned by SPC_VERSION in .github/workflows/build.yml. Every SDK is a spc build --build-embed output: a static libphp.a, never a shared libphp.so.
| Platform | Tarball suffix | Output |
|---|---|---|
| Linux x86_64 (glibc, floor 2.28) | -linux-x86_64-gnu |
lib/libphp.a + headers, ZTS |
| Linux aarch64 (glibc, floor 2.28) | -linux-aarch64-gnu |
lib/libphp.a + headers, ZTS |
| Linux x86_64 (musl, fully static) | -linux-x86_64 |
lib/libphp.a + headers, ZTS |
| Linux aarch64 (musl, fully static) | -linux-aarch64 |
lib/libphp.a + headers, ZTS |
| macOS aarch64 | -macos-aarch64 |
lib/libphp.a + headers, ZTS |
| Windows x86_64 | -windows-x86_64 |
lib/php8embed.lib + dep .libs + headers, ZTS |
| Linux x86_64 (glibc, NTS) | -linux-x86_64-gnu-nts |
lib/libphp.a + headers, non-thread-safe |
| Linux aarch64 (glibc, NTS) | -linux-aarch64-gnu-nts |
lib/libphp.a + headers, non-thread-safe |
| Linux x86_64 (glibc, NTS, shared embed) | -linux-x86_64-gnu-nts-shared |
lib/libphp.so (dependencies linked in) + headers, non-thread-safe |
| Linux aarch64 (glibc, NTS, shared embed) | -linux-aarch64-gnu-nts-shared |
lib/libphp.so (dependencies linked in) + headers, non-thread-safe |
| Windows x86_64 (experimental, clang-cl + TAILCALL VM) | -windows-x86_64-clang |
lib/php8embed.lib + dep .libs + headers, ZTS |
| Windows x86_64 (shared engine, official build) | -windows-x86_64-shared |
bin/php8ts.dll + lib/php8ts.lib + lib/php8embed.lib + headers + official bin/ext/php_*.dll, ZTS |
The glibc floor is 2.28 (built on AlmaLinux 8 with gcc-toolset-13), so a consumer binary linked against these runs on RHEL/Alma 8, Ubuntu 20.04+, Debian 10+, Amazon Linux 2023 and Fedora 40+.
ZTS is the default and is unsuffixed. ePHPm is a threaded server and links the ZTS SDK; renaming those assets breaks it. The -gnu-nts variants exist for consumers that cannot use a thread-safe build.
Detect which one you have from include/php/main/php_config.h: thread-safe builds define ZTS 1, non-thread-safe builds do not define ZTS at all.
NTS tarballs:
- are Linux-glibc only (x86_64 and aarch64),
- are not part of a
platform=allbuild and do not gate release completeness — a failed NTS build never blocks a release, - carry
ffiin addition to the shared extension set (see below), - are kept current only for the minors listed under
minors_ntsinversions.json.
The -gnu-nts-shared variants are the same NTS builds packaged as a shared
embed: lib/libphp.so with the dependency libraries linked in, for consumers
that must dlopen() or link -lphp against a shared libphp (e.g. PAM). The
static libphp.a variants stay the default for everything else.
-windows-x86_64-clang is an additive, explicitly-dispatched lane
(platform=windows-x86_64-clang) that builds PHP itself with clang-cl and
hand-defines HAVE_PRESERVE_NONE, so PHP 8.5+ selects the
ZEND_VM_KIND_TAILCALL interpreter instead of the slow ZEND_VM_KIND_CALL
every MSVC build gets. Dependencies stay MSVC-built; clang-cl is
MSVC-ABI-compatible and the resulting php8embed.lib links into MSVC-target
consumers unchanged. The build hard-fails unless the compiled VM kind is
verifiably TAILCALL.
Status: experimental, but a measured performance win with the pinned toolchain. On PHP 8.5.7 CLI (Ryzen 9 5950X, 2026-08, best-of-5 hrtime loops), LLVM 22.1.8 clang-cl + TAILCALL runs the dispatch-bound int loop 1.7x faster than the MSVC lane's CALL VM (8.4 ms vs 14.2 ms) and wins on string-append and function-call loops too; the mixed reference loop drops from 4.4 ms to 2.7 ms — Windows lands within ~12% of the same loop on Linux (HYBRID VM). Two sharp edges, which is why this stays experimental:
- The toolchain must be the pinned LLVM release. The VS-bundled
clang-cl 19.1.5 generates Zend-engine code ~45–90% slower than MSVC —
enough to swamp the VM-kind win and end up slower than the stock lane.
The workflow pins
LLVM_VERSIONand hard-fails on mismatch. - The build hard-fails unless the compiled VM kind is verifiably
TAILCALL (disassembled out of
php8embed.lib), so it can never silently regress to the CALL VM.
It is never part of platform=all, never gates a release, and must not be
moved into platforms_required until it has soaked as an opt-in lane.
macOS SDKs do not ship the HYBRID VM. The macOS build uses clang (Homebrew
LLVM), and clang defines __GNUC__ but fails PHP's HAVE_GCC_GLOBAL_REGS
configure check, so the fast ZEND_VM_KIND_HYBRID interpreter is never
selected. The result depends on the PHP minor:
| macOS PHP | Zend VM kind | Notes |
|---|---|---|
| 8.5+ | TAILCALL (5) |
clang ≥ 19 musttail + preserve_none; ~HYBRID speed |
| 8.4 | CALL (1) |
slowest VM — no TAILCALL path before 8.5 |
| 8.3 | CALL (1) |
slowest VM — no TAILCALL path before 8.5 |
HYBRID would require building PHP and its ~20 dependency libraries with real
GCC on aarch64-apple-darwin. That is not a supported static-php-cli
configuration: config/env.ini's [macos] section hardcodes CC=clang, and
the toolchain classes only set the SPC_LINUX_DEFAULT_* vars the macOS section
never reads — so SPC_TOOLCHAIN=GccNativeToolchain does not switch the macOS
compiler. Wiring that up (plus getting Homebrew GCC to pass
HAVE_GCC_GLOBAL_REGS for the aarch64 global registers and produce a working
JIT, which the build already had to abandon Apple clang for) is a large spc
change PHP upstream does not test on macOS. Until then: use macOS PHP 8.5 for
CPU-bound workloads. The build logs the selected VM kind (macOS "Record Zend
VM kind" step) and warns when a pre-8.5 macOS SDK lands on CALL.
The default Windows SDK links PHP statically, and a static PHP cannot load
extension DLLs: php_xdebug.dll and every other PHP extension import the
engine from php8ts.dll, which a static build does not have. The -shared
tarball is the alternative for consumers that need to load extensions (Xdebug
first of all).
It is not compiled here. .github/workflows/windows-shared.yml runs
tools/windows-shared/package.ps1, which repackages the official
windows.php.net ZTS x64 build of the same release (the zip already contains a
small static php8embed.lib whose objects import the engine through
php8ts.lib; the matching devel pack carries php8ts.lib and the headers).
Using the official engine means the official extension DLLs and xdebug.org's
Xdebug DLLs are ABI-matched to it by construction. Every download is
sha256-verified against tools/windows-shared/checksums.txt or the live
windows.php.net checksum list, and the lane refuses to package unless a smoke
test — the official php.exe run from a directory holding only the packaged
bin/ payload — loads every extension of the static SDK's set plus Xdebug.
include/php/ headers (devel pack)
lib/php8embed.lib embed SAPI, static, imports the engine
lib/php8ts.lib import library for php8ts.dll
bin/php8ts.dll the engine: ship it next to the consuming .exe
bin/*.dll engine + extension dependency DLLs (OpenSSL, ICU, ...)
bin/ext/php_*.dll official shared extensions
ephpm-sdk.json php_version, php_api_no, zts, crt, and `preload`
LICENSE-PHP.txt, readme-redist-bins.txt, sbom/
Extensions the static SDK compiles in but the official build ships as DLLs
(mbstring, openssl, curl, intl, pdo_mysql, ... and opcache on 8.3
and 8.4) are listed, in a dependency-safe load order, under preload in
ephpm-sdk.json; a consumer loads them at startup to get the same extension
set as the static SDK. The VS toolset is the one windows.php.net uses for the
minor (vs16 for 8.3, vs17 for 8.4+), recorded as crt.
The same run publishes ephpm-ext-<ver>-windows-x86_64.tar.gz to the
ext-<ver> release: php_xdebug.dll plus a manifest.json in the Linux
catalog's schema (with "libc": "msvc", "os": "windows").
gh workflow run windows-shared.yml -f php_version=8.5.7Older releases move to windows.php.net's archives/, which publishes no
checksum list — pin their hashes in checksums.txt (the script prints the
hash to pin when one is missing). A new Xdebug version needs its three
php_xdebug-<ver>-<minor>-ts-<vs>-x86_64.dll hashes from xdebug.org added
there too.
lib/ libphp.a (or php8embed.lib) + every static dep archive
include/php/
main/ includes php_config.h (ZTS marker) and php_version.h
Zend/
TSRM/
ext/
sapi/embed/php_embed.h
bin/php-config Linux and macOS only
THIRD-PARTY-NOTICES.txt
Assets are named:
php-sdk-{php_version}-{os}-{arch}[-gnu][-nts[-shared]][-clang|-shared].tar.gz
A relocatable POSIX-sh shim, generated by tools/mk-sdk-metadata.sh at package time. It resolves its own prefix at run time, so the tarball can be extracted anywhere. It exists because build scripts that expect a system PHP (php-config --includes) run it unconditionally.
Supported: --prefix, --includes, --include-dir, --ldflags, --libs, --extension-dir, --version, --vernum, --php-sapis, --help.
Deliberately refused, with a message and a non-zero exit, rather than answered with a plausible lie:
--php-binary— this SDK ships no PHP executable.--configure-options— static-php-cli strips the configure line from the build.
--libs enumerates the static archives in lib/. It is a starting point, not a verified link line: whole-archive and symbol-ordering decisions belong to the consumer. See ePHPm's crates/ephpm/build.rs for a working static link against these tarballs, including the -Wl,--export-dynamic that lets extension=-loaded .so files resolve zend_* symbols against the host binary.
No php-config is generated on Windows — it is a shell script and nothing in the MSVC toolchain consumes one.
Compiled into libphp.a on Linux and macOS:
bcmath, bz2, calendar, ctype, curl, dom, exif, fileinfo, filter, ftp, gd,
gettext, gmp, hash, iconv, intl, mbregex, mbstring, mysqli, mysqlnd, opcache,
openssl, pcntl, pcre, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, pgsql, phar,
posix, session, shmop, simplexml, soap, sockets, sodium, sqlite3, sysvmsg,
sysvsem, sysvshm, tokenizer, xml, xmlreader, xmlwriter, xsl, zip, zlib
The NTS variants additionally include ffi.
Windows drops the Unix-only entries (pcntl, posix, sysv*), plus gmp, pgsql and pdo_pgsql, which static-php-cli cannot build statically on Windows today. It also does not yet carry shmop or sockets — PHP supports both there, but neither has been validated through spc's Windows toolchain from this repo.
ffi is not in the shared extension set on purpose. static-php-cli's ext-ffi passes --enable-zend-signals, and extension arguments land after the target arguments on the configure line, so it would override the --disable-zend-signals that spc emits for ZTS builds — silently changing signal behavior in the SDK ePHPm links. NTS builds emit no --disable-zend-signals, so ffi rides along there.
Loadable shared ZTS extensions (igbinary, msgpack, apcu, redis, mongodb) are a separate artifact built by .github/workflows/extensions.yml and published under ext-<version> tags. That catalog is ZTS-only by design: Debian and Sury publish no ZTS builds, which is the gap it fills. There is no NTS catalog. Windows has a catalog entry for Xdebug only (ephpm-ext-<ver>-windows-x86_64.tar.gz, from windows-shared.yml); it loads only into a binary linked against the -windows-x86_64-shared SDK.
# full release
gh workflow run build.yml -f php_version=8.5.2
# one platform (uploads to the existing release, leaves other assets alone)
gh workflow run build.yml -f php_version=8.5.2 -f platform=linux-aarch64-gnu
# non-thread-safe variant
gh workflow run build.yml -f php_version=8.4.23 -f platform=linux-x86_64-gnu-ntsstatic-php-cli resolves php-src from php.net's GA-only release feed, and
its --with-php accepts only numeric x.y/x.y.z. So a beta/RC cannot go
through the normal path. Set php_src_url to the upstream pre-release tarball
(the release manager's QA dir, downloads.php.net/~<RM>/); every OS leg then
fetches php-src from that URL and hands it to spc via --custom-url/
--custom-local, while --with-php gets the numeric major.minor. The
resulting release tag (v<php_version>) is marked prerelease.
gh workflow run build.yml \
-f php_version=8.6.0beta1 -f platform=all \
-f php_src_url=https://downloads.php.net/~svpernova09/php-8.6.0beta1.tar.gzBetas are not added to versions.json minors (watch-php.yml polls the GA
feed and would error on a non-GA minor) and never gate a stable release. This
lane is experimental: no spc release has explicit 8.6 support — it builds
because spc has no upper-version cap and reads the real PHP_VERSION_ID from
the supplied source. The Windows embed leg for a brand-new beta is unproven.
Any dispatch from a branch other than main MUST set release_tag_suffix. On 2026-07-13 a validation build from a side branch uploaded to the plain v8.5.7 tag and overwrote four production assets. The input exists to make that impossible:
gh workflow run build.yml --ref my-branch \
-f php_version=8.5.7 -f platform=linux-x86_64-gnu -f release_tag_suffix=-mytestwatch-php.yml polls php.net every 6 hours, mirrors new php-src tarballs to the php-sources release tag, and dispatches builds for anything missing.
Builds do not fetch PHP or its GNU dependencies from upstream. Both come from this repo's own php-sources release tag, fetched with plain curl and handed to static-php-cli as file:// custom URLs.
Asset on php-sources |
Populated by | Consumed by |
|---|---|---|
php-X.Y.Z.tar.gz |
watch-php.yml |
build.yml, extensions.yml |
libiconv-*, gettext-*, ncurses-*, libunistring-*, libidn2-* |
mirror-deps.yml |
build.yml, extensions.yml |
gnu-deps-manifest.txt, SHA256SUMS |
mirror-deps.yml |
build.yml, extensions.yml |
Those libraries resolve through static-php-cli type: filelist sources pointed at https://ftpmirror.gnu.org/gnu/<pkg>/. That host is a redirector to volunteer backend mirrors and returns HTTP errors when it picks a bad one. None of the five has a source-mirror fallback in spc, so a single bad backend fails the whole spc download and therefore the build. That took every scheduled build in this repo down from 2026-08-06 onward.
gmp is also GNU-hosted but is not in the mirror: it is the one GNU dependency with its own source-mirror fallback in spc, so mirror-deps.yml resolved it from there and correctly did not select it. The mirrored set is whatever spc actually resolved from ftpmirror.gnu.org, not a fixed list.
Refresh the mirror when you want newer dependency versions:
gh workflow run mirror-deps.yml # resolve + upload
gh workflow run mirror-deps.yml -f dry_run=true # resolve + print onlymirror-deps.yml runs on a GitHub-hosted runner (different egress from the self-hosted fleet) but resolves inside the same almalinux:8 container with the same SPC_TARGET, so it picks exactly the artifacts the fleet would have. The set is derived from spc's own downloads/.cache.json — every artifact whose source URL is on ftpmirror.gnu.org.
Note the tradeoff: filelist sources normally track the newest tarball in the directory index on every build. Once mirrored, builds pin to whatever was current when mirror-deps.yml last ran. That is the same tradeoff php-src already makes, and it stops builds drifting underneath us.
If the manifest is missing from the release tag, builds log a warning and fall back to fetching from upstream GNU mirrors — the pre-mirror behaviour, and the known failure mode.
ePHPm's release workflow:
- name: Download PHP SDK
run: |
gh release download v${{ matrix.php }} \
--repo ephpm/php-sdk \
--pattern "php-sdk-*-${{ matrix.os }}-${{ matrix.arch }}.tar.gz" \
--output php-sdk.tar.gz
mkdir -p php-sdk && tar xzf php-sdk.tar.gz -C php-sdkthen builds with PHP_SDK_PATH=php-sdk cargo build --release.
The MIT license in this repository covers the workflows and scripts here. It does not cover the contents of the published tarballs: those are an aggregate of PHP (PHP License 3.01 / Zend Engine License 2.00) and roughly twenty upstream libraries, each under its own terms — including a bundled libstdc++.a carrying the GCC Runtime Library Exception.
Every tarball ships a THIRD-PARTY-NOTICES.txt listing the components, their SPDX identifiers, and where to get the full license texts. Read it before redistributing anything built from this SDK.
The -windows-x86_64-shared tarball is the exception: it is the official windows.php.net binary distribution repackaged unmodified, so it carries that distribution's own notices instead — LICENSE-PHP.txt (PHP's license.txt), readme-redist-bins.txt (the bundled third-party DLLs and their licenses) and the upstream SBOM under sbom/. Xdebug (ephpm-ext-<ver>-windows-x86_64.tar.gz) is under the Xdebug License.