Every shared hosting operator knows the failure mode. One customer updates a plugin, their WordPress starts allocating 400 MB per request, and the other 300 accounts on the box slow to a crawl. CloudLinux exists for exactly one reason: to turn that into a one-account problem instead of a whole-server problem.
This post covers what LVE actually is, which limit fires when, what a 508 is really telling you, and where CageFS, PHP Selector and MySQL Governor sit in the picture.
What LVE actually is
LVE — Lightweight Virtual Environment — is a kernel-level metering and throttling layer, one per hosting account. It is not a VM and not a chroot. Think of it as a wrapper that counts processes, slices CPU, and either slows down or refuses work when a ceiling is hit.
An LVE's ID is the account's UID. cat /proc/lve/list shows live state; limits live in /etc/container/ve.cfg.
The thing that actually places an incoming request into the right LVE is a web-server-side component: mod_hostinglimits on Apache, the server's own built-in CloudLinux support on LiteSpeed. The request is already inside the LVE before PHP starts executing, which is why limit enforcement happens before PHP's own error handling ever gets a chance.
The detail people miss most often: an LVE is per account, not per site. A customer hosting 30 domains under a single cPanel user shares one set of limits across all 30.
The six limits, and which one bites
- SPEED — CPU share.
100%means one full core,200%means two. When it fills up, requests are not rejected, they are throttled. So a CPU ceiling never produces a 508; it produces timeouts and "the site is really slow" tickets. - PMEM — physical memory. Exceed it and the next allocation fails, the process dies, and the visitor typically sees a 500 or 503.
- EP (Entry Processes) — how many requests are inside the LVE at the same time. In practice: concurrent PHP requests. This is the limit that produces a 508.
- NPROC — total processes owned by the user. When it is full, nothing new can spawn:
fork: Cannot allocate memoryorResource temporarily unavailableover SSH and cron, and 500/503 on the web side. NPROC does not produce a 508 — that is the common mix-up. - IO — disk throughput in KB/s.
- IOPS — disk operations per second. If IO has headroom but IOPS is saturated, the symptom looks like "the disk is slow" when the real issue is a workload made of thousands of tiny files.
There is also VMEM (virtual memory) still sitting in the config, but it is deprecated — CloudLinux's own advice is to leave it at 0 and cap memory with PMEM only. The reason is simple: modern runtimes reserve large virtual regions they never touch, so measuring against VMEM gives misleading results.
Reading a 508 correctly
508 is not an undefined status code — RFC 5842 defines it as "Loop Detected" for WebDAV. CloudLinux repurposes it, and the "508 Resource Limit Is Reached" page it serves means exactly one thing: when the request arrived, the account's EP limit was full, so the request was not even queued.
The usual misreading is "traffic went up." EP is concurrency, not traffic. The relationship is simple:
concurrent requests = requests per second × average request duration
With EP=20:
- Pages averaging 0.2 s: nothing happens until roughly 100 requests/second.
- Pages averaging 2 s: 10 requests/second is enough to start throwing 508s.
Same traffic, tenfold difference in outcome. So a 508 is usually a duration problem, not a volume problem. Raising EP hides the symptom and raises total server load; cutting page duration (opcode cache, page cache, fixing the query) removes the problem.
To find out who is hitting what, and when:
lveinfo --period=1d --display-username— last 24 hours, by username instead of LVE IDlveinfo --period=7d --display-username --by-fault=any --limit=20— the 20 worst offenders (--limitdefaults to 10, so set it explicitly)- WHM > CloudLinux LVE Manager > Users — the same data with coloured bars
It is also worth turning on end-user notifications in LVE Manager. Once a customer can see their own fault history in cPanel's Resource Usage screen, a good share of your tickets stop being "your server is broken" and become "I removed that plugin."
lve-stats data defaults to SQLite at /var/lve/lvestats2.db. On busy boxes that file grows until lvestats itself becomes a visible IO consumer — that is the point to move the statistics database to MySQL.
Setting limits by measurement, not by guess
Set limits per package first, then carve out per-user exceptions.
lvectl package-set starter --speed=100% --pmem=1G --ep=20 --nproc=100 --io=4096 --iops=1024— the package's limitslvectl apply all— pushes the settings in/etc/container/ve.cfginto every running LVElvectl set-user username --speed=200% --pmem=2G --ep=30— an exception for one accountlvectl list— dump current limits
Do not forget lvectl apply all after changing a package. Once it runs, the new limits are live immediately and nothing needs restarting.
A reasonable starting set for a mid-density, WordPress-heavy box: speed 100%, pmem 1G, ep 20, nproc 100, io 4096 KB/s, iops 1024. Then watch lveinfo for a week and tighten or loosen based on the actual fault distribution rather than a hunch.
Three mistakes that show up constantly:
- Leaving IO unbounded. Clamp CPU and memory but leave IO open, and one nightly backup script will pin the disk while LVE does nothing about it.
- Setting NPROC too close to EP. NPROC counts cron jobs, SSH sessions and background processes too. EP=20 with NPROC=25 walls users off for no good reason; something around 100 is far healthier.
- One package for everyone, then never revisiting it. Putting a busy store and a static brochure site in the same package means unnecessary tickets for one and wasted headroom for the other.
CageFS: locking each user into their own filesystem
CageFS gives every user a virtualised, isolated filesystem. Reading /etc/passwd shows only their own line plus a handful of system accounts. Running ps aux shows only their own processes. There is no path at all to another account's home directory.
The practical payoff is that the classic shared-hosting symlink attack — reading someone else's wp-config.php from your own account — is closed. Users only get the binaries you explicitly expose, and dangerous SUID tools simply are not there.
cagefsctl --enable-all— enable for every user except those on the disabled listcagefsctl --list-disabled— who is outside the cagecagefsctl --disable username— exclude one accountcagefsctl --update— refresh the skeleton (mandatory after installing packages)cagefsctl --force-update— rebuild without waiting for the update period, slowcagefsctl --remount-all— remount the skeleton and every user
Installed something system-wide and users cannot see it? Nine times out of ten the answer is that cagefsctl --update was never run. To expose your own tool, drop /etc/cagefs/conf.d/mytool.cfg containing [mytool], comment=My Tool and paths=/usr/local/bin/mytool, then run cagefsctl --force-update.
There is a cost. The skeleton eats several GB of disk, --force-update takes minutes on a busy server, and because every user gets their own /tmp, support staff occasionally insist a file "isn't there" while looking at the wrong namespace.
PHP Selector, and three constraints worth settling first
PHP Selector lets users pick their own PHP version and extensions from their control panel. The alt-php packages span old 5.x releases up through 8.x, each version shipping as its own RPM set.
selectorctl --list— installed versionsselectorctl --summary— who is on whatselectorctl --set-user-current=8.2 --user=usernameselectorctl --list-extensions --version=8.2
Three things to settle before you deploy it:
- CageFS is required. PHP Selector does not work for a user who is outside CageFS. The two are not independent features.
- It does not coexist with PHP-FPM on cPanel. If an account has PHP-FPM enabled, Selector is bypassed for that account and it runs the EA-PHP version instead. This is where most people get stuck, and it forces a choice: FPM's performance profile, or letting users pick their own version.
- MultiPHP Manager outranks it. The server's System PHP Version has to be an ea-php build — pointing it at alt-php breaks Selector — and any domain that should use Selector must be left on "inherit" in MultiPHP Manager, otherwise the panel's choice overrides the user's.
On the LiteSpeed side, mod_lsapi works cleanly with alt-php and gives a comparable concurrency profile without needing FPM at all. That is why most people running CloudLinux alongside a LiteSpeed license end up staying on that combination.
MySQL Governor: the blind spot LVE leaves
LVE does not measure database load, because the query executes inside mysqld, not inside the user's LVE. A single badly written JOIN can burn the server's CPU while LVE sees nothing at all — that account's CPU graph looks perfectly clean.
MySQL Governor closes that gap. It tracks per-user database CPU and read/write volume, and restricts accounts that cross the threshold.
Installing it replaces MySQL/MariaDB with CloudLinux's own build: /usr/share/lve/dbgovernor/mysqlgovernor.py --install. That needs a maintenance window, since the database restarts. CloudLinux's builds can also trail upstream releases somewhat, so if you need a minor release that shipped this week, plan around it.
dbctl list— per-user limits and current usagedbctl set username --cpu=150,100,70,50 --read=2048,1500,1000,800— four values, one per successive restriction leveldbctl restrict username/dbctl unrestrict username— restrict or release by handdbctl list-restricted— who is restricted right nowdbctl ignore username— keep an account out of restriction entirelydbtop— live per-user MySQL usage
Configuration lives in /etc/container/mysql-governor.xml. The use value on the lve element decides how a restricted user is handled, and the level1 to level4 values on the restrict element set how long each of the four successive restriction levels lasts (60 seconds, 15 minutes, 1 hour and 1 day in the shipped example). Per user there are three modes: restrict (the default), norestrict (track usage but never restrict) and ignore (neither track nor restrict).
Going live in norestrict for a week to see who is genuinely generating load produces far fewer tickets than jumping straight to hard limits.
Its second benefit is underrated: killing long-running queries. Turning on the slow_queries setting in the same file cuts off queries past the threshold, so the whole server does not sit waiting on one SELECT that has been running for minutes.
What CloudLinux does not solve
Being honest about the gaps:
- It does not limit network bandwidth. An account serving large files can saturate the uplink without tripping a single LVE limit. That has to be handled at the web server or traffic shaping layer.
- It is not security software. CageFS gives you isolation, but malware detection, WAF and brute force protection are a separate layer — you will want to look at Imunify360 or an equivalent alongside it.
- It does not fix bad code. It confines the damage to one account. That account's customer still sees the 508.
- It ships its own kernel. Security patches mean reboots. If you want to stop scheduling those, KernelCare is the live-patching piece, and the two are designed to run on the same server.
On the licensing side
A CloudLinux license bought through vialicense.com binds to the server's IP address, not to a key file. That is what reduces installation to a single command: the script finds the license from the IP. When you move servers, you change the IP yourself from the panel and the old address stops immediately.
Activation is automatic — it draws from prepaid balance, there is no approval step, and it is usually live within a minute. Monthly licenses run to the end of the period, with no commitment and no cancellation fee. Editions and activation steps are on the CloudLinux license page.