Panellicense

Add custom binaries to the CageFS skeleton on CloudLinux

Expose Composer, WP-CLI, Node tools, or any custom binary inside CageFS so end users can run them over SSH or cron without leaking the rest of the host.

8 min readUpdated 2026-05-17cagefs · cloudlinux · cpanel · wp-cli
schema: HowToschema: FAQPageschema: BreadcrumbList

CageFS hides almost everything in /usr/bin, /usr/sbin, and /opt from end users by default — and that's the point. The cage is a per-user virtual filesystem with a curated copy of system binaries, libraries, and config. Anything not in the skeleton doesn't exist as far as the user is concerned, even if it's installed on the host.

The flip side is that every site builder eventually hits a missing tool. wp is on the host but the user gets command not found over SSH. Composer works as root but cron jobs fail. A custom Bash script the reseller wants their clients to call returns nothing. This guide walks through the right way to expose a binary inside the cage — via a CageFS configuration file, not by hand-symlinking inside the skeleton.

How the skeleton works

CageFS builds a single template directory (/usr/share/cagefs-skeleton by default) by reading every .cfg file under /etc/cagefs/conf.d/. Each user's cage is a bind-mounted view of that skeleton, plus their own home directory. Anything you want visible to users has to be declared in a config file and the skeleton has to be rebuilt — manually copying a binary into the skeleton works once and then gets wiped on the next cagefsctl --force-update.

The two directives that matter:

  • paths= — file paths to expose inside the cage. The file is copied (or symlinked, depending on type) into the skeleton.
  • comments= — free-text description shown in cagefsctl --list-applied-configs.

That's almost the whole API. The complexity is in knowing which paths a given binary actually needs.

Step 1 — Find every file the binary depends on

A binary is rarely self-contained. Composer needs PHP, WP-CLI needs PHP plus a wrapper script, a Node CLI needs Node plus a shebang and the script. Before writing the config, trace what the binary actually opens:

ldd $(which composer)

For a PHP-based tool, ldd returns "not a dynamic executable" because the file is a Phar script. Read the shebang instead:

head -n 1 /usr/local/bin/composer

If the shebang points to /usr/bin/php, that interpreter has to be in the cage too — which it already is on a CloudLinux cPanel server, since alt-php is a standard skeleton entry. If your tool's shebang points to a custom Node or Python path under /opt, you'll need to expose the interpreter in the same config file.

For native binaries, follow the ldd output. Every .so listed has to either already be in the skeleton or be explicitly included.

Step 2 — Write a CageFS config file

Create one file per logical tool under /etc/cagefs/conf.d/. The filename is arbitrary but the convention is <toolname>.cfg:

cat > /etc/cagefs/conf.d/composer.cfg <<'EOF'
[composer]
comments=Composer dependency manager for PHP
paths=/usr/local/bin/composer
EOF

If the binary lives somewhere unusual, list every path it needs explicitly:

cat > /etc/cagefs/conf.d/custom-tools.cfg <<'EOF'
[custom-tools]
comments=In-house helper scripts and dependencies
paths=/usr/local/bin/site-deploy, /usr/local/bin/site-rollback, /opt/in-house/lib/, /opt/in-house/bin/
EOF

A few rules the parser cares about:

  • The section name in square brackets has to match the filename stem (composer.cfg[composer]). Mismatches are silently ignored.
  • Multiple paths go on one line, comma-separated. Trailing commas break the parse.
  • A path ending in / is treated as a directory and recursed. Without the slash it's a single file.

Step 3 — Rebuild the skeleton

The config file doesn't take effect until the skeleton is regenerated:

cagefsctl --force-update

This takes 30 seconds to 2 minutes depending on disk speed and skeleton size. It does not affect logged-in user sessions — those keep their existing bind mount until the next session.

If you only changed one config file and the full rebuild is too slow, update just that one:

cagefsctl --addrpm composer
cagefsctl --force-update-etc

--addrpm here is a misnomer — it works for any .cfg file, not just RPM-backed ones.

Step 4 — Verify the binary is visible

Test as a real user, not as root. Root bypasses the cage:

su - someuser -c "which wp; wp --version"

If which returns the path but the command fails with a library or interpreter error, the binary made it into the cage but one of its dependencies didn't. Re-run ldd and add the missing libraries to the config.

If which returns nothing, the skeleton wasn't rebuilt — re-run cagefsctl --force-update.

Common cases

WP-CLI

WP-CLI is a Phar that ships under /usr/local/bin/wp with a #!/usr/bin/env php shebang. Since alt-php is already exposed, the only path to add is the wrapper itself:

[wp-cli]
comments=WordPress CLI
paths=/usr/local/bin/wp

If your users SSH in and wp runs the system PHP instead of their selected alt-php version, that's a PATH issue, not a cage issue. See setting up the PHP Selector for how the per-user PHP wrapping works — the same wrapping makes wp pick up the user's selected version automatically when called via the Selector-aware php.

Node CLI tools (Yarn, pnpm, npx)

If you're using CloudLinux's Node.js Selector, the per-user Node binaries live under /opt/alt/alt-nodejs*/root/usr/bin/ and are exposed automatically. System-wide tools like a globally installed yarn at /usr/bin/yarn need to be added explicitly:

[yarn]
comments=Yarn package manager
paths=/usr/bin/yarn, /usr/lib/node_modules/yarn/

The node_modules directory is the critical part — without it, yarn runs but immediately errors on missing internal modules.

Git

Modern CloudLinux skeletons already include /usr/bin/git, but git lfs, git-crypt, and other extension binaries are not. Expose them as separate paths:

[git-extras]
comments=Git LFS and extensions
paths=/usr/bin/git-lfs, /usr/libexec/git-core/git-lfs

Removing an exposed binary

Delete the config file and rebuild:

rm /etc/cagefs/conf.d/custom-tools.cfg
cagefsctl --force-update

The skeleton is fully rebuilt from the surviving .cfg files, so removed paths disappear. You can also exclude a specific user from CageFS entirely with cagefsctl --disable <user>, but that's a sledgehammer — it turns off all isolation for that account, not just the missing binary.

Don't edit the skeleton directly

It's tempting to cp a binary straight into /usr/share/cagefs-skeleton/usr/local/bin/ and call it done. Two reasons not to:

  1. The next cagefsctl --force-update (triggered by any RPM transaction or CloudLinux update) wipes manual additions.
  2. CageFS hashes the skeleton against the declared configs; out-of-band files cause integrity warnings in cagefsctl --check runs.

If you find yourself reaching for a manual copy, write the .cfg file instead — it's the same two lines and survives upgrades.

Verifying the full config set

To see every config currently applied:

cagefsctl --list-applied-configs

And to validate that the live skeleton matches what the configs declare:

cagefsctl --check

A clean run prints nothing. Any output is a discrepancy worth investigating before the next CloudLinux update.

For broader CageFS troubleshooting, see fixing 'Cannot manage PHP versions' when CageFS is disabled — many "binary missing" symptoms turn out to be CageFS being off for the user, not a skeleton gap.

FAQ

Why is my custom binary missing inside CageFS?+
By default CageFS only exposes a curated subset of system binaries. Anything you've installed under /usr/local/bin or /opt is invisible until you declare it in a .cfg file under /etc/cagefs/conf.d/ and rebuild the skeleton with cagefsctl --force-update.
Do I need to restart anything after adding a CageFS config?+
No service restart is needed. Run cagefsctl --force-update to rebuild the skeleton. Existing user sessions keep their old view until they reconnect; new SSH sessions and cron runs pick up the change immediately.
Will my CageFS config survive a CloudLinux update?+
Yes — files under /etc/cagefs/conf.d/ are user-owned configuration and are not touched by package updates. The skeleton itself is rebuilt from those configs after every update, so your custom paths reappear automatically.
How do I add a binary that's a shell script?+
Same as any other file — add its path to a .cfg file. Make sure the shebang interpreter (often /bin/bash, already in the skeleton) and any commands the script calls are also exposed. ldd doesn't help for scripts; trace the calls manually.
Can I expose a binary to one user but not all users?+
Not via the skeleton — it's global. For per-user binaries, install them under the user's home directory; everything under ~/ is automatically visible to that user's cage. Cross-user sharing has to go through the global skeleton.
Why does WP-CLI find PHP but fail to run inside CageFS?+
Almost always a library or extension the user's selected alt-php version is missing. Run wp --info as that user to see which PHP binary it's calling, then check whether the extension it needs is enabled in PHP Selector for that account.

Next steps

Switch in an afternoon

Switch from your current reseller — free.

We migrate active cPanel, Plesk, LiteSpeed and CloudLinux licenses from any reseller. We prorate the first month so you never pay twice, and your customers see zero downtime during the swap.