Troubleshooting
Start with phpray status (see CLI) — it names the first
thing that is wrong in every case below.
Extension not loading
php -m | grep phpray prints nothing, or php -v fails:
- Wrong PHP version — the
.soin the release is per version (php8.3-glibc-x86_64will not load under 8.2). Matchphp -vto the artifact name. - ZTS vs NTS — build with
--enable-shared(ZTS) needs the ZTS variant; most distro PHP is NTS.php -i | grep Threadtells you. - Wrong libc — musl (Alpine) vs glibc (everything else). Alpine
containers need the
muslartifact. - Check
error_log/dmesgfor the loader message (missing symbols → wrong version or build).
No traces for a site
Run phpray status and phpray trace -d <domain> -n 5; common causes in order:
- the URI is in
phpray.ignore_uris(or the site's.user.inisets it) phpray.mode=manualand no trigger is active (console/CLI) — switch tosmartor fire a profilephpray.enabled=0in a.user.ini/.htaccesscloser to the docroot (PERDIR directives overridephp.ini; the closest file wins)- the request was fast —
smartonly storesnormalat 200 ms and up; lowerphpray.smart_threshold_normalor useall phpray.profile_mode=off— traces still appear, just without the components table (expected, not a fault)
Collector not reading shm
phpray status shows "collector ok" but no data:
- the extension's
phpray.shm_path/phpray.shm_sizeand the collector's[input] shm_pathmust match exactly (different PHP versions with different ini includes are a classic split); with per-user rings (shm_path = /run/phpray/ring-%u) the collector needs the glob formshm_path = "/run/phpray/ring-*" output_mode=fileon one side,shmon the other- shm on a full
/dev/shm— checkdf -h /dev/shm - per-user rings appear only after the first traced request of that user
(
ls -la /run/phpray/showsring-<uid>files, mode0600)
Permission issues
/var/lib/phpraymust be owned by thephpraycollector user; if the extension writes files (output_mode=file|both), the PHP user (e.g.nobody,www-data, the per-user lsphp uid) must be able to write it — on multi-user boxes useshmmode so PHP never writes to the storage dir- a per-user ring (
%uinshm_path) is used only when the file is a regular file owned by that uid: a file or symlink another user planted under the name is ignored (phpray_ring_stats()returnsfalse); delete it as root
CloudLinux / CageFS
Symptom: the extension loads, phpray status says "collector ok", but no
traces arrive — and /dev/shm/phpray on the host stays empty (or the cage
creates its own copy). Cause: every CageFS user has a private, empty
/dev/shm (own mount namespace), so a ring under /dev/shm is invisible to
the collector, and a single world-writable ring would let customers read each
other's traces anyway. Use a shared directory and one ring per user:
# 1. shared runtime dir, sticky and world-writable (survives reboots via tmpfiles)
install -d -m 1777 -o root -g root /run/phpray
echo 'd /run/phpray 1777 root root -' > /etc/tmpfiles.d/phpray.conf
# 2. mount it into every cage: one line without prefix in cagefs.mp
grep -qx '/run/phpray' /etc/cagefs/cagefs.mp || echo '/run/phpray' >> /etc/cagefs/cagefs.mp
cagefsctl --force-update # server-wide: rebuilds the skeleton and remounts all users
# (cagefsctl --remount-all only remounts; CloudLinux documents it
# as the command to run after a cagefs.mp change — both are global)
Extension ini (every PHP version, including /opt/alt/php*):
extension=phpray.so
phpray.enabled = 0 ; opt-in: phpray.enabled=1 in the account's .user.ini
phpray.output_mode = shm
phpray.shm_path = /run/phpray/ring-%u ; one private 0600 ring per uid
phpray.shm_size = 2097152 ; per user — keep it small
phpray.control_path = /run/phpray/control ; shared, read-only for PHP
Collector (/etc/phpray/collector.toml, collector 0.15+ reads the glob):
[input]
shm_path = "/run/phpray/ring-*"
control_path = "/run/phpray/control"
systemctl restart phpray-collector.
Verify from inside a cage, as the customer:
su -s /bin/bash <user> -c 'ls -ld /run/phpray && php -r "var_dump(phpray_ring_stats());"'
# after the first traced request of that site:
ls -la /run/phpray/ # ring-<uid> -rw------- <user>
phpray status # collector: rings found
The installer does all of the above when it detects cagefsctl
(install.sh --cagefs forces it, --no-cagefs skips it), see
Shared hosting.
Two failure modes worth knowing
Both come from the systemd unit and both were seen on a live CloudLinux server.
Ring buffer … not usable yet: read-only file systemin the collector log. The unit runs withProtectSystem=strict, which makes/runread-only, so/run/phprayhas to be listed inReadWritePaths. Units from PHPRay 0.15 on include it.- PHP stops writing its ring after the collector restarts —
phpray_ring_stats()returnsfalseinside the cage while the directory looks perfectly fine from the host.RuntimeDirectory=phpraydeletes/run/phpraywhen the service stops; the cages keep a mount of the removed inode. Compare them:
stat -c %i /run/phpray # host
su -l <account> -s /bin/bash -c 'stat -c %i /run/phpray' # inside the cage
Different numbers mean a stale mount: cagefsctl --remount-all fixes it. Units
from PHPRay 0.15 on set RuntimeDirectoryPreserve=yes and RuntimeDirectoryMode=1777,
so the directory survives restarts; an older unit needs a drop-in:
# /etc/systemd/system/phpray-collector.service.d/cagefs.conf
[Service]
RuntimeDirectoryMode=1777
RuntimeDirectoryPreserve=yes
ReadWritePaths=-/run/phpray
Long-lived PHP workers keep the .so they loaded at start: after replacing the
extension, recycle that account's workers (pkill -u <account> -f lsphp) instead
of restarting the whole web server.
Collecting a bug report
phpray report -o phpray-report.txt
journalctl -u phpray-collector --since "10 min ago" >> phpray-report.txt
Send the report, your PHP version and distro, and the exact
php -i | grep -E "phpray|extension_dir" output.
docs-site/docs/troubleshooting.md · PHPRay 0.14The Polish version is a translation of the English original; parameters and code examples keep their original syntax.