WordPress security audits mostly happen when something goes wrong. You tend to notice your site has been flagged by Google or that it has been hacked (Japanese SEO spam, pharma hack, redirect hack, etc.).
When these or any other types of attacks happen, the audit becomes reactive. As a security plugin provider, we always recommend going for security audits before anything breaks. That way, you can catch vulnerabilities early, before attackers penetrate further into your site.
How do you perform a WordPress security audit? Well, this guide is the answer. We wrote it for you in detail and in the easiest way possible, so you can easily follow along.
Table of Contents
- What a WordPress security audit actually covers
- Before you start: what you need
- Phase 1: User accounts and access control
- Phase 2: WordPress core, plugins, and themes
- Phase 3: File system and permissions
- Phase 4: Database audit
- Phase 5: Login and authentication hardening
- Phase 6: Server and hosting configuration
- Phase 7: Malware and indicator-of-compromise scan
- Phase 8: Logging and monitoring
- How often to run a full audit
- FAQ
1. What a WordPress Security Audit Actually Covers
A security audit is a systematic review of every layer of your site’s attack surface: who has access, what software is running, how the server is configured, whether any malware is present, and whether you’d know if something went wrong.
A complete audit covers these areas:
- User accounts and privilege levels
- Software versions and known vulnerabilities
- File system state and permissions
- Database contents and user accounts
- Authentication configuration
- Server and hosting configuration
- Malware and indicators of compromise
- Logging and alerting coverage
Each section below is a self-contained phase. Run them in order the first time. For subsequent audits, you can run phases independently as spot checks.
2. Before You Start: What You Need
Access requirements before beginning:
- WordPress admin credentials
- SFTP or SSH access to the server (required for Phases 3, 4, 7)
- Database access (phpMyAdmin or direct MySQL, required for Phase 4)
- Hosting control panel access (required for Phase 6)
- A text file or spreadsheet to log findings
If you don’t have SSH access, you can complete Phases 1, 2, 5, 7, and 8 through the WordPress admin and a security plugin. Phases 3, 4, and 6 require server-level access and are worth requesting from your hosting provider or developer if you don’t have it directly.
Timing: run the audit on a staging clone if possible, or during low-traffic hours. Some checks (particularly file integrity scans) put load on the server.
3. Phase 1: User Accounts and Access Control
User account hygiene is one of the most important parts of a WordPress security audit. Weak passwords, compromised accounts, and users with more access than they actually need are some of the most common reasons WordPress sites get hacked.
Check 1: Audit every administrator
In WordPress admin, go to Users → All Users, filter by Administrator role. For each administrator account, verify:
- Do you recognize this account?
- Is there a real person associated with it?
- Does this person still need administrator access?
- When was this account created?
Any administrator you don’t recognize, or that was created without your knowledge, is a red flag. Delete it immediately and investigate when it was created and what it did.
You can also pull this directly from the database:
SELECT
u.ID,
u.user_login,
u.user_email,
u.user_registered
FROM wp_users u
INNER 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 2: Dormant accounts
Accounts that haven’t logged in for 90+ days represent unmonitored access. A former employee’s account with a reused password is a credential-stuffing target. Review every dormant account and either confirm it’s still needed or delete it.
SELECT user_login, user_email, user_registered
FROM wp_users
WHERE user_login NOT IN (
SELECT DISTINCT user_login FROM wp_users
WHERE last_login > DATE_SUB(NOW(), INTERVAL 90 DAY)
);
Note: last_login isn’t a native WordPress field, it’s populated by security plugins. If you’re not logging last login times, add that to the monitoring phase.
4. Phase 2: WordPress Core, Plugins, and Themes
Outdated software and nulled plugins are the leading cause of WordPress compromises. This phase takes ten minutes but catches the vulnerabilities attackers scan for first.
Check 1: WordPress core version
Dashboard → Updates shows your current version and whether an update is available. You should be running the current release. The latest major release is WordPress 7.0 “Armstrong”, which officially launched on May 20, 2026.
Check 2: Plugin audit
Go to Plugins → Installed Plugins.
For each plugin:
- Is it active? Inactive plugins still present attack surface. Delete plugins you’re not using, don’t just deactivate them.
- Is it up to date?
- Is it still maintained? Check the plugin’s WordPress.org page. “Tested up to” version more than two major versions behind current WordPress, or “last updated” more than two years ago, indicates an abandoned plugin. Abandoned plugins don’t receive security patches.
- Do you recognize it? A plugin you didn’t install is a significant red flag.
Flag every plugin with update=available as a required action. Delete any inactive plugins, and investigate any plugins you don’t recognize.
Check 3: Theme audit
Same logic as plugins. Go to Appearance → Themes. Delete every theme you’re not actively using, including the default WordPress themes if you’re running a custom theme. Each installed theme is a potential attack surface even if inactive.
Also, cross-reference your installed plugins and their current versions against the WPScan vulnerability database (wpscan.com/plugins) or your security plugin’s vulnerability feed.
5. Phase 3: File System and Permissions
Incorrect file permissions are both a vulnerability (writable files that should be read-only) and a detection mechanism (unexpected new files or modifications signal compromise).
Check 1: Standard permission baseline
WordPress file permissions should follow this structure:
Directories: 755 (owner read/write/execute, group and world read/execute)
Files: 644 (owner read/write, group and world read)
wp-config.php: 400 or 440 (owner read only)
Audit and correct in bulk:
# Fix directory permissions
find /var/www/yoursite/ -type d -exec chmod 755 {} \;
# Fix file permissions
find /var/www/yoursite/ -type f -exec chmod 644 {} \;
# Lock wp-config.php
chmod 400 /var/www/yoursite/wp-config.php
Check 2: World-writable files
World-writable files (permissions 777 or 666) can be modified by any process on the server. This is a common misconfiguration and a significant risk on shared hosting.
find /var/www/yoursite/ -type f -perm -o+w
find /var/www/yoursite/ -type d -perm -o+w
Any result should be corrected to the standard baseline above.
Check 3: PHP files in the uploads directory
The uploads directory should contain media files, images, PDFs, documents. It should never contain PHP files. PHP files in uploads are almost always malicious web shells.
find /var/www/yoursite/wp-content/uploads/ -name "*.php"
find /var/www/yoursite/wp-content/uploads/" -name "*.phtml"
find /var/www/yoursite/wp-content/uploads/" -name "*.php7"
Any result: quarantine the file and investigate. There are no legitimate reasons for PHP in the uploads directory.
Check 4: Recently modified files
After any suspected compromise, or as a routine audit check, look for PHP files modified in the last 14 days:
find /var/www/yoursite/ -name "*.php" -mtime -14 | sort
Cross-reference results against:
- Plugin or theme updates you made in that period (legitimate modification)
- WordPress core updates (legitimate modification)
- Anything else (investigate)
Look specifically for obfuscated code in flagged files:
find /var/www/yoursite/ -name "*.php" -mtime -14 \
| xargs grep -lE "eval\s*\(|base64_decode|str_rot13|gzinflate|str_replace.*chr" 2>/dev/null
Check 5: wp-config.php review
Open wp-config.php and verify:
- Database credentials look correct (no unexpected changes)
- The following constants are set:
define('DISALLOW_FILE_EDIT', true); // Disables theme/plugin editor in admin
define('DISALLOW_FILE_MODS', true); // Disables plugin/theme installation
define('FORCE_SSL_ADMIN', true); // Forces HTTPS on admin
define('WP_DEBUG', false); // Debug off in production
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
The secret keys and salts are present and non-default (not the placeholder text from the wp-config-sample.php)
6. Phase 4: Database Audit
The database is the component most audits skip entirely. It shouldn’t be, malware increasingly stores payloads in the database rather than the filesystem specifically because filesystem scanners miss it.
Check 1: Database user privileges
The WordPress database user should have only the privileges it needs:
SHOW GRANTS FOR 'wordpress_user'@'localhost';
Expected: SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER on the WordPress database only.
Not expected: GRANT OPTION, FILE, SUPER, PROCESS, or access to other databases. If you see these, restrict the user’s privileges.
Check 2: Suspicious content in wp_options
The wp_options table stores site configuration, plugin settings, and cached data. It’s also where database-resident malware frequently hides.
SELECT option_name, LEFT(option_value, 200)
FROM wp_options
WHERE option_value LIKE '%eval(%'
OR option_value LIKE '%base64_decode%'
OR option_value LIKE '%gzinflate%'
OR option_value LIKE '%str_rot13%'
ORDER BY option_id DESC;
Any result containing obfuscation functions in option values is suspicious. Legitimate plugin configuration doesn’t use eval() or base64_decode.
Check 3: Cron job audit
WordPress’s cron system (wp-cron) is stored in wp_options under the key ‘cron’. Malware frequently registers persistent cron jobs to re-infect the site after cleanup.
SELECT option_value
FROM wp_options
WHERE option_name = 'cron';
The value is a serialized PHP array. Use WP-CLI for a readable format:
wp cron event list
Review every scheduled event. You should recognize every hook name as belonging to a plugin or theme you actively use. Any event pointing to an unrecognized function name, or a function name that looks like random characters, is a red flag.
Check 4: Spam or injected content in posts
Some malware injects hidden links or content into posts to serve SEO spam. Check for common patterns:
SELECT ID, post_title, LEFT(post_content, 300)
FROM wp_posts
WHERE post_status = 'publish'
AND (
post_content LIKE '%<iframe%'
OR post_content LIKE '%eval(%'
OR post_content LIKE '%base64_decode%'
OR post_content LIKE '%<script src=%'
)
LIMIT 50;
Legitimate published posts shouldn’t contain iframes or JavaScript with external sources unless you deliberately added them.
7. Phase 5: Login and Authentication Hardening
Authentication configuration is where the gap between “technically functional” and “actually secure” is widest for most WordPress sites.
Check 1: Two-factor authentication status
Is 2FA enforced on every administrator account? Not optional , enforced, so that an admin cannot log in without completing the second factor.
If you don’t have 2FA enforced on all admin accounts, this is a critical finding. It’s the single most effective control against credential stuffing. Fix it before completing the rest of the audit.
Check 2: Login URL
Is your site still using the default /wp-login.php URL? While changing it is obscurity rather than real security, it removes your site from automated scan lists that spray /wp-login.php without checking whether a site exists.
If your WAF or security plugin doesn’t already obscure the login URL, add it to the action list.
Check 3: XML-RPC status
Unless you actively use Jetpack or the WordPress mobile app, XML-RPC should be disabled. It’s the highest-leverage brute force amplifier on a default WordPress install, a single request can contain hundreds of password attempts.
Test whether XML-RPC is accessible:
curl -s -o /dev/null -w "%{http_code}" https://yoursite.com/xmlrpc.php
A 200 response means XML-RPC is accessible. A 403 or 404 means it’s blocked. If you get 200 and don’t need XML-RPC, block it at the server level.
Check 4: User enumeration
Test whether your REST API leaks usernames:
curl -s https://yoursite.com/wp-json/wp/v2/users | python3 -m json.tool
If this returns a list of users with their login names, your site is leaking credentials to anyone who asks. Block the endpoint or restrict it to authenticated users.
Test the author parameter:
curl -s -o /dev/null -w "%{http_code}" -L "https://yoursite.com/?author=1"
A redirect to /author/username/ means user enumeration is possible. Block it.
Check 5: Login error messages
Visit your login page and attempt to log in with a username you know exists and a wrong password. Then attempt with a username that doesn’t exist. If you get different error messages (“incorrect password” vs “unknown email”) your login form is leaking whether usernames are valid.
Normalize error messages to a single generic response:
add_filter('login_errors', function() {
return 'Invalid login credentials.';
});
8. Phase 6: Server and Hosting Configuration
Check 1: SSL/TLS configuration
Run your domain through SSL Labs (ssllabs.com/ssltest). You should see an A or A+ rating. Below B indicates a misconfigured TLS setup, outdated protocol versions, weak cipher suites, or missing HSTS.
Minimum acceptable configuration:
- TLS 1.2 minimum (TLS 1.3 preferred)
- No SSLv3, TLS 1.0, TLS 1.1
- HSTS header with a minimum max-age of one year
Check 2: Security headers
Check your security headers at securityheaders.com. The headers that matter most for WordPress:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Missing headers are quick wins. Add them at the server level, not via WordPress, server-level headers are more reliable and don’t depend on WordPress loading correctly.
Check 3: Directory listing
Can visitors browse your directory contents? Test:
curl -s https://yoursite.com/wp-content/uploads/ | grep -i "index of"
If you see an index listing, directory browsing is enabled. Disable it:
# Nginx
autoindex off;
# Apache .htaccess
Options -Indexes
Check 4: PHP execution in uploads
Even if there are no PHP files in uploads today, is PHP execution blocked at the server level? If not, any PHP file uploaded in the future by an attacker or through a vulnerable plugin, could execute.
# Nginx — block PHP execution in uploads
location ~* /wp-content/uploads/.*\.php$ {
deny all;
return 403;
}
# Apache
<Directory "/var/www/yoursite/wp-content/uploads">
php_flag engine off
</Directory>
Check 5: Sensitive file exposure
Check whether .env, .git, and similar sensitive files are accessible:
curl -s -o /dev/null -w "%{http_code}" https://yoursite.com/.env
curl -s -o /dev/null -w "%{http_code}" https://yoursite.com/.git/config
curl -s -o /dev/null -w "%{http_code}" https://yoursite.com/wp-config.php.bak
Any 200 response is a critical finding, you’re exposing credentials or source code. Block access to these files at the server level immediately.
9. Phase 7: Malware and Indicator-of-Compromise Scan
Check 1: Security plugin scan
Run a full scan with your installed security plugin. If you’re not running one, install GuardianGaze and run an initial scan before proceeding, it gives you baseline coverage across signatures, file integrity, and behavioral indicators simultaneously.
Review every finding. Don’t dismiss warnings as false positives without investigating. A “suspicious code in plugin file” warning that turns out to be legitimate still needs to be verified, not assumed.
You can also use our free Malware Scanner & Website Security Checker.

Check 2: Google Search Console
Log into Google Search Console and check Security Issues. Google’s crawler indexes your site regularly and flags malware, phishing content, and hacked pages. If Google has flagged anything, it will appear here before you see it in other places.
Also run:
site:yoursite.com
in Google search and review the first page of results. Unusual titles, Japanese or pharmaceutical keywords in snippets, or URLs you don’t recognize indicate an SEO spam injection.
Check 3: Outbound connection audit
If you have server access, review what outbound connections WordPress is making:
# While WordPress is handling requests, watch outbound connections
watch -n 1 "ss -tp | grep php"
Or check your hosting provider’s bandwidth or connection logs for unusual outbound traffic. Malware phoning home, exfiltrating data, or relaying spam shows up as outbound connections to unfamiliar destinations.
10. Phase 8: Logging and Monitoring
An audit is a point-in-time check. Monitoring is what keeps you informed between audits.
Check 1: Failed login logging
Are failed login attempts being logged? You should be able to answer: how many failed logins did my site receive in the last 7 days, and from which IPs?
If you can’t answer that question, you have no visibility into brute force activity. Add it.
Check 2: Successful admin login alerts
Are you notified when an administrator logs in from a new IP address? This is a high-signal, low-noise alert, legitimate admins log in from a small set of locations. A login from an unfamiliar IP during an unusual hour is worth investigating immediately.
Check 3: File change alerts
Are you notified when a WordPress core file changes? Core files don’t change outside of updates. An unexpected core file modification is a potential compromise indicator that warrants immediate investigation.
Check 4: Off-server log storage
If an attacker compromises your site, one of their first actions is often to clear server logs. Logs stored only on the server are worthless after a successful compromise.
Are your security logs being forwarded off-server? Options include your hosting provider’s centralized logging, a logging service like Datadog or BetterStack, or GuardianGaze’s log endpoint, which stores audit logs independently of your server.
Check 5: Uptime and anomaly monitoring
Response time spikes, sudden traffic increases, and 503 error rates are all signals of active attacks or compromised site behavior. A basic uptime monitor (UptimeRobot, Better Uptime) with anomaly alerting adds visibility that pure security scanning misses.
11. How Often to Run a Full Audit
Full audit (all eight phases): quarterly, or immediately after any suspected compromise.
Abbreviated monthly check (Phases 1, 2, 7, 8): monthly. Takes 20–30 minutes. Catches the highest-frequency issues, new admin accounts, outdated plugins, malware detections, monitoring gaps.
Triggered audit: immediately if you see any of these signals:
- Google Search Console security warning
- Hosting provider suspension or warning
- Unexpected admin account in the user list
- Security plugin alert you haven’t investigated
- Unusual traffic spikes with no obvious cause
- Customer report of suspicious redirects
12. Keep Your WordPress Site Protected with GuardianGaze
A security audit shows you your site’s current security posture, but protecting it is an ongoing process. GuardianGaze helps you stay secure with continuous monitoring, malware detection, login protection, file integrity monitoring, vulnerability checks, and other WordPress security hardening features available across both the free and premium versions.
Start with the free GuardianGaze plugin to scan your site and strengthen its security. If you need real-time monitoring, advanced threat detection, and additional protection, you can upgrade to a premium plan anytime.
13. FAQ
How long does a WordPress security audit take?
A complete audit usually takes 2–4 hours if you have server access. A basic audit using only the WordPress dashboard and a security plugin takes about 45 minutes, but it won’t catch everything.
Can I perform a security audit without technical knowledge?
Yes, you can complete many checks from the WordPress dashboard. However, server-level checks require technical knowledge or help from a developer.
Should I use a security plugin during the audit?
Yes. A security plugin can detect malware, monitor file changes, and log login activity. It should support your audit, not replace the manual checks.
What should I do if the audit finds an issue?
Update outdated software immediately. Remove suspicious administrator accounts after investigating them. If you find malware or unknown PHP files, clean the site before putting it back online.
What’s the difference between a security audit and a vulnerability scan?
A vulnerability scan looks for known security flaws. A security audit is more comprehensive and also reviews user accounts, permissions, configurations, and signs of compromise.
Does passing a security audit mean my site is secure?
No. It means your site appears secure at the time of the audit. Regular audits and continuous monitoring are needed to keep your site protected.