If you manage a VPS or dedicated server running DirectAdmin and Apache is suddenly pinning your CPU at 100%, websites are timing out and your load average has gone through the roof — you need a fast, reproducible diagnosis. This guide is a step-by-step runbook for the directadmin apache high cpu usage fix that takes you from zero to root-cause in under 10 minutes, using exact DirectAdmin file paths and real commands.
Whether the culprit is a brute-force attack on xmlrpc.php, a misconfigured MPM spawning hundreds of httpd workers, or a single runaway PHP script, the process below will surface it — and give you the tools to stop it.
What Causes Apache CPU to Spike on DirectAdmin Servers
Before reaching for a fix, it helps to know what you are actually fighting. The most common root causes on DirectAdmin servers are:
- Too many simultaneous httpd workers —
MaxRequestWorkers(or the legacyMaxClients) is set higher than your RAM can support, causing the kernel to swap and thrash. - HTTP flood or DDoS attack — bots hammering WordPress
xmlrpc.php,wp-login.php, or other entry points generate thousands of short-lived Apache children. - Slow back-end (PHP/MySQL) — if PHP-FPM or MySQL is slow, Apache workers block waiting for a response, filling the process table and driving load sky-high.
- KeepAlive set too aggressively — long
KeepAliveTimeoutvalues tie up workers for clients that have already disconnected. - ModSecurity rule exhaustion — complex WAF rule sets run per-request regex matching which is extremely CPU-intensive under high traffic.
- Runaway PHP or CGI script — a single site with an infinite loop or memory leak can consume an entire CPU core.
💡 None of these worked? Skip the guesswork.
Get Expert Help →Step 1 — Immediately Identify the Offending Process or Site
The moment you suspect a problem, run:
uptime
top -b -n 1 -o %CPU | head -30
If httpd (or apache2) processes fill the top of the list, you are dealing with an Apache overload. Note the PIDs of the highest consumers.
ps aux | grep httpd | grep -v grep | wc -l
On a 2 GB VPS with Prefork MPM the safe ceiling is roughly 20–30 workers. Anything above 80–100 indicates runaway spawning.
lsof -p <PID> | grep -E 'vhost|public_html'
Replace <PID> with the offending process ID. The output will show the open file handles pointing to a specific user's public_html directory — that is your problem site.
strace -p <PID> -e trace=read,write -s 512 2>&1 | head -40
This reveals what system calls the process is actually executing, useful for catching an infinite PHP loop or a blocking socket.
Add the following block to /etc/httpd/conf/extra/httpd-info.conf (create it if it does not exist) and include it from the main config:
<IfModule mod_status.c>
ExtendedStatus On
<Location /server-status>
SetHandler server-status
Require ip 127.0.0.1
</Location>
</IfModule>
Then add to /etc/httpd/conf/httpd.conf:
Include conf/extra/httpd-info.conf
Reload Apache:
service httpd reload
Access the status page from the server itself:
curl -s http://127.0.0.1/server-status?auto | head -50
Look at the Request column — you will instantly see if every worker is serving requests from the same IP, or all hitting the same URL (a clear sign of a flood attack or bot storm).
DirectAdmin stores per-user access logs at:
/var/log/httpd/domains/<domain.com>.log
The global combined log is at:
/var/log/httpd/access_log
To find the top 20 requesting IPs in the last 5,000 lines:
tail -5000 /var/log/httpd/access_log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
To find the top 20 requested URLs:
tail -5000 /var/log/httpd/access_log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20
If /xmlrpc.php, /wp-login.php, or /?author= scans appear thousands of times, you have a WordPress attack — jump to Step 5.
Rule of thumb: MaxRequestWorkers = (Total RAM in MB − 256 MB for OS) ÷ Average httpd process size in MB
Check average httpd process RAM:
ps aux | grep httpd | grep -v grep | awk '{sum+=$6; count++} END {print sum/count/1024 " MB average"}'
A 4 GB server with 30 MB average workers can safely run about 125 workers: (4096−256) ÷ 30 ≈ 128. Set it slightly below that.
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 100
MaxConnectionsPerChild 300
</IfModule>
Lower MaxConnectionsPerChild (formerly MaxRequestsPerChild) forces workers to recycle after 300 requests, preventing PHP memory leaks from accumulating indefinitely.
In /etc/httpd/conf/httpd.conf:
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 3
Reducing KeepAliveTimeout from the default 15 seconds to 3 seconds frees workers much faster between requests.
apachectl configtest && service httpd reload
cd /usr/local/directadmin/custombuild
./build set webserver nginx_apache
./build nginx
./build apache
./build rewrite_confs
service nginx status
service httpd status
Nginx listens on port 80/443. Apache moves to port 8080 (internal only). Your users see no change — Nginx proxies PHP requests to Apache on 8080 and serves everything else itself.
The Nginx virtual host config per user is at:
/etc/nginx/conf.d/virtual/<domain.com>.conf
The Apache virtual host template (now internal) stays at:
/usr/local/directadmin/data/users/<username>/httpd.conf
Add to /etc/nginx/nginx.conf inside the http {} block:
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
# Drop a single attacking IP
iptables -I INPUT -s 203.0.113.45 -j DROP
# Drop a /24 subnet
iptables -I INPUT -s 203.0.113.0/24 -j DROP
# Save rules
service iptables save
Add to the WordPress site's per-user httpd.conf at /usr/local/directadmin/data/users/<username>/httpd.conf inside the <VirtualHost> block, or create a site-specific include:
<Files xmlrpc.php>
Order Deny,Allow
Deny from all
</Files>
Alternatively, block it in .htaccess inside public_html:
# Block xmlrpc.php
<Files xmlrpc.php>
Require all denied
</Files>
# Block wp-login.php brute force — only allow your IP
<Files wp-login.php>
Require ip 192.0.2.1
</Files>
Install and enable fail2ban with the Apache-specific jails:
yum install fail2ban -y # AlmaLinux / CentOS
# or
apt install fail2ban -y # Ubuntu / Debian
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
In /etc/fail2ban/jail.local enable:
[apache-auth]
enabled = true
maxretry = 5
bantime = 3600
[apache-noscript]
enabled = true
[apache-overflows]
enabled = true
systemctl enable fail2ban
systemctl restart fail2ban
fail2ban-client status
ModSecurity config on DirectAdmin lives at:
/etc/httpd/conf/extra/httpd-modsecurity.conf
To temporarily verify ModSecurity is the culprit, switch it to detection-only mode:
SecRuleEngine DetectionOnly
If CPU immediately drops, your rule set is too broad for your traffic volume. Consider switching to OWASP CRS in Paranoia Level 1 or disabling rule IDs that match your legitimate traffic.
If your server is under persistent attack or you want proactive protection, our team provides managed server support including 24/7 monitoring, emergency response, and Apache hardening — so you are never fighting a CPU crisis alone.
