The WordPress security checklist BetterShield runs
54 read-only checks across five areas of your site. Each finding explains what it is, why it matters and what could break if you act on it, and nothing changes until you choose a fix.
Access
Who can sign in, how, and what each account can do once it has.
-
Critical Sign-in caps cannot be enforced
Wrong sign-in codes, backup-code guesses, printed recovery-code guesses and password re-confirmations are counted in one of this plugin’s own tables, and a statement against that table did not run. A limit that cannot be counted is not a limit, so a check whose counter fails is refused outright, including a correct code. Printed recovery codes also need this table to be spent safely. The independent recovery link still works.
Could break: nothing. The plugin tries to put the table back by itself, and this closes on its own at the next audit once the records answer again. If it stays open, the database account may not be allowed to create tables (your host can say) and deactivating and reactivating the plugin runs the installer again.
-
Medium orHigh The recovery link is close to expiring
The recovery link is what gets you back in when nothing else works, and it stops working ninety days after it was issued. Generate a new one under Protect › Recovery and store it wherever you keep the last one. If passkey-only sign-in is switched on for any role, this matters more: a password will not get those accounts back in, so the link and your printed codes are the whole of the way back.
Could break: nothing. Generating a new link retires the old one, so replace the copy you have saved.
-
High Sign-in lockouts are not running
WordPress is not being reached directly on this site: requests arrive from inside your own network, and nothing names the visitor on the other side of whatever is in front of it. Counting the address they arrive from would put every visitor under one lockout, you included; believing a forwarding header that no named proxy vouches for would let anyone change the address they appear to come from and never be locked out. So nothing is being counted, and repeated password guessing is not being stopped.
Fix under Protect › Login & Access › Trusted proxies: name your proxy’s address range there, or, if it is already named, set the proxy to send an X-Forwarded-For header naming the visitor. Nothing else changes, and lockouts start working immediately.
-
High Nothing is limiting sign-in attempts
Pausing sign-in after repeated failures is switched off, and no active plugin BetterShield recognizes limits sign-in attempts. Unless another plugin is doing that job, anyone can go on guessing passwords at the sign-in form for as long as they like. This usually happens when the job was left to another security plugin during setup, and that plugin has since been deactivated or deleted.
Fix by switching on “Pause sign-in after repeated failures” under Protect › Login & Access. Someone who signs in normally notices nothing; only a connection that keeps getting the password wrong is paused. If a plugin BetterShield does not recognize already limits sign-ins, a developer can name it with the bettershield_coexisting_plugins filter instead.
-
High Anyone can register, and new accounts get more than reader access
Registration is open to the public and every new account arrives with a role that can do more than read. That hands an attacker a working login on request, with no need to guess anything.
Fix under Settings › General by setting the default role to Subscriber, or by turning off open registration. Existing accounts keep whatever role they already have.
-
Medium Some administrator passwords use an older storage format
WordPress stores passwords scrambled, and newer versions use a much slower method that is far more expensive to work backwards from. Accounts that have not signed in since the site was updated are still stored the old way, so if a copy of the database ever got out, those passwords would be the first to fall.
Fix by asking each of those people to sign in once. WordPress quietly re-saves the password in the newer format as they do: nobody has to choose a new one, and this plugin never changes an account itself.
-
Low orMedium Some accounts signed in with passwords below the policy
These passwords were chosen before the policy existed, and nothing about them blocks anyone: signing in works exactly as before. They are listed because a password below the current standard is easier to guess, and the accounts stay flagged only until their next password change.
Could break: nothing. Each person updates their own password from their profile, whenever suits them. No reset is forced, and no account is locked.
-
Medium An account that can change this site has not been used for a long time
A dormant administrator is a wide, quiet door: full rights, an old password, and nobody who would notice it being used. It is usually a former colleague, a contractor whose work finished, or a test account somebody made during a migration.
Could break: nothing. This is a list. Removing or demoting an account is yours to do, because getting it wrong locks out somebody who was on leave.
-
Low Some recovery options need attention
Review your recovery link, unused printed codes, administrator addresses and email checks before you need them. A valid address or a message accepted for sending does not prove that somebody received it. These checks never spend a recovery credential.
Could break: nothing. This check only reads the recovery state.
-
Low User profiles are publicly listable One-click fix
The REST API lists user profiles to visitors by default, which hands automated tools a list of usernames to try passwords against.
Could break: nothing on most sites. Sites embedding author directories from the public API would need the block removed.
-
Low A user is named "admin"
Automated login attacks try the username "admin" first. An account with that name gives them half of a valid login for free.
Fix by creating a new administrator with a different name and deleting this one (reassigning its content).
-
Low The first account created is still an administrator
Automated tools address the first account on a site by its number rather than its name, and on almost every WordPress install that number is 1. Renaming the account does not change it.
Fix by creating a second administrator, reassigning this one’s content to it, and deleting this one. Nothing changes until you do.
-
Low A role below administrator may publish unfiltered code
Accounts in the roles listed may save script in a post, or upload a file of any type, and it runs in the browser of whoever views it, administrators included. WordPress gives Editors unfiltered HTML on a single site, and that default is not reported here; any other role below administrator that holds an account is.
Fix by removing the capability from the role with a role editor, or by setting DISALLOW_UNFILTERED_HTML in wp-config.php, which takes it from everybody, administrators included. Some plugins grant it on purpose, so check what the role is for first.
-
Low Application passwords that nothing has used for months
Application passwords are how connected tools and AI agents sign in. One that nothing has used for ninety days is a live credential with no live purpose: if it ever leaks, nothing legitimate notices the theft.
Could break: any integration secretly relying on one. Each user revokes their own under Profile › Application Passwords; revoke the stale ones and any tool that mattered will fail loudly and re-enroll.
Exposure
What the site shows or answers to people who are not signed in.
-
Critical A stranger can download something from this site that should not be there
These files are left behind by a deploy that copied too much, or by an editor that saved a backup before somebody changed something. They are not part of WordPress and nothing on the site needs them, but a web server hands them over to anybody who asks for the address, and what they contain is usually the credentials to everything else. This did not guess: it asked for each address and reports only what actually came back.
Could break: nothing here. Removing a file that should not be published is yours to do, and it is worth keeping a copy somewhere outside the web root first.
-
Medium toCritical A leftover installer or database tool can be opened from the web
Migrations and debugging sessions leave tools behind in the web root: a database manager, a PHP information page, a search-and-replace script, an installer and the archive it came with. They are not part of WordPress, nothing on the site needs them once the job is done, and a stranger who finds one can run it. This did not guess: it asked for each address and reports only a tool that actually answered.
Could break: nothing on the site. Quarantine moves the tool’s contents where nothing serves them and leaves an empty file in its place; put it back from the quarantine list on the Files screen if you still need it.
-
Medium toCritical The site’s certificate is close to expiring
An expired certificate takes a site offline for every visitor and every browser at the same moment. It is one of the most complete outages a site can have and one of the easiest to see coming: owners usually hear about it from a customer.
Could break: nothing. Renewing is done with your host or your certificate provider; this only says when.
-
High An ability that changes the site is open to low-privilege users
A plugin has registered an agent-callable ability that is not read-only, is reachable over the REST API, and whose own permission check admits accounts far below administrator. Any signed-in low-privilege account (or an agent holding such an account’s credentials) can invoke it.
Could break: nothing from this finding itself. The fix belongs to the plugin that registered the ability; until it ships one, consider whether that plugin needs to stay active, and check the activity log for surprises.
-
High Another website can make signed-in requests to admin-ajax and read the answers
A browser shares a response with another website only when the response says so. Asked with the address of a website nobody owns, admin-ajax named that website back and allowed the visitor’s cookies. WordPress allows only this site’s own addresses there, so the list was widened, by code or by the web server. Any page a signed-in visitor opens can then call admin-ajax as them and read what comes back from every action that does not check a nonce. The REST API answers other websites too, on purpose, and is not reported: it treats a cookie without the nonce WordPress prints on its own pages as a stranger’s.
Could break: nothing here. This is a report. Narrowing the allowed origins to the addresses that need them can stop a front end or app on another address that calls admin-ajax as a signed-in visitor.
-
Medium orHigh Accounts below administrator may upload files that can carry script
An SVG, HTML or JavaScript file served from your own address runs as part of your site in the browser of whoever opens it. WordPress keeps these types from accounts that may not publish unfiltered HTML, and a plugin or theme has allowed them again for the roles listed. A PHP type (php, phtml, phar and the like) is worse: where the server runs PHP from the uploads folder, uploading one runs code on the server itself.
Fix in the plugin or theme that allows these types, usually in its upload or SVG setting. If you need SVG uploads, keep them to administrators, or use a tool that cleans each file as it is uploaded. For a PHP type, also switch on “Stop PHP running in uploads” under Hardening.
-
Medium XML-RPC is enabled One-click fix
A legacy remote-access API that most sites no longer use, and a frequent target of automated login attempts.
Could break: the WordPress mobile app and Jetpack, if you publish through them.
-
Medium The dashboard file editor is enabled One-click fix
The built-in plugin and theme editor lets anyone with admin access edit code from the browser: a favorite tool of attackers who obtain a session.
Could break: nothing. Files stay editable over SFTP and through your host.
-
Low orMedium The published security contact is about to expire
This site publishes a security contact at /.well-known/security.txt, and the standard requires that file to say when it stops being trustworthy. Past that date a finder who reads it has been told something the site no longer stands behind, which is worse than publishing nothing, because they trust it and get nowhere.
Could break: nothing. Move the date on, or turn the file off.
-
Low Something is scheduled that no code answers
A scheduled event whose hook has no code behind it is a call into nothing. Usually it is left over from a plugin that was removed and nobody cleaned up after: WordPress keeps the schedule when the plugin goes. Occasionally it is the other way around: an event placed there to run code that has not arrived yet, which is how something removed from a site puts itself back. This names them and reads nothing into them.
Could break: nothing. This is a list. Removing a scheduled event is yours to do, and an event belonging to a plugin that only registers its hook on the front end would be named here while working perfectly: so check what it is before removing it.
-
Low Published content still points at plain HTTP
An image, script or stylesheet loaded over plain HTTP on an HTTPS page is either blocked by the browser or breaks the padlock. It is the usual reason a site that was moved to HTTPS still looks insecure to visitors, and the cause is almost always an address typed into a post years ago.
Could break: nothing. This reads published content and changes none of it.
-
Low Common security response headers are missing One-click fix
Four small headers tell browsers not to guess at file types, not to let another site frame your pages, not to leak full addresses to third parties, and not to enroll your visitors in ad topic tracking. Without them a browser falls back to its most permissive behavior.
Could break: almost nothing. If another site legitimately embeds your pages in a frame, that embed would stop working. Any header your server already sends is left exactly as it is.
-
Low The WordPress version is published in every page One-click fix
Each page carries a tag naming the exact WordPress version. Automated scanners read it to decide which known flaws to try, so an out-of-date site advertises what will work against it.
Could break: nothing. Hiding it is cosmetic: it does not fix an out-of-date version, it only stops announcing one.
-
Info Plugins are answering the REST API to visitors who are not signed in
Closing the four username routes is the best-known part of this and not the whole of it. Every plugin on a site can register REST routes, and a route with no permission check answers anybody. This lists which namespaces beyond WordPress’s own answer a stranger, so the decision is yours rather than nobody’s.
Could break: nothing. This is a list, not a change: most of what is open is open on purpose, and closing routes on a guess is how a headless front end stops working.
Updates & extensions
WordPress core, plugins and themes: updates, abandoned code and files that changed.
-
Medium orCritical Files no longer match the official copies
One or more core or plugin files differ from what their publisher shipped, or are there when nothing shipped them. Sometimes that is a developer’s deliberate edit. Sometimes it is code somebody else put on your site. The Files tab shows exactly what differs, line by line, so you can tell which.
Putting a file back replaces it with the official copy and keeps the current one, so nothing is lost either way. A file you edited on purpose can be marked expected instead.
-
High A WordPress core update is available
Core updates regularly contain security fixes, and disclosed vulnerabilities are exploited within hours of publication.
Update from Dashboard › Updates. Your host may also offer staged updates.
-
Medium Plugins have updates available
Most WordPress compromises come through known, already-fixed plugin vulnerabilities on sites that have not updated yet.
Update from Dashboard › Updates. Test on staging first if a plugin is business-critical.
-
Medium An installed plugin is no longer in the WordPress.org directory
WordPress.org has stopped listing this plugin. The directory does not publish the reason, and this does not guess at one, but a closed listing means two things for certain: there will be no further updates, and something led somebody at WordPress.org to close it. It is often the first public sign of a security problem, weeks before an advisory names one.
Could break: nothing from this finding itself. Removing or replacing the plugin is yours to decide, and it is worth checking the plugin author’s own site first: some closures are the author’s own choice and nothing to do with security.
-
Medium The vulnerability data is old
The last time this site was compared against published advisories is long enough ago that new ones may have been published since. Old data presented as current would be worse than no data, so its age is stated here.
The check runs daily on its own. If it keeps failing, the reason is on the Settings screen; if the feed itself is behind, there is nothing to do but wait.
Built, but no vulnerability data source is connected in BetterShield 1.1.0, and the Findings screen says so.
-
Medium Files have not been checked against the official copies
Nothing has compared this site’s files against the copies WordPress published, or the last comparison is old enough that it no longer describes the site as it is now. An unchecked site is not a clean one: it is one nobody has looked at.
The check only reads. Start one from the Files tab; on a large site it runs in the background across several passes.
-
Varies Installed versions have known vulnerabilities
A published advisory names a version you are running. Advisories are read by attackers too, and disclosed vulnerabilities are exploited within hours of publication, usually against sites that have not updated yet.
Update to the fixed version from Dashboard › Updates. Where no fix exists yet, deactivating the plugin or theme closes the door until one does. The list names each item, the version you have, and the version that fixes it.
Built, but no vulnerability data source is connected in BetterShield 1.1.0, and the Findings screen says so.
-
Low BetterShield does not update itself automatically
A security plugin that waits for somebody to press Update keeps running the older release, and the fixes in the newer one wait with it. WordPress can install this plugin’s new releases on its own, and that is switched off for it here.
Could break: nothing this plugin knows of. Turn it on with Enable auto-updates on this plugin’s row of the Plugins screen. If your host or your own tools install updates for you, nothing here needs changing.
-
Low The plugins folder holds code that no installed plugin claims
WordPress knows a plugin by the header at the top of its main file, and lists only what carries one. These files and folders hold PHP, but no installed plugin claims them, so they appear on no screen and nothing updates them. Most often it is a copy left behind after an update, or a folder a host or a plugin keeps its own files in. Now and then it is code placed there to be called from somewhere else, which is why it is worth a look. This names what is there and reads nothing into it: no file was opened.
Could break: nothing. This is a list. A plugin you use may keep its own files there, so check what each one is before moving anything, and keep a copy somewhere outside the site first.
-
Low An installed plugin has had no update in the WordPress.org directory for years
The WordPress.org directory shows the date each plugin was last updated there, and for these it is years ago: two by default. An old date is not a problem in itself, and some small plugins are simply finished. But nobody may be looking at the code anymore, so if a problem is found in it, a fix may never come. Each plugin is named with the date the directory gives, and nothing is read into it.
Could break: nothing from this finding itself. Keeping, replacing or removing the plugin is yours to decide, and it is worth checking the plugin’s page and its author’s own site first. A newer release may be published somewhere else.
-
Low Deactivated plugins or themes are still installed
A deactivated plugin still has its files on the server, and some of its files can be reached directly by a visitor. Because it is switched off it also tends to stop being updated, so it quietly becomes the oldest code on the site.
Fix by deleting the ones you know you will not use again. Deleting a plugin can remove its settings, so reactivate and export first if you are unsure.
Server
The PHP version, HTTPS, file permissions and the disk the site runs on.
-
High orCritical The site address is not HTTPS
Without HTTPS, logins and cookies travel readable through every network between the visitor and the server.
Most hosts issue a free certificate in one click; WordPress can then switch the address under Settings › General. If this is a development machine rather than a live site, add WP_ENVIRONMENT_TYPE with the value local to wp-config.php: this is graded as a live site because nothing says otherwise, and a custom local domain is not something the plugin can recognize on its own.
-
High This PHP version no longer receives security fixes
PHP itself gets security fixes only for supported branches. A site on an end-of-life branch inherits every future PHP vulnerability permanently.
Your hosting control panel usually offers a newer PHP version as a dropdown. Test after switching.
-
Medium orHigh The disk this site writes to is almost full
A full disk fails quietly and everywhere at once: the activity log stops recording, the daily seal of the log cannot be written, quarantine has nowhere to keep a file, and an update can unpack halfway and leave a plugin broken. Security records stop exactly when they may be needed. This reads the free space the server reports for the content and uploads folders; a hosting account’s own quota can be lower still, and the host’s panel shows it.
Could break: nothing here. This is a report. Free space by removing old backups kept on the server, old log files or unused media, or ask your host for more.
-
High wp-config.php can be written by other accounts on the server
The file holding your database credentials and secret keys is writable by users other than its owner. On shared hosting that can mean another customer on the same machine, and anything that can write this file can take over the site.
Fix by setting the file to 0644 or stricter through your host’s file manager or SFTP client. This plugin never changes file permissions and never writes that file.
-
Medium The sign-in page is served from a page cache
A cached sign-in page hands every visitor the same copy of a page that was built for somebody else. The form carries values meant for one request, so a stale copy can refuse a correct password for reasons nobody can see, and a lockout answer meant for one visitor can be shown to the next. This asked for the sign-in page twice, as a visitor with no cookies, and the second answer came with headers that say a cache served it.
Could break: nothing here. This is a report. Exclude the sign-in address from caching in your cache plugin, your host’s panel or your CDN’s rules; the page then loads fresh for each visitor, as it is meant to.
-
Medium Scheduled work has stopped running
WordPress runs its scheduled work when a visitor’s page load triggers it, and a host that turns that off is expected to run it another way. One of this plugin’s own scheduled checks has missed a whole cycle (an hourly one is more than an hour past its appointment), which means neither is happening: the file check, the vulnerability check, the alert digest and the housekeeping the plugin does for itself all wait until something runs the schedule. What can run from your own dashboard visits does, and each visit asks the site to run the rest, but a schedule that misses whole cycles is not being run.
Could break: nothing. This is a report. The fix is on the host: a system cron that runs wp-cron.php, or “wp cron event run --due-now”, every few minutes: the host’s own guidance describes it. If one is already set up, it is failing, and its log says why.
-
Medium Folders under wp-content can be written by every account on the server
The folders listed, which hold your plugins, themes or uploads, are writable by any account on the machine, not only the one the site runs as. On shared hosting that can include other customers, and whatever can write these folders can add code the site will run.
Fix by setting the folders listed to 0755 through your host’s file manager or SFTP client, or ask your host to. This plugin never changes file permissions.
-
Medium Files kept as evidence are not sealed off from the web
When a changed file is put back, the version that was there is kept rather than deleted, and it may be exactly what somebody planted. The deny rule this plugin writes to shut that folder off from the web is an Apache mechanism, and here it is not in force: either this server does not read the file it goes in, or the file is not in the folder. The names are unguessable, so nothing can be found by asking; the folder is simply not sealed the way it would be on Apache.
Nothing to undo here: nothing on your site was changed to raise this. Denying the folder in your server configuration is the fix, and denying it breaks nothing: nothing on your site ever loads from it.
Configuration
wp-config.php, debug output, backups and BetterShield’s own records.
-
High A sealed day of the activity log no longer matches its seal
Each finished day of the activity log is sealed with a digest, and each seal carries the one before it. A day that no longer matches means its rows have been edited or removed since the day closed. That is worth knowing on its own: the log is what an investigation reads, and somebody who reaches a site often tidies it before leaving.
Could break: nothing. This reads the log and changes none of it.
-
High The secret keys in wp-config.php are missing or unchanged
WordPress signs every login cookie with a set of long random phrases kept in wp-config.php. If they are absent, left as the shipped placeholder, or too short, a login session becomes far easier to forge without ever knowing a password.
Fix by replacing the eight lines in wp-config.php with a fresh set from the WordPress.org secret-key generator. Everyone is signed out once, and no password changes. Rotating the sign-in keys under Protect, Hardening is worth doing as well and is a different thing: it mixes a strong stored secret into every key, and it does not alter the constants this reads.
-
Medium orHigh Debug output is shown to visitors
WP_DEBUG with display enabled prints errors (including file paths and occasionally credentials) into pages anyone can view.
Set WP_DEBUG_DISPLAY to false in wp-config.php (a change you make; this plugin never edits that file).
-
High Part of this plugin’s storage has been unavailable for more than an hour
BetterShield keeps its records in its own tables, and a table, a column or an index the migration was meant to create is missing. The part of the plugin that writes there has stopped; everything else carries on. The migration tries again every hour on its own, so a part still unavailable after that is one an attempt has already been made for. Repair, on the Overview, runs it once more and reports what the database answered.
Could break: nothing. A repair only creates what is missing: no table, column or record is dropped or rewritten, and no existing data is touched.
-
Low orMedium The domain’s mail policy or certificate record could be stronger
WordPress sends password resets and notices from this site’s own domain. An SPF record says which servers may send that mail, and a DMARC policy tells receiving mail servers what to do with mail that fails the check. Without them a message pretending to be the site is delivered as readily as a real one, which is what “reset your password” phishing aimed at the site’s own users relies on. A CAA record limits which certificate authorities may issue a certificate for the domain. These were read with the server’s own DNS lookups.
Could break: nothing here. This is a report. DNS records are changed where the domain is managed. An SPF record must list every service that sends mail as the domain, or that mail starts to fail, so start DMARC at p=none and read its reports before tightening it.
-
Low No recognized backup plugin is active
BetterShield did not recognize an active backup plugin. This does not mean you have no backups: your host or another tool may handle them. Confirm where your backups live, how recently one completed, and how to restore it. An installed backup plugin alone proves none of those things. If you have this covered elsewhere, mark this finding not applicable; it will stay settled.
Could break: nothing. This check reads the plugin inventory and makes no external requests.
-
Low The database uses the default table prefix
Tables are named wp_ as they come out of the box. It is not a hole on its own, but it means an attack that manages to inject a database query does not need to discover anything first.
Changing this on a live site means renaming tables and editing wp-config.php, and it goes wrong easily. It is worth doing when you next move or rebuild the site, and not worth an emergency.
Found something? Fix it in one click
5 of these checks are closed by one of the 16 one-click fixes. Each shows what it will change before you apply it, and each has an undo that never expires.
Frequently asked questions
Short answers to the questions we get asked most about the checklist.
Do the checks change anything on my site?
No. All 54 checks only read. Nothing changes until you choose a fix, and every one-click fix shows what it will change first, and you can switch it off again.
When does the audit run?
When you activate BetterShield, and again whenever you press Run audit. Each finding explains what it is, why it matters and what could break if you act on it.
Can I put a finding off?
Yes. Snooze it for 7 or 30 days.
Does BetterShield detect vulnerable plugins?
Not yet. The check against published vulnerability advisories is built, but no data source is connected in this version, and the Findings screen says so rather than showing an empty list as a clean result.
Close the open doors today
Install the free plugin. The first audit runs when you activate it, and nothing changes until you choose a fix.
Requires WordPress 6.7 or newer and PHP 8.0 or newer.