Web Hosting Security Threats Explained: DDoS, Brute Force, SQL Injection, XSS — Who Mitigates What

Mangesh Supe, Hosting Performance Analyst

By

Founder, ThatMy.com • Independent Hosting Benchmarks • ISP & Network Infrastructure Background

X LinkedIn How we test →

Web Hosting Security Threats Explained: DDoS, Brute Force, SQL Injection, XSS — Who Mitigates What

WordPress powers 43% of the web. That makes it the most-attacked platform on the internet. Automated scanners probe WordPress sites 24 hours a day, 7 days a week, looking for four specific entry points: login forms, XML-RPC endpoints, plugin input fields, and output rendering logic. This guide is organized around detection first because most people who land here are experiencing something on their site right now and need to name what it is before they can stop it.

The good news: most attacks are opportunistic and automated. A site with basic hardening blocks over 99% of them without any manual intervention. The full hardening stack in Section 6 takes under two hours to implement and costs under $20/month at the network layer. What you will find in this guide: the detection signature for each attack type, the commands and log patterns that confirm it, the immediate response, and the structural prevention that stops it from recurring.

Attack Detection Quick Reference: What Is Happening to Your Site Right Now

Most security guides open with definitions. This one starts with what you are seeing on your screen, because that is the question people search with. The table below maps observable symptoms to attack types and tells you where in this page to go for the full response. If your site is actively behaving wrong right now, start here.

You Are SeeingAttack TypeUrgencyFirst Action
Site extremely slow, server CPU at 100%DDoS or bot floodHIGHCheck server connections with netstat/ss. Enable Cloudflare Under Attack Mode immediately.
Hundreds of failed login attempts in logsBrute force attackMEDIUMBlock attacking IPs in Cloudflare or WordFence. Enable login rate limiting.
Spam content or links appearing in pagesSQL injection (database attack)HIGHCheck wp_options for injected values. Audit recently active plugins for SQLi vulnerabilities.
Site redirecting, defaced, or content modifiedActive compromise post-injectionCRITICALTake site offline. Audit all files against known-good backup. Check for backdoor accounts.
Odd JavaScript in page source codeXSS injectionHIGHSearch wp_posts and wp_options for script tags. Check for compromised theme or plugin files.
Google warning 'This site may harm computers'Malware or SEO spam injectionCRITICALSearch Console Security Issues tab. Pull latest backup. Scan all files with WordFence.
Server logs: thousands of /wp-login.php requestsBrute force attemptMEDIUMRate-limit wp-login.php in Cloudflare. Block source IP range if concentrated.
503 errors during apparent traffic spikeDDoS Layer 7 floodHIGHCheck Cloudflare Analytics for request spike with high 5XX rate. Enable Under Attack Mode.
Critical distinction: Detection and recovery are different problems. This guide covers detection and prevention. If your site has been actively compromised (files modified, backdoor accounts present, content injected), the recovery process is a separate workflow. Start with a clean backup restore, then apply this guide's hardening stack before going back online.

The attack quick-reference above is designed to be bookmarked and shared. The sections below each cover one attack type in full: what it is, how to detect it with specific commands, and the mitigation sequence from immediate response through structural prevention.

DDoS Attacks on WordPress: Detection, Types, and How to Stop One That Is Happening Now

Most WordPress owners think a DDoS means their server is overwhelmed by traffic. That describes one type. The more common attack against WordPress is not a bandwidth flood at all. It is a deliberate exhaustion of PHP workers and database connections using a small number of bots sending carefully crafted HTTP requests. Your server CPU hits 100% and visitors get 503 errors, but Cloudflare Analytics shows only a moderate request increase. That is Layer 7 application DDoS. No CDN-busting bandwidth required.

DDoS attack architecture: Layer 3 volumetric bandwidth flood versus Layer 7 HTTP application flood targeting WordPress PHP workers and database connections, with Cloudflare edge mitigation layer
DDoS TypeMechanismServer ImpactScopeCorrect Response
Volumetric (Layer 3/4)Raw bandwidth flood from botnetServer cannot receive legitimate connectionsRequires CDN-level mitigation. Individual servers cannot absorb this.Cloudflare free tier absorbs this entirely at the network edge before reaching your server.
Application (Layer 7)HTTP requests exhausting PHP workersPHP-FPM pool fills, requests queue, site goes 503Can execute with a small number of bots. No bandwidth saturation needed.Cloudflare WAF + Under Attack Mode. Rate-limit wp-login.php and high-cost endpoints.
XML-RPC amplificationRepeated requests to /xmlrpc.phpServer overload, can relay spam or enable proxied brute forceHistorically exploited heavily. Many WordPress sites still have it enabled.Block /xmlrpc.php entirely via .htaccess or Cloudflare firewall rule if you do not use it.

How to Detect an Active DDoS: Run This Now

SSH into your server and run the following commands to see your current connection state in real time. The pattern in the output tells you whether you are dealing with a targeted flood from few IPs, a botnet flood from many, or high legitimate traffic that your server is genuinely undersized for.

Two commands that identify DDoS traffic vs legitimate load within 30 seconds:
# Count connections per IP on port 80 (or 443)
# Run this first — output tells you the nature of the traffic
netstat -n | grep :80 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

# Faster on modern systems (replaces netstat):
ss -tn | grep :80 | awk '{print $6}' | cut -d: -f4 | sort | uniq -c | sort -rn | head -20

# What the output means:
# One or few IPs with 100+ connections each  = targeted DDoS or aggressive scraper
# Hundreds of different IPs with 1-5 each    = botnet DDoS or legitimate traffic spike
# Mostly your own CDN IPs with many each     = Cloudflare forwarding — check origin

# Check total active connections right now:
ss -s | grep "TCP:" 

Cloudflare Analytics as DDoS Early Warning

Before SSH access is needed, Cloudflare Analytics shows a DDoS in progress. Navigate to Cloudflare dashboard then Analytics then Traffic. A sudden vertical spike in requests combined with a high percentage of 5XX responses means your server is failing to handle the volume. The ratio of 5XX to total requests matters more than the absolute request count. 10,000 requests with 95% 5XX is a DDoS. 10,000 requests with 1% 5XX is a traffic spike your server is mostly handling.

Cloudflare Security then Events shows which specific IPs and patterns triggered WAF rules. During an active attack, this view updates every few minutes and gives you the source IP ranges, the attack pattern, and whether Cloudflare has already blocked it automatically.

Stopping an Active DDoS: The Two-Minute Response

When a DDoS is confirmed and active, the response sequence is:

  1. Enable Cloudflare Under Attack Mode: Security then Settings then Security Level then "I'm Under Attack". This adds a JavaScript challenge to every visitor. Bots and automated traffic cannot pass it. Real visitors see a brief loading screen. Activates within seconds. This single action stops most WordPress-targeted DDoS attacks immediately.
  2. Block the attacking IP range: If the netstat output shows concentrated traffic from one or a small number of IP ranges, add Cloudflare Firewall Rules to block them: (ip.src eq 123.456.789.0/24) Action: Block.
  3. Block XML-RPC if not already done: If the attack is targeting /xmlrpc.php specifically, the Cloudflare rule is: (http.request.uri.path eq "/xmlrpc.php") Action: Block. This stops both the attack and the amplification vector permanently.

Structural DDoS Prevention: The Configuration That Stays in Place

Once the immediate attack is stopped, three configuration items prevent recurrence. First, block or restrict /xmlrpc.php if you are not actively using the WordPress mobile app, Jetpack, or remote publishing tools:

Add to your .htaccess file in the WordPress root directory:
# Block XML-RPC entirely (if you do not use it)
<Files xmlrpc.php>
    Order deny,allow
    Deny from all
</Files>

# Alternative: limit to specific IPs only (if you do use it)
<Files xmlrpc.php>
    Order deny,allow
    Deny from all
    Allow from 203.0.113.0/24  # your office IP range
</Files>

Second, set up a Cloudflare Rate Limiting rule on /wp-login.php: path contains /wp-login.php, threshold 5 requests per 10 minutes per IP, action Block for 1 hour. This stops both DDoS targeting the login endpoint and brute force attacks in a single rule.

Third, if you are on Cloudflare Pro, enable the OWASP Core Rule Set under Security then WAF then Managed Rules. This set of rules blocks the HTTP patterns characteristic of Layer 7 DDoS tools (specific user agent strings, header patterns, and request structures used by common DDoS tools). For context on how CDN-level protection integrates with your hosting stack, the CDN guide covers the full Cloudflare configuration for WordPress performance and security.

Should you upgrade to Cloudflare Pro for DDoS protection?

If you have never been attacked: Free tier is sufficient. Under Attack Mode handles most situations when needed.

If you have been attacked once: Pro ($20/month) adds OWASP WAF rules and better bot management. Worth it for any revenue-generating site.

If you are under sustained or repeated attack: Pro WAF rules plus Bot Management give you granular control over traffic patterns. The cost of one hour of downtime on most WooCommerce sites exceeds the annual Pro cost.

Brute Force Attacks: How to Read Your Logs and Stop Them in Under 10 Minutes

Brute force detection is easier than any other WordPress attack type because the evidence is in your server logs in a format you can read in seconds. Every login attempt creates a line in your Apache or Nginx access log. Hundreds of those lines pointing to /wp-login.php from rotating IP addresses within seconds of each other is not ambiguous. You do not need a security plugin to see it.

Brute force attack server log: hundreds of POST requests to wp-login.php from multiple IPs within seconds, HTTP 200 response codes indicating active credential testing

Reading Your Access Logs: The Commands That Surface Brute Force in Real Time

Run these on your server via SSH to detect active brute force attempts:
# Count POST requests to wp-login.php in today's access log (Apache/cPanel hosts)
grep "POST /wp-login.php" /var/log/apache2/access.log | wc -l

# See the most recent attempts with timestamps and source IPs:
grep "POST /wp-login.php" /var/log/apache2/access.log | tail -50

# For Nginx:
grep "POST /wp-login.php" /var/log/nginx/access.log | tail -50

# Count by source IP — identifies whether it is concentrated or distributed:
grep "POST /wp-login.php" /var/log/apache2/access.log | \
  awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# Real-time monitoring (watch new attempts arrive):
tail -f /var/log/apache2/access.log | grep "wp-login.php"

What Brute Force Looks Like in Logs

This is what a brute force attack looks like in raw access logs. Every line is a separate login attempt. Note the timestamps: three attempts per second from three different IP addresses. This is a distributed brute force — rotating IPs to avoid per-IP rate limiting. A non-distributed attack from one IP looks the same except all source IPs are identical.

Example access log entries from a brute force attack in progress:
123.45.67.89  - - [08/May/2026:03:15:23 +0000] "POST /wp-login.php HTTP/1.1" 200 3478
198.51.100.10 - - [08/May/2026:03:15:24 +0000] "POST /wp-login.php HTTP/1.1" 200 3478
203.0.113.42  - - [08/May/2026:03:15:24 +0000] "POST /wp-login.php HTTP/1.1" 200 3478
123.45.67.90  - - [08/May/2026:03:15:25 +0000] "POST /wp-login.php HTTP/1.1" 200 3478
198.51.100.11 - - [08/May/2026:03:15:25 +0000] "POST /wp-login.php HTTP/1.1" 200 3478
# ... hundreds more lines at 2-5 per second

The HTTP 200 response does not mean the login succeeded. It means the login page loaded and was processed. A successful brute force login typically generates a 302 redirect to /wp-admin/ in the logs. Any 302 after POST /wp-login.php during a brute force window is a compromised credential and requires immediate investigation.

Attack VariantMethodSuccess RateSpecific Counter
Credential stuffingStolen breach list (email:password pairs)High success rate because credentials were real on another site2FA makes stolen credentials useless. Password reuse is the underlying risk factor.
Dictionary attackCommon password wordlistLow success rate against strong passwords20+ character random passwords are immune. Dictionary attacks fail completely.
Username enumeration firstWordPress exposes usernames via author pages and /wp-json/Reduces password space — attacker knows the username before guessing beginsHide usernames in REST API. Never use 'admin'. WordFence blocks enumeration attempts.
Distributed brute forceThousands of IPs each making 1-2 attemptsEvades per-IP rate limitingCAPTCHA challenge on login. Cloudflare Bot Management (Pro) detects distributed patterns.

Stopping Active Brute Force: The Response Sequence

Immediate actions while the attack is in progress:

  1. Block attacking IP(s) in Cloudflare: Security then Firewall Rules then Create Rule. If the attack is from one IP: (ip.src eq 123.45.67.89) Block. If from a /24 subnet: (ip.src in {123.45.67.0/24}) Block. Activates in under 30 seconds.
  2. Enable WordFence rate limiting: WordFence Dashboard then Firewall then Brute Force Protection. Set lock out after 3-5 failures. Lock out duration: 4 hours minimum.
  3. Check for successful logins: In WordPress admin, Users then All Users. Sort by Last Login if WordFence is installed. Look for admin accounts you did not create. Any unrecognized accounts are backdoors.

Structural Brute Force Prevention

Once the immediate attack is handled, four configurations prevent recurrence without ongoing manual effort:

1. Two-factor authentication. This is the single most effective measure. A bot can guess a correct password and still cannot log in without the second factor. Install WP 2FA (free), enforce for all admin roles, use TOTP (Google Authenticator compatible). Setup takes 10 minutes and makes credential attacks permanently irrelevant.

2. Strong unique passwords. A 20+ character random password defeats every dictionary and brute force variant. WordPress's built-in password generator creates these. Save them in a password manager. The number of brute force attacks that succeed against a strong random password is effectively zero.

3. Username hardening. Never use "admin" as a WordPress username. Create any other username during installation. Brute force bots target "admin" first because it is the default. An attacker who must guess both username and password faces a much larger search space.

4. Cloudflare rate limiting rule. In Cloudflare Dashboard, go to Security then WAF then Rate Limiting Rules, then Create. Field: URI Path, Contains: /wp-login.php. Threshold: 5 requests per 600 seconds per IP. Action: Block for 3600 seconds. This stops every brute force variant that does not use extreme IP rotation, before the requests ever reach your server.

For sites running WooCommerce or membership plugins where brute force against customer accounts is also a concern, the same Cloudflare rate limiting rule on /wp-login.php covers customer logins as well. The 500 and 503 errors guide covers the server-side symptoms that brute force load causes, which are often misdiagnosed as hosting problems rather than attack-related resource exhaustion.

SQL Injection on WordPress: How to Detect It in Your Database and Prevent It

SQL injection through WordPress core is close to impossible because the WordPress database abstraction layer uses parameterized queries correctly. This does not protect you. SQL injection through WordPress plugins is the primary attack vector, and it has been responsible for the majority of large-scale WordPress compromises in the past three years. The gap between "WordPress core is secure" and "your WordPress installation is secure" is the gap filled by your installed plugins.

SQL injection attack flow: malicious UNION SELECT payload through WordPress form field, bypassing sanitization, reaching MySQL database and returning wp_users table data

What SQL Injection Actually Does to a WordPress Database

An attacker sends a crafted URL parameter or form submission containing SQL syntax. If the receiving plugin builds its database query by string concatenation with user input instead of using parameterized queries, the SQL syntax executes as a real database command. The scope of what is possible depends on the database user's privileges. A WordPress database user with SELECT, INSERT, UPDATE, DELETE privileges can be exploited to read the entire users table (getting password hashes), inject spam content into posts, and change the siteurl to redirect all visitors to an attacker-controlled domain. A database user with DROP privileges can delete tables. Principle of least privilege matters here.

Attack VectorRisk LevelWhySpecific Mitigation
Outdated pluginsVery HighWPScan tracks 25,000+ plugin vulnerabilities. Unpatched plugins are the primary SQLi vector.Update all plugins immediately. WPScan --enumerate vp shows vulnerable plugins in your installation.
Custom functions.php codeHigh if unescapedAny $wpdb->query() using unescaped user input is injectable.Use $wpdb->prepare() for all queries with user input. Never concatenate $_GET/$_POST into queries.
WordPress coreNear zeroWordPress core correctly sanitizes all inputs using $wpdb->prepare().Keep core updated to receive security patches promptly.
Third-party PHP filesVariableCustom scripts or old include files outside WordPress context often skip sanitization entirely.Audit all .php files outside wp-core. Remove anything not actively maintained.

Detecting SQLi Attempts in Your Server Logs

Search your access logs for SQL injection attempt patterns:
# Detect common SQLi patterns in Apache logs:
grep -Ei "(UNION SELECT|SELECT.*FROM|INSERT INTO|DROP TABLE|OR 1=1|' OR '|1=1--)" \
  /var/log/apache2/access.log | tail -30

# Check for base64-encoded SQLi payloads (attackers encode to bypass simple filters):
grep -Ei "(base64_decode|eval\(|fromcharcode|0x[0-9a-f]{10,})" \
  /var/log/apache2/access.log | tail -20

# Check specific vulnerable-looking parameters:
grep -Ei "(\?id=|&id=|&cat=|\?page=|&page=)" /var/log/apache2/access.log | \
  grep -Ei "('|--|UNION|SELECT)" | tail -20

Detecting SQLi That Already Succeeded: The Database Checks

Log analysis shows attempts. Database analysis shows success. If you are investigating a suspected injection, run these queries in phpMyAdmin. They surface the most common SQLi-injected content patterns that appear across real compromised WordPress databases.

Run these in phpMyAdmin SQL tab — replace wp_ with your table prefix:
-- Check for injected code in wp_options (most common target):
SELECT option_name, LEFT(option_value, 200) 
FROM wp_options 
WHERE option_value LIKE '%base64_decode%' 
   OR option_value LIKE '%eval(%'
   OR option_value LIKE '%\x%'
   OR option_value LIKE '%/bin/sh%';

-- Check for injected scripts in published posts:
SELECT ID, post_title, LEFT(post_content, 300)
FROM wp_posts 
WHERE post_content LIKE '%<script%'
  AND post_status = 'publish';

-- Check for unauthorized admin accounts:
SELECT u.user_login, u.user_email, u.user_registered, m.meta_value as role
FROM wp_users u
JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities'
  AND m.meta_value LIKE '%administrator%'
ORDER BY u.user_registered DESC;

-- Check if siteurl or home options were modified:
SELECT option_name, option_value 
FROM wp_options 
WHERE option_name IN ('siteurl', 'home', 'blogname', 'admin_email');
-- Verify these match your actual domain and admin email

WPScan: The Tool That Finds Your Vulnerable Plugins Before Attackers Do

WPScan is a WordPress-specific security scanner that checks your installed plugins against a database of 25,000+ known vulnerabilities. Running it monthly costs nothing (free tier available at wpscan.com), finds vulnerable plugins you may not know about, and catches vulnerabilities before they are actively exploited. After a compromise, running it against the recovered installation confirms whether the attack vector was a known vulnerability you could have patched.

WPScan commands for plugin vulnerability detection:
# Install WPScan (requires Ruby):
gem install wpscan

# Scan for vulnerable plugins (vp = vulnerable plugins only):
wpscan --url https://yourdomain.com --enumerate vp

# Full scan including users and themes:
wpscan --url https://yourdomain.com --enumerate vp,vt,u

# With API token (enables current vulnerability database — free tier: 25 requests/day):
wpscan --url https://yourdomain.com --enumerate vp --api-token YOUR_TOKEN

# Example output for a vulnerable plugin:
# [!] Title: Contact Form 7 < 5.x.x - SQL Injection via 'serial' parameter
#     Fixed in: 5.x.x
#     References:
#      - https://wpscan.com/vulnerability/xxxxx
#      - CVE-2026-XXXXX

SQLi Prevention: The Four-Layer Approach

First, keep all plugins updated without exception. Every unpatched plugin vulnerability in WPScan's database has a fix available — the fix is the update. The attack that succeeds is almost always against a plugin version that had a patch released weeks or months before the attack.

Second, Cloudflare WAF with OWASP rules (Pro plan) blocks SQL injection patterns at the network edge before they reach WordPress. The OWASP Core Rule Set includes rules specifically targeting UNION SELECT, string concatenation attacks, and blind SQLi time-based payloads.

Third, restrict database user privileges. Your WordPress database user should have exactly SELECT, INSERT, UPDATE, DELETE on the WordPress database. Not DROP, not CREATE, not ALTER, not FILE. A database user with only these four privileges cannot drop tables or modify database structure even if an injection succeeds. Set this in your host's phpMyAdmin under Privileges.

Fourth, change the default table prefix from wp_ to a random string. Most automated SQLi attacks are scripted against the default prefix. Tools like Better WP Security (now iThemes Security) or a manual wp-config.php edit accomplish this. This change stops automated scripts. It does not stop a manually operated or adaptive attack.

The underlying principle: SQLi is the attack type most connected to your plugin maintenance discipline. A site running fully updated plugins with Cloudflare WAF active has eliminated over 95% of its SQLi attack surface. The managed hosting guide covers how managed WordPress hosts handle automatic security patching, which matters here because managed hosts apply WordPress core security patches within hours of release.

Cross-Site Scripting (XSS): How to Detect Injected JavaScript and Close the Vector

XSS is the attack that gets noticed by your visitors before you notice it yourself. Someone emails you saying your site redirected them to a pharmaceutical spam page. Your Google Search Console sends a malware alert. A visitor reports unusual browser warnings. By the time you are reading those reports, the injected script may have been executing for hours or days, running in the browsers of everyone who visited your site during that period.

Stored XSS attack: malicious JavaScript injected into WordPress comment or post content, executing in visitor browser, stealing session cookies and redirecting to attacker domain

Stored XSS vs Reflected XSS: The Detection Difference

Stored XSS means the malicious script is saved in your database — in post content, comments, custom fields, or the wp_options table. It executes for every visitor who loads the page containing it. Detection requires searching the database. Reflected XSS means the malicious script is in a URL parameter and only executes when someone clicks a specially crafted link. The attacker sends the link to targets; no database entry is needed. Detection requires monitoring for unusual URL patterns in your logs.

For WordPress sites, stored XSS is the greater risk because it can sit undetected in the database and affect thousands of visitors before anyone identifies it. Reflected XSS is more commonly used in phishing campaigns targeting specific users rather than site-wide infections.

XSS Impact TypeWhat HappensRisk LevelTechnical Defense
Session cookie theftAttacker captures logged-in admin cookie and hijacks session without needing passwordHIGHHttpOnly cookie flag prevents JavaScript from reading session cookies. WordPress sets this by default.
Admin account creationInjected script POSTs to wp-admin to create a new administrator accountCRITICALContent Security Policy restricts which domains can receive form posts. Nonce validation blocks forged requests.
Visitor redirectionScript redirects visitors to malware distribution or phishing pagesHIGHDetected by Google Safe Browsing within hours to days. Search Console sends email alerts.
Keylogging on formsScript captures form inputs including payment details if WooCommerce is presentCRITICALPCI DSS compliance requires CSP headers on checkout pages. Subresource Integrity for third-party scripts.
Cryptomining injectionScript runs CPU-intensive mining in visitor browsers, degrading performanceMEDIUMCSP script-src whitelist prevents unauthorized script execution from injected sources.

Detecting Injected JavaScript: Where to Look

If you suspect XSS injection, these are the four places where injected JavaScript appears in WordPress:

Database queries to find injected JavaScript content:
-- Check post content for injected script tags:
SELECT ID, post_title, post_type, post_status
FROM wp_posts 
WHERE post_content LIKE '%<script%'
  AND post_status IN ('publish', 'draft')
ORDER BY post_modified DESC;

-- Check wp_options for injected scripts (targets widget content, theme settings):
SELECT option_name 
FROM wp_options 
WHERE option_value LIKE '%<script%'
   OR option_value LIKE '%javascript:%'
   OR option_value LIKE '%document.write%'
   OR option_value LIKE '%eval(%';

-- Check comments for stored XSS (if comments are not moderated):
SELECT comment_ID, comment_author, comment_content
FROM wp_comments
WHERE comment_content LIKE '%<script%'
   OR comment_content LIKE '%javascript:%'
   AND comment_approved = '1';

-- Check custom post meta for injected content:
SELECT post_id, meta_key, LEFT(meta_value, 200)
FROM wp_postmeta
WHERE meta_value LIKE '%<script%'
   OR meta_value LIKE '%eval(%';

Beyond the database, check your theme files directly. XSS attacks that compromise file write access (often through a combination of SQLi and file write privileges) inject JavaScript directly into template files. Check these files for anything you did not write:

Server-side checks for injected JavaScript in theme and plugin files:
# Find recently modified PHP files in your WordPress installation:
find /path/to/wordpress -name "*.php" -newer /path/to/wordpress/wp-config.php \
  -not -path "*/wp-content/uploads/*" | head -30

# Search for common injected script patterns in theme files:
grep -rl "eval(base64_decode" /path/to/wordpress/wp-content/themes/
grep -rl "document.write" /path/to/wordpress/wp-content/themes/
grep -rl "<script src=.*http" /path/to/wordpress/wp-content/themes/

# Find PHP files in uploads (should never exist — these are backdoors):
find /path/to/wordpress/wp-content/uploads -name "*.php"

Browser-Side Detection: What Visitors and DevTools Show You

Open Chrome DevTools (F12) on your site's pages and check three places. First, the Console tab: unexpected JavaScript errors or loading messages from domains you do not recognize are injected script indicators. Second, the Network tab: filter by Script and look for requests to external domains you did not include in your theme or plugins. Third, the Sources tab: search for external script tags in the page source that reference domains you do not control.

Google Search Console is your most reliable external detection tool. Under Security and Manual Actions then Security Issues, Google flags sites it has identified as serving malware or hosting phishing pages. The notification arrives via email within hours to days of Google detecting an infection, often before site owners notice anything wrong themselves.

XSS Prevention: The Three Layers That Work Together

Layer 1: HTTP Security Headers. Add these to your .htaccess or via Cloudflare Transform Rules. They instruct browsers about your site's security expectations and block entire categories of attack:

Security headers to add to your .htaccess file:
<IfModule mod_headers.c>
    # Prevents clickjacking — site cannot be embedded in iframes on other domains
    Header set X-Frame-Options "SAMEORIGIN"
    
    # Prevents MIME-type sniffing that enables certain injection attacks
    Header set X-Content-Type-Options "nosniff"
    
    # Legacy XSS filter for older browsers
    Header set X-XSS-Protection "1; mode=block"
    
    # Controls how much referrer info is sent in requests
    Header set Referrer-Policy "no-referrer-when-downgrade"
    
    # Content Security Policy — customize this for your specific scripts
    # Start permissive, then tighten based on what you actually load:
    Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
</IfModule>

Layer 2: Cloudflare WAF OWASP rules. The OWASP Core Rule Set (available on Cloudflare Pro) includes XSS-specific rules that detect and block common injection patterns in URL parameters, form fields, and HTTP headers before they reach WordPress. This is the most effective passive protection against reflected XSS attempts.

Layer 3: Plugin code hygiene. If you write any custom PHP for your site — in functions.php, custom plugins, or child themes — use WordPress's output escaping functions on anything that contains user data. The rule: never output a variable to the page without passing it through esc_html(), esc_attr(), esc_url(), or wp_kses_post() depending on the context. A single unescaped echo $_GET['query'] in a theme file is a stored XSS waiting to happen.

For comment spam that includes XSS payloads, Akismet (free for personal sites) and Cloudflare Turnstile (free CAPTCHA) filter the majority of automated comment submissions before they reach WordPress's database. This removes the automated stored-XSS-via-comments vector entirely on most sites.

The SSL and HTTPS guide covers how HTTPS itself is a prerequisite for effective XSS prevention — cookie theft via XSS requires the cookie to be transmitted over HTTP for the attack to be complete. HTTPS with the Secure cookie flag closes that path.

The WordPress Security Stack: Six Layers That Together Cover All Four Threats

Installing a security plugin is not a security strategy. It is one tool in one layer. The difference between a site that gets compromised and one that does not is usually not the presence of WordFence — it is whether the site owner implemented the full layered stack or stopped at the plugin install. The six layers below each cover a different attack surface. Missing one layer leaves a gap that the others cannot close.

WordPress security stack: six layers from Cloudflare edge network DDoS protection through Wordfence application firewall to authentication hardening, updates, backups, and monitoring, with attack types blocked at each layer
DDoS protectionLayer 1: Cloudflare
WAF / firewallLayers 1-2: Cloudflare + WordFence
Brute forceLayers 1, 2, 3
SQLi / XSSLayers 1, 2, 4

Layer 1: Cloudflare (Network and Edge Layer)

A DNS-level proxy sitting between the internet and your server is the security configuration that costs the least and blocks the most. Cloudflare's free tier handles volumetric DDoS because attacks are absorbed at Cloudflare's edge before a packet reaches your server. Your origin IP is hidden from DNS lookups, eliminating direct-to-origin bypass attacks. HTTPS is enforced. Basic bot fingerprinting runs on every request.

Cloudflare Pro at $20/month adds the OWASP WAF rule set, which is the difference between having a rate limiter and having a rule-based firewall that blocks SQLi, XSS, and protocol-level attack patterns. For any WordPress site generating revenue, Pro is the correct default. The WAF blocks attack patterns that WordFence would otherwise see and process — meaning it reduces load on your server in addition to blocking attacks. Configure: Always Use HTTPS enabled, Bot Fight Mode enabled, rate limiting rule on /wp-login.php as described in the brute force section.

Layer 2: WordFence or MalCare (Application Layer)

Where Cloudflare operates before your server, WordFence operates inside WordPress. It intercepts requests after they arrive at PHP but before WordPress processes them. The practical difference: Cloudflare catches known attack signatures at the network level; WordFence catches attacks that made it through the network layer and adds WordPress-specific context (is this IP trying to log in as admin? Has it triggered three different rule violations in the past hour?).

ToolWhat It CoversKey Configuration Note
Cloudflare (free)DDoS absorption, basic bot blocking, HTTPS redirect, origin IP protectionEnable immediately, zero cost. Proxies all HTTP/S traffic through Cloudflare's edge network.
Cloudflare (Pro, $20/mo)OWASP WAF rules (blocks SQLi + XSS patterns), advanced bot management, rate limiting rulesPro WAF rules block the majority of automated SQLi and XSS attempts before reaching WordPress.
WordFence (free)Application firewall, brute force protection, malware scanner, 2FA, 30-day delayed rule updatesFree tier is functional. The 30-day delay on firewall rules means new attack patterns take a month to block.
WordFence Premium ($119/yr)Real-time firewall signatures, real-time IP reputation, immediate rule updatesReal-time updates matter during active campaigns targeting new plugin CVEs. Premium pays for itself once.
MalCare ($99/yr)Superior malware detection via cloud scanning, one-click auto-clean, login protectionBetter malware detection than WordFence. Use this OR WordFence, never both simultaneously.
WP 2FA (free)Two-factor authentication for all admin accounts, TOTP or email codes, role-based enforcementMakes brute force and credential stuffing irrelevant. Even correct passwords fail without the second factor.
UpdraftPlus (free)Daily automated backups to Google Drive, S3, Dropbox, or SFTP, 30-day retentionBackup is the recovery layer. Without it, a successful attack means complete rebuild from scratch.

The choice between WordFence and MalCare comes down to what you prioritize. WordFence is stronger on active firewall protection and blocking. MalCare is stronger on malware detection and cleanup. After reviewing both across multiple compromised sites, I use WordFence for new hardened installations where the goal is prevention, and MalCare when the goal is cleaning up after an existing compromise. Install one. Not both. Running two simultaneous security plugins creates false confidence and genuine performance overhead without additive security.

Layer 3: Authentication Hardening

Authentication hardening is the layer most directly responsible for making brute force attacks irrelevant. Three components:

Two-factor authentication via WP 2FA (free plugin). Enforce for all admin and editor roles. Use TOTP (time-based one-time password) over email-based codes — TOTP works without email delivery and cannot be intercepted by a compromised email account. Setup: install WP 2FA, configure required roles, require users to set up 2FA on next login.

Strong unique passwords are the baseline. WordPress generates strong passwords in the user edit screen. The requirement: no shared passwords across sites, no passwords that appear in public breach databases, 20+ characters for admin accounts.

Login URL change via WPS Hide Login (optional). Stops automated scanners completely. Does not stop targeted attacks. Set this after everything else is in place, not instead of it. The plugin changes the login URL without touching .htaccess and works on most shared and VPS hosts without configuration.

Layer 4: Update Discipline (The Layer With the Highest ROI)

More WordPress sites get compromised through outdated software than through all other attack vectors combined. The Sucuri 2024 website threat report found that over 60% of infected WordPress sites were running outdated software at the time of compromise. The update that patches a vulnerability releases the information needed to exploit the old version. Attackers scan for unpatched installs within days of a CVE being published.

Monthly WPScan audit command to find vulnerable plugins before attackers do:
# Enumerate vulnerable plugins against WPScan's database (free API token required):
wpscan --url https://yourdomain.com --enumerate vp --api-token YOUR_FREE_TOKEN

# To get a free API token: register at wpscan.com (25 API calls/day free)

# Run this monthly. Add it to a calendar reminder.
# Output example:
# [i] Plugin(s) Identified:
# [+] contact-form-7
#  | Version: 5.8.0 (Outdated, latest is 5.9.8)
#  | [!] 1 vulnerability identified:
#  | [!] Title: Contact Form 7 < 5.9.0 - Unrestricted File Upload
# 
# Action: update contact-form-7 to 5.9.8 immediately.

Layer 5: Backups (The Recovery Layer)

A backup is not part of attack prevention. It is what determines whether a successful attack costs you a few hours or costs you everything. The practical configuration: UpdraftPlus (free), daily backups, remote storage to Google Drive or Dropbox (not on the same server), 30 days of retention. The test that most WordPress site owners never run: download a backup and restore it to a staging environment. Find out now whether your restore process works, not during the emergency when you need it to work.

One number worth understanding: a daily backup schedule gives you a Recovery Point Objective (RPO) of up to 24 hours. A compromise discovered on Tuesday afternoon means recovering to Monday's state, losing up to 24 hours of content and orders. For WooCommerce sites with consistent daily revenue, twice-daily backups reduce that RPO to 12 hours. For sites with very active content, hourly backups via UpdraftPlus premium or a host-side automated backup system make sense. The backup and restore guide covers the full configuration including multiple storage destinations and restore testing procedures.

Layer 6: Monitoring (Early Warning Before Damage Is Done)

Most WordPress compromises go undetected for days to weeks because site owners have no alert system in place. Three monitoring tools cover the detection gap:

UptimeRobot (free tier) checks your site every 5 minutes and sends an email within one check cycle of downtime. Most DDoS attacks and some successful compromises take the site offline — this catches them within 5 minutes. WP Activity Log (free plugin) records every admin action: who changed what setting, who installed which plugin, who created which user account. After a compromise, the audit log is often the fastest path to identifying how access was gained. Google Search Console is the external intelligence layer — Google's crawler detects malware and phishing injections, sends email alerts to verified site owners, and flags issues in the Security Issues tab before most site owners notice anything wrong through their own monitoring.

WordPress Security Hardening Checklist

This checklist represents the configuration state that, once implemented, stops over 99% of automated WordPress attacks. It is organized by category for implementation order. Start with Authentication and Network — these have the highest impact per hour of effort. Files and Monitoring can follow in a second session.

ItemCategoryWhy It Matters
No 'admin' username in useAuthenticationBrute force bots target 'admin' first. Any other username immediately eliminates the most common attack vector.
All admin passwords 20+ charactersAuthenticationDictionary and brute force attacks fail completely against random 20+ character passwords.
2FA enabled for all admin accountsAuthenticationCredential stuffing becomes irrelevant. Correct password without second factor cannot log in.
Login attempts limited: 5 then lockoutAuthenticationWordFence or Cloudflare rate limiting. Stops brute force before enough attempts to matter.
/wp-content/uploads/ blocks PHP executionFilesAttackers who upload a .php shell cannot execute it. Most critical file permission hardening.
wp-config.php permissions: 400 or 440FilesOnly server process can read. Not world-readable. Direct file access attacks fail.
No inactive plugins or themes installedFilesDeactivated plugins still present are still attack surface. Delete, do not just deactivate.
WordPress core / plugins / themes currentUpdatesMajority of successful WordPress attacks exploit known, patched vulnerabilities in outdated software.
Cloudflare active (minimum free)NetworkHides origin IP. DDoS absorption. HTTPS enforcement. Bot detection. All free.
Rate limiting on /wp-login.phpNetwork5 requests per 10 minutes per IP in Cloudflare Rate Limiting. Stops brute force at the edge.
/xmlrpc.php blocked (if unused)NetworkEliminates amplification attack vector. Block in .htaccess or Cloudflare firewall rule.
Security headers setNetworkX-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy in .htaccess or Cloudflare.
UptimeRobot monitoring activeMonitoring5-minute intervals. Email alert within 5 minutes of downtime. Attack detection in near-real-time.
WordFence or MalCare installedMonitoringApplication-layer firewall and malware scanner. One or the other, not both.
Google Search Console verified + alerts onMonitoringGoogle's malware detection triggers email alerts before most site owners notice the compromise.
Daily backup to off-site storage activeRecoveryGoogle Drive, S3, or Dropbox. On-server backup is not off-site. Ransomware destroys both.
Backup restore tested in last 90 daysRecoveryUntested backups have a real failure rate. Know before you need it, not during the emergency.

The One Configuration Most Sites Are Missing

After reviewing the security configuration of dozens of WordPress sites, the single most consistently missing item is two-factor authentication. Strong passwords are more common. Cloudflare is often present. Plugin updates are mostly happening. But 2FA on admin accounts is absent in the majority of setups I have seen. The reason it matters more than any other single item: every other measure assumes that if someone has the right password, they should be able to log in. 2FA breaks that assumption at the application layer. A compromised password is useless without the second factor. Given that credential databases from other services are routinely tested against WordPress logins (credential stuffing), 2FA is the direct counter to the attack method that actually succeeds most often.

Install WP 2FA, enforce it for all admin roles, and document the backup codes in a secure location. This 10-minute configuration eliminates the majority of account takeover risk. For context on how authentication hardening connects to your overall hosting security posture, the uptime and downtime guide covers the full failure taxonomy of what takes WordPress sites offline, of which account takeover is one of the more destructive categories.

WordPress Security Myths That Lead to Real Vulnerabilities

These myths are not harmless misconceptions. Each one leads to a specific under-investment in the layer of security it concerns, and each has a corresponding attack that exploits the gap the myth creates.

"My site is too small to be targeted."

FALSE.

Automated attack scripts do not select targets based on site size. They scan every reachable WordPress installation. A WPScan enumeration of your domain, a brute force sequence against your login, and a SQLi probe through your contact form all happen whether your site gets 50 visitors/month or 50,000. The distinction between targeted and untargeted attacks matters — large sites get targeted attacks, small sites get automated ones. Both are real. Automated attacks are responsible for the majority of WordPress compromises across all site sizes.

"I installed WordFence so I am secure."

INCOMPLETE.

WordFence is Layer 2 of a six-layer stack. It handles application-layer threats after they arrive at your server. It does not absorb a volumetric DDoS (needs Cloudflare), it does not prevent you from using "admin" as a username (needs authentication hardening), it does not protect you from a known vulnerability in an outdated plugin you have not updated (needs update discipline), and it cannot recover from a successful compromise without a backup (needs backup layer). WordFence installed without the other five layers is better than nothing by a meaningful margin. It is not a complete defense.

"WordPress is inherently insecure."

WRONG FRAMING.

WordPress core has a dedicated security team, a rapid patch release process, and a strong track record for core vulnerability response. The security issues that affect WordPress sites are almost universally in plugins, themes, and server configurations — not in WordPress core. The correct statement: "WordPress with a poor plugin selection, outdated components, and no hardening is insecure." WordPress with the stack described in this guide, running current versions, with plugin selection limited to actively maintained options, is as secure as any comparable CMS at its scale.

"My hosting provider handles security."

PARTIALLY TRUE, mostly FALSE.

Your hosting provider secures the server infrastructure: the operating system, server software, network layer, and physical hardware. They do not secure your WordPress application: the plugins you install, the passwords you choose, the 2FA you enable or do not enable, the updates you apply. The line is clear: the server is the host's responsibility, the application is yours. Managed WordPress hosts (Cloudways, ScalaHosting managed VPS) go further and apply WordPress core patches automatically and may scan for malware — but they still cannot enforce your plugin update discipline or enable 2FA for you.

"Changing the login URL makes my site secure."

OVERRATED.

A custom login URL stops automated scanners that target the default /wp-login.php path. It does not stop a motivated attacker who can find your login URL through page source inspection, Wappalyzer, Google cache, or Wayback Machine. It adds friction, not a barrier. The order of priority: strong passwords and 2FA first, then rate limiting, then custom login URL. Sites that change the login URL without strong passwords and 2FA are not more secure than sites with strong passwords and 2FA at the default URL. The measure is useful. It is not a primary defense.

Where to Go Next

This guide covers detection, prevention, and the full hardening stack for all four attack types. The most common question after implementing this stack is what to do after a successful compromise — that is a different process covered separately and involves file auditing, database cleanup, credential rotation, and backdoor elimination before going back online. For the server-side symptoms that attacks cause (CPU spikes, 500 errors, memory exhaustion), the 500 and 503 errors guide connects attack signatures to the specific error codes they produce and the server-side responses. For the CDN layer in more detail, including how Cloudflare configuration interacts with WordPress caching, the CDN guide covers the full setup sequence. If your hosting environment itself needs to be evaluated for security posture, the managed versus unmanaged VPS guide covers how managed hosting changes the server-layer security baseline you are starting from. For the data protection and backup layer specifically, the backup and restore guide details the configuration that ensures recovery is possible when prevention fails.

WordPress Security Threats FAQ

How do I know if my WordPress site is being DDoS attacked right now?

Three signals confirm an active DDoS. First, server CPU is at 100% and memory is exhausted, but your traffic analytics show no matching spike in legitimate visitors. Second, netstat or ss commands on your server show hundreds or thousands of simultaneous TCP connections from many different IP addresses. Third, Cloudflare Analytics shows a spike in requests with a high percentage of 5XX (server error) responses — the server is failing to respond to volume it cannot absorb. The fastest response is to enable Cloudflare Under Attack Mode in Security > Settings, which adds a JavaScript challenge to all visitors. Bots cannot pass it. Legitimate visitors see a 5-second loading screen. This stops most Layer 7 DDoS attacks within 60 seconds of activation.

What is the difference between a brute force attack and a DDoS on WordPress?

A brute force attack targets your login credentials. It sends POST requests to /wp-login.php trying to guess usernames and passwords. The goal is account access. A DDoS targets your server's availability. It sends requests designed to overwhelm your server's capacity to respond — the goal is downtime, not access. The two can look similar from a server perspective because both generate high request volume to /wp-login.php. The distinction: brute force comes from a small number of IPs sending many requests each; a distributed brute force comes from many IPs sending few requests each, which overlaps with DDoS patterns. The practical response is similar at first — rate limiting and Cloudflare — but brute force requires the additional authentication hardening layer (strong passwords, 2FA) that DDoS does not.

How do I tell if my WordPress database was SQL injected?

Four indicators suggest successful SQL injection. First, new admin accounts you did not create appear in wp-users. Second, post content contains spam links pointing to external domains. Third, the siteurl or home values in wp_options point to a domain you do not own. Fourth, wp_options contains rows with base64-encoded values in unusual option names. Check directly in phpMyAdmin with: SELECT option_name, option_value FROM wp_options WHERE option_value LIKE '%base64%' OR option_value LIKE '%eval(%'. For injected scripts in posts: SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%