Technical Guides

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.

cloudlinuxlvecpanel508wordpresshosting

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:

LimitWhat it countsWhat you see when it is hit
EP (entry processes)Concurrent requests entering the account508 Resource Limit Is Reached
NPROCEvery process in the account — PHP, cron, MySQL, mailProcesses fail to start, usually a 500
PMEMThe account’s physical memoryProcesses get killed — blank page or 500
SPEED (CPU)CPU timeThe site gets slow, but throws no error
IO / IOPSDisk throughput and operation countThe 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 2442 is the classic 508 case: uEP 4 against a limit of lEP 30 at 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 2523 and 3012 look like the opposite: zero EP faults, but uPMem is exactly equal to lPMem — 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 ps for CPU percentage — ps averages CPU over each process’s entire lifetime, not the current moment. Use top for 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:

  1. 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: hit
    hit means the page came from cache and PHP never started. If you see miss on every reload, the cache is only enabled on paper.
  2. Put Cloudflare in front of the site and rate-limit wp-login.php and xmlrpc.php. That stops the bots before they ever reach the server.
  3. Move backups outside peak hours and disable batch mode if the plugin allows it.
  4. 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:
    define('DISABLE_WP_CRON', true);
    Then add a cron job from your hosting panel:
    */15 * * * * cd /home/user1/public_html && /usr/local/bin/php wp-cron.php >/dev/null 2>&1
    And confirm it actually took effect:
    wp config get DISABLE_WP_CRON
    1
  5. 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.
  6. 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 errorMeaningDirection
Module kmodlve not found in directory ...No module built for this kernelChange the kernel, see below
Invalid argument / version magicModule/kernel mismatchUpdate kmod-lve
Cannot allocate memorycgroup or GRUB problemCloudLinux KB article on cgroup + GRUB
Signature rejected in dmesgSecure BootCheck 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 +debug kernel. 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.el8 or 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-lve in the same window as the kernel. This is CloudLinux’s own instruction, and that exact gap is what causes the outage.
  • Run grubby --default-kernel after every kernel update.
  • Keep only one kmod-lve version installed:
    rpm -qa 'kmod-lve*' | sort -V
    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.
  • 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 -sI check that it is actually being used.
  • Cloudflare with rate limits on wp-login.php and xmlrpc.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.

Related Articles