508 Resource Limit Is Reached: Complete Diagnosis and Fix
508 is not a WordPress error. It comes from the server, before PHP has even started. Here is which limit causes it, how to prove it with commands and real output, and what to do when an entire server starts returning it.
Your site was running fine, and then it starts returning a blank page reading 508 Resource Limit Is Reached. Five minutes later it recovers on its own. An hour later it is back.
Search for a fix and you will find dozens of articles telling you to disable your plugins one by one or switch themes. Sometimes that does help — but more often it does not, because 508 does not come from WordPress at all. It comes from the server, and it comes before PHP has started. Whatever is in your plugins, none of it had even been read by the time that response was returned.
This article walks through the real diagnosis: which limit actually causes 508, how to prove it with specific commands instead of guessing, and what to do in the rare but very unpleasant case where the error appears on every site on the server at once.
What 508 actually means
Most shared hosting runs CloudLinux. The idea is simple: every hosting account lives inside its own container, called an LVE (Lightweight Virtual Environment), with its own limits for memory, CPU and process count. That way one account that suddenly starts eating the server does not take the other fifty down with it.
The web server module that enforces this is mod_hostinglimits. On every request it tries to place the process into that account’s LVE. When it fails — most commonly because the account has already hit its limit on concurrent requests — the web server returns 508 and stops there. PHP never starts, WordPress never loads, the database is never queried.
That is why deleting a plugin rarely changes anything immediately. It affects how long each request runs, not how many requests are in flight.
The five limits and what each one looks like
LVE is not one limit but several, and each one shows up differently. This is the first thing to know, because the symptom tells you which limit was hit:
| Limit | What it counts | What you see when it is hit |
|---|---|---|
| EP (entry processes) | Concurrent requests entering the account | 508 Resource Limit Is Reached |
| NPROC | Every process in the account — PHP, cron, MySQL, mail | Processes fail to start, usually a 500 |
| PMEM | The account’s physical memory | Processes get killed — blank page or 500 |
| SPEED (CPU) | CPU time | The site gets slow, but throws no error |
| IO / IOPS | Disk throughput and operation count | The site hangs waiting for data, no error |
If you have root access, the exact values for your server are one command away:
lvectl list | head
ID SPEED PMEM VMEM EP NPROC IO IOPS
default 200 2048M 0K 30 75 50000 2048
3220 200 4096M 0K 30 75 250000 5120
Here default is the package that applies to most accounts: 30 concurrent requests, 75 processes in total, 2 GB of memory. The row below it is an account with a raised limit. The numbers on your server will differ, but the column layout is the same.
Note the last two rows of the table above. Exceeding the IO limit usually generates no error at all — the site simply stops responding while it waits for data to move from disk to memory. People often go hunting for an error that does not exist instead of looking at the right metric.
For 508, though, the answer is almost always the same one: EP. Too many concurrent requests to the account.
First question: is it only your site?
This is where the paths split, and from here on we are talking about two completely different problems that display the same page.
The check takes thirty seconds:
curl -sI https://your-domain/ | head -1
curl -sI https://your-domain:2083/ | head -1
HTTP/1.1 508 Resource Limit Is Reached
HTTP/1.1 200 OK
If cPanel responds normally while the site returns 508, the problem is inside your account — continue with the next section. If both return 508, skip straight to the section on the LVE module. That case is rare, but it is a full outage and none of the usual advice will do anything for it.
If you have no console access, the same check works by hand: open your cPanel and webmail in a browser, and if you know another site on the same server, try that too.
Case A: your account is hitting its limit
Look at the facts before you change anything
In cPanel, go to Metrics → Resource Usage. There is a faults section showing how many times the account hit each limit and when. That is the only place that tells you the truth. If you see EP faults, you have too many concurrent requests. If you see PMEM faults, the real problem is memory and 508 is only a side effect.
If you have root access to the server, the same data is available in more detail:
lveinfo --period=1h -o PMemf -d \
--show-columns=ID,From,To,uEP,lEP,EPf,uPMem,lPMem,PMemF
ID | From | To | uEP | lEP | EPf | uPMem | lPMem | PMemF
------+---------------------+---------------------+-----+-----+------+-------+-------+-------
2523 | 2026-08-12 12:00:00 | 2026-08-12 13:00:00 | 1 | 30 | 0 | 2.00G | 2.00G | 1149
3012 | 2026-08-12 12:00:00 | 2026-08-12 13:00:00 | 1 | 30 | 0 | 2.00G | 2.00G | 983
2442 | 2026-08-12 12:00:00 | 2026-08-12 13:00:00 | 4 | 30 | 612 | 1.31G | 2.00G | 0
Read the columns like this: those starting with u are usage, those starting with l are the limit, and those ending in f or F are fault counts — how many times the limit was actually hit.
- Row
2442is the classic 508 case:uEP 4against a limit oflEP 30at the moment of the snapshot, but 612 EP faults over the period. The requests arrive in bursts, and on every burst the account slams into the ceiling. - Rows
2523and3012look like the opposite: zero EP faults, butuPMemis exactly equal tolPMem— memory is pinned at the ceiling with over a thousand PMEM faults. Here 508 is a consequence and the real problem is memory. We will come back to this case at the end, because it hides a trap.
Run the same command for a 24-hour window and compare the two:
lveinfo --period=1d -o PMemf -d \
--show-columns=ID,From,To,uEP,lEP,EPf,uPMem,lPMem,PMemF
This is a small trick that saves a lot of time: if the one-hour and 24-hour numbers are nearly identical, everything happened within the last hour — meaning you have a sudden event, not gradual degradation. The opposite — high 24-hour counts with low one-hour counts — means a chronic problem that has been smouldering for a while, and that is an entirely different conversation.
Who is actually making the requests
Faults tell you that there is a problem. Access logs tell you who is causing it. Over SSH inside the account:
cd ~/access-logs && ls
example.com example.com-ssl_log shop.example.com shop.example.com-ssl_log
The file with no suffix is HTTP, the one ending in -ssl_log is HTTPS. The real path on the server is /etc/apache2/logs/domlogs/USERNAME, and through the panel the same files download from cPanel → Raw Access.
Every line is one request, which means one process:
IP - - [Timestamp] "Request" Code Size "Referer" "UserAgent"
Count which addresses are hitting you hardest:
awk '{print $1}' example.com-ssl_log | sort | uniq -c | sort -rn | head
4187 203.0.113.47
3902 198.51.100.22
118 66.249.66.1
41 192.0.2.15
33 192.0.2.88
The gap between the first two rows and the rest is the whole answer. Four thousand requests from one address is not a visitor. Check what they want:
grep 203.0.113.47 example.com-ssl_log | tail -3
203.0.113.47 - - [12/Aug/2026:12:14:03 +0300] "POST /wp-login.php HTTP/1.1" 200 4021 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
203.0.113.47 - - [12/Aug/2026:12:14:03 +0300] "POST /wp-login.php HTTP/1.1" 200 4021 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
203.0.113.47 - - [12/Aug/2026:12:14:04 +0300] "POST /wp-login.php HTTP/1.1" 200 4021 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
Three login attempts inside one second. That is where the requests are coming from.
The same thing by requested path rather than by IP:
awk -F'"' '{print $2}' example.com-ssl_log | awk '{print $2}' \
| sort | uniq -c | sort -rn | head -5
8214 /wp-login.php
1902 /xmlrpc.php
884 /wp-admin/admin-ajax.php
311 /
204 /wp-cron.php
If you see this picture, you know exactly what to rate-limit.
What is running right now
The other half of the answer is which processes are actually alive. Over SSH inside the account:
ps -u $USER -o pid,etime,%cpu,%mem,cmd --sort=-%cpu | head
PID ELAPSED %CPU %MEM CMD
31402 04:11 24.6 1.9 lsphp /home/user1/public_html/wp-cron.php
31455 03:58 22.1 1.8 lsphp /home/user1/public_html/wp-admin/admin-ajax.php
31501 03:44 19.7 2.1 lsphp .../plugins/updraftplus/includes/class-backup.php
31502 03:44 18.9 2.0 lsphp .../plugins/updraftplus/includes/class-backup.php
31503 03:43 18.2 2.1 lsphp .../plugins/updraftplus/includes/class-backup.php
31504 03:41 17.4 1.9 lsphp .../plugins/updraftplus/includes/class-backup.php
Everything is visible here: four concurrent UpdraftPlus processes, each running for close to four minutes, plus wp-cron.php and admin-ajax.php. Six slots out of thirty are occupied without a single real visitor. A little traffic on top of that and the account is full.
If you are running this check as the server administrator, do not trust
psfor CPU percentage —psaverages CPU over each process’s entire lifetime, not the current moment. Usetopfor an instantaneous picture. That detail has already cost fifteen minutes in a real incident.
The real causes, in order of frequency
Bots on wp-login.php and xmlrpc.php. By far the most common cause, exactly as in the example above. Every login attempt is a separate request, and bots make several per second. The account hits its limit within seconds and the site goes down without a single real visitor being involved.
Backup plugins that work in batches. UpdraftPlus is the classic example: instead of one long-running process, it spawns separate processes for plugins, themes, uploads, the database, core and so on. If you have several sites in one account and their backups start at roughly the same time, you will see dozens of concurrent PHP processes appear out of nowhere.
admin-ajax.php. WordPress Heartbeat and plugins that constantly poll this file. Every poll is a request. An admin panel left open in several browser tabs counts too.
No caching. Without a cache, every visit goes through the full PHP and MySQL cycle. With one, most requests are served as ready-made HTML and never enter the limit at all.
Slow MySQL queries. A query counts as slow once it takes more than three seconds. For that whole time the process is busy and the slot is blocked. Twenty such queries at once and the account is full, even on modest traffic.
Genuine traffic. Sometimes the cause is a pleasant one — a post took off and the site has outgrown its plan.
What actually helps
In order of effect relative to effort:
- Turn on caching. LiteSpeed Cache if your host runs LiteSpeed, otherwise WP Rocket or an equivalent. This is the single highest-impact change and it is almost always enough on its own. Verify it is working rather than trusting the setting:
curl -sI https://your-domain/ | grep -i 'x-litespeed-cache\|x-cache'x-litespeed-cache: hithitmeans the page came from cache and PHP never started. If you seemisson every reload, the cache is only enabled on paper. - Put Cloudflare in front of the site and rate-limit
wp-login.phpandxmlrpc.php. That stops the bots before they ever reach the server. - Move backups outside peak hours and disable batch mode if the plugin allows it.
- Disable the built-in WordPress cron and replace it with a system one. In
wp-config.php, above the/* That's all, stop editing! */line:
Then add a cron job from your hosting panel:define('DISABLE_WP_CRON', true);
And confirm it actually took effect:*/15 * * * * cd /home/user1/public_html && /usr/local/bin/php wp-cron.php >/dev/null 2>&1wp config get DISABLE_WP_CRON1 - Optimise the slow queries. This is developer work and your host will not do it for you — but it is also the only fix if that is where the cause lies.
- Only then raise the limit. If the previous five did not help, the account has simply outgrown shared hosting.
What does not help
Deleting plugins at random. Switching themes. Waiting and hoping it passes. All three appear in every article on the subject and all three leave the cause untouched — the site goes down again at the next spike.
Case B: every site on the server returns 508
This brings us to the part you will find written down almost nowhere.
If every site on the server returns 508 — not one account but all of them, including the panel — this is not a resource problem. Nobody has hit their limit. The problem is that LVE is not working at all, and when the web server cannot place a process into an LVE, 508 is what it returns by default.
Two definitive signs. First, /var/log/messages fills up:
grep -i 'pam_sulve' /var/log/messages | tail -3
Aug 12 11:58:02 srv12 sshd[19187]: pam_sulve[19187]: Unable to initialize check LVE 2
Aug 12 11:58:14 srv12 sshd[19203]: pam_sulve[19203]: Unable to initialize check LVE 2
Aug 12 11:58:31 srv12 crond[19288]: pam_sulve[19288]: Unable to initialize check LVE 2
The trailing 2 is ENOENT — the PAM module cannot find /proc/lve.
Second, lvectl fails:
lvectl list
error: clcommon: get_lve_version: Can`t open file /proc/lve/list
This is not a CageFS problem. Rebuilding or reinitialising CageFS will not fix it and only adds risk during an outage. Do not waste your time there.
The checks
Everything here is read-only. Nothing changes the system.
uname -r
lsmod | grep -i lve
ls -l /proc/lve/
Broken looks like this:
5.14.0-284.1101.el8.tuxcare.11.els9.x86_64
ls: cannot access '/proc/lve/': No such file or directory
lsmod returns nothing at all, and /proc/lve/ does not exist. For comparison, on a healthy server the same three commands give:
4.18.0-553.144.1.lve.el8.x86_64
kmodlve 823296 310
total 0
-r--r--r-- 1 root root 0 Aug 12 14:02 list
Next, see which packages are installed:
rpm -qa | grep -Ei 'kmod-?lve|^lve-'
kmod-lve-2.1-69.el8.x86_64
kmod-lve-2.1-71.1.el8.x86_64
lve-utils-6.4.1-1.el8.cloudlinux.x86_64
Two different kmod-lve versions installed side by side is a known problem — CloudLinux states only one should be present, because the old one can try to load instead of the correct one.
Now the most important check — which kernels actually have a module built for them:
find /lib/modules -name 'kmodlve*'
/lib/modules/4.18.0-553.111.1.lve.el8/extra/lve-2.1-71.1/kmodlve.ko
/lib/modules/4.18.0-553.111.1.lve.el8/extra/lve-2.1-69/kmodlve.ko
/lib/modules/5.14.0-284.1101.el8.tuxcare.11.els7/weak-updates/lve-2.1-56/kmodlve.ko
/lib/modules/4.18.0-553.144.1.lve.el8/weak-updates/lve-2.1-71.1/kmodlve.ko
Compare these names against what uname -r returned. The running kernel was ...els9, and the list contains ...els7 plus three kernels from the 4.18 line. The kernel currently running is nowhere in it — that is the fault.
Confirm with modprobe:
modprobe kmodlve; echo "rc=$?"
modprobe: FATAL: Module kmodlve not found in directory /lib/modules/5.14.0-284.1101.el8.tuxcare.11.els9.x86_64
rc=1
If the message is different, check dmesg as well:
dmesg | grep -i -E 'lve|module verification|signature' | tail -20
The error text tells you which case you are in:
| modprobe error | Meaning | Direction |
|---|---|---|
Module kmodlve not found in directory ... | No module built for this kernel | Change the kernel, see below |
Invalid argument / version magic | Module/kernel mismatch | Update kmod-lve |
Cannot allocate memory | cgroup or GRUB problem | CloudLinux KB article on cgroup + GRUB |
Signature rejected in dmesg | Secure Boot | Check mokutil --sb-state |
How you end up here
The example above is from a real outage. A TuxCare ELS update moved the box from els7 to els9, and there is no LVE module for els9. The default boot entry had also been switched to els9, so the state survived reboots. The server came up every time — just without LVE, and every site on it returned 508.
The fix
Pick a kernel that satisfies all three conditions at once: it is present in /boot, it has a kmodlve.ko under /lib/modules/<kernel>/, and it is current on security.
ls /boot/vmlinuz-*
/boot/vmlinuz-0-rescue-8a3f1c
/boot/vmlinuz-4.18.0-553.111.1.lve.el8.x86_64
/boot/vmlinuz-4.18.0-553.144.1.lve.el8.x86_64
/boot/vmlinuz-5.14.0-284.1101.el8.tuxcare.11.els9.x86_64
Cross-referenced with the find output, the only sensible choice here is 4.18.0-553.144.1.lve.el8.x86_64 — it has a module and it is the newer of the two.
Two rules that save you a second outage:
- Never pick a
+debugkernel. No LVE module is built for debug kernels. - Prefer newer. At the time of writing, GhostLock (CVE-2026-43499) requires
kernel-4.18.0-553.141.2.lve.el8or newer on CloudLinux 8.
Verify the module file is real and not a dangling symlink:
ls -lL /lib/modules/4.18.0-553.144.1.lve.el8/weak-updates/lve-2.1-71.1/kmodlve.ko
-rw-r--r-- 1 root root 823296 Jul 30 09:14 /lib/modules/4.18.0-553.144.1.lve.el8/weak-updates/lve-2.1-71.1/kmodlve.ko
The key is -L, which follows the link. A real file with a size is good. If instead you see:
ls: cannot access '/lib/modules/.../kmodlve.ko': No such file or directory
the link is dangling and you need a different kernel.
Then set the default kernel. This step changes only the boot entry, nothing on the running system, and it is reversible:
grubby --set-default=/boot/vmlinuz-4.18.0-553.144.1.lve.el8.x86_64
grubby --default-kernel
/boot/vmlinuz-4.18.0-553.144.1.lve.el8.x86_64
The second command must echo back exactly the kernel you chose. If it still shows the old one, stop and check GRUB_DEFAULT=saved and grubenv before rebooting.
Only then reboot — with console or IPMI access already in place before you press Enter.
Confirming the fix
uname -r
lsmod | grep kmodlve
lvectl list | head
Healthy looks like this:
4.18.0-553.144.1.lve.el8.x86_64
kmodlve 823296 310
ID SPEED PMEM VMEM EP NPROC IO IOPS
default 200 2048M 0K 30 75 50000 2048
3220 200 4096M 0K 30 75 250000 5120
One important detail: lvectl list must show real user IDs, not just default. If you only see default and limit, that is a different fault — it points to reinstalling the LVE userspace packages.
Finally, check the visitor-facing side:
curl -sI https://your-domain/ | head -1
grep -i 'pam_sulve\|initialize check LVE' /var/log/messages | tail -5
HTTP/1.1 200 OK
Aug 12 11:58:31 srv12 crond[19288]: pam_sulve[19288]: Unable to initialize check LVE 2
Expect a 2xx or 3xx, and in the log, no pam_sulve lines timestamped after the reboot. The single remaining line here predates the outage, which is exactly what you want to see.
The trap: zero faults does not mean healthy
This deserves its own section, because it misleads even experienced people. Go back to rows 2523 and 3012 in the lveinfo output near the start — zero EP faults, with memory pinned at the ceiling.
During one outage lveinfo showed exactly that: EPf 0 for accounts that at that same moment were running 63 to 66 processes against a limit of 30. By every visible metric the limits were being respected. They were not.
The reason is in how the counting works. LVE registers an entry process at the moment a request comes in over HTTP. But with LiteSpeed, the LSAPI backend children are pooled outside that accounting, so EP simply never fires. As a result, the only limit genuinely constraining the account is PMEM — and PMEM constrains in a completely different way: it does not queue requests, it kills processes.
Count the processes directly and compare against lEP from lveinfo:
for u in user1 user2 user3; do
printf "%-10s procs=%s\n" $u \
$(wc -l < /sys/fs/cgroup/memory/lve$(id -u $u)/cgroup.procs)
done
user1 procs=66
user2 procs=63
user3 procs=64
Sixty-six processes against a limit of thirty. That is the violation the faults never showed.
Next, find which accounts are pinned against their memory ceiling:
cd /sys/fs/cgroup/memory
for d in lve*; do
echo "$(( $(cat $d/memory.usage_in_bytes) * 100 / $(cat $d/memory.limit_in_bytes) ))% $d"
done | sort -rn | head -10
100% lve2523
99% lve3012
99% lve2540
99% lve2442
97% lve2425
59% lve2891
41% lve3104
38% lve2677
22% lve3390
19% lve2115
You are looking for exactly that sharp cliff — five accounts at the ceiling, then the sixth suddenly at 59 percent. Everything above the cliff is a suspect. If there is no cliff and everything is moderate, the problem is somewhere else.
The last check explains why this is so dangerous:
grep -E '^(rss|cache)' /sys/fs/cgroup/memory/lve2523/memory.stat
free -h | grep -i swap
rss 2127155200
cache 3313664
Swap: 0B 0B 0B
rss is roughly 2 GB — exactly at the limit. cache has collapsed to 3 MB, and that is the proof that the kernel has already tried to reclaim memory and taken everything it could. Without swap, anonymous memory cannot be reclaimed at all. From here on, every new allocation enters an endless cycle of failed reclaim and burns CPU inside the kernel until the whole server chokes.
If you have got this far, the confirmation is in dmesg:
dmesg -T | grep -i 'oom' | tail -3
[Wed Aug 12 12:31:07 2026] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=/,mems_allowed=0,oom_memcg=/lve2523,task_memcg=/lve2523,task=lsphp,pid=48211,rss=19422
[Wed Aug 12 12:31:07 2026] Memory cgroup out of memory: Killed process 48211 (lsphp) total-vm:412904kB, anon-rss:77688kB, file-rss:0kB
[Wed Aug 12 12:31:08 2026] Memory cgroup out of memory: Killed process 48219 (lsphp) total-vm:409512kB, anon-rss:76104kB, file-rss:0kB
CONSTRAINT_MEMCG means a per-account limit, not the server’s — the machine can have 80 percent of its RAM free and this still happens. oom_memcg=/lve2523 hands you the account directly. And the killed processes are 76–77 MB each, which means there is no single greedy process — the pressure is aggregate. Do not go hunting for a memory leak that is not there.
Keeping it from happening again
On the server side:
- Update
kmod-lvein the same window as the kernel. This is CloudLinux’s own instruction, and that exact gap is what causes the outage. - Run
grubby --default-kernelafter every kernel update. - Keep only one
kmod-lveversion installed:
If that returns more than one line, remove the old version in a planned window with console access — not on a machine you have just recovered.rpm -qa 'kmod-lve*' | sort -V - If TuxCare ELS kernels and stock CloudLinux 8 kernels coexist on the machine, decide which line you are staying on and exclude the other from updates so it cannot silently become the default.
On the site side:
- Caching that genuinely works, and a
curl -sIcheck that it is actually being used. - Cloudflare with rate limits on
wp-login.phpandxmlrpc.php. - Backups scheduled in low-traffic hours.
- A periodic look at Resource Usage, instead of waiting for the site to go down.
In short
508 is not a WordPress error — it comes from mod_hostinglimits and almost always means too many concurrent requests to the account.
First use curl -sI to establish whether only the site is down or the panel is too. If it is only the site, look at the faults in Resource Usage or lveinfo, then at the access logs — they will tell you who is making the requests. If everything is down, go looking for the LVE module: lsmod, /proc/lve and modprobe kmodlve will answer you in under a minute.
And remember that zero faults is not proof of health. Sometimes the limit that is supposed to protect you simply never fires.
Photo: A view of the server room at The National Archives by The National Archives (UK), CC BY 3.0, cropped.
If your site keeps going down with 508 and you would rather not run the diagnosis yourself, get in touch — we do both site optimisation and server administration.