# Troubleshooting BetterShield: common problems and fixes

Find the message BetterShield is showing you, see why it appears, and fix it: lockouts, emails, fixes, file checks, scheduled tasks, passkeys and more.

Start from what you see. Each section below quotes the message [BetterShield](/) shows, says why it appears, and what to do about it. Most of them also open a finding on **BetterShield › Findings**, so the same problem shows up there with its severity. If yours is not here, see [Getting help](/docs/getting-help/).

## Failed sign-ins are not being counted, so password guessing is not stopped

**What you see:** the finding **Failed sign-ins are not being counted, so password guessing is not stopped** (High). On **BetterShield › Protection › Login & Access**, the **How this site sees you** card starts "Lockouts are not running on this site yet."

**Why:** BetterShield only locks out a visitor address it can verify. When requests reach WordPress from a private address (a reverse proxy, load balancer or container network in front of the site) and nothing names that proxy, every visitor seems to come from the same place. Locking that address would lock everyone out, you included, so lockouts stand down. The public form limits record those visitors but never close a form to them.

**What to do:**

- Add your proxy's address or range under **Addresses of your CDN or proxy** on the same screen. Lockouts start working straight away.
- If the proxy is already named, the card says it "sends no forwarding header naming the visitor behind it". Set the proxy to send an X-Forwarded-For header.
- Cloudflare needs no entry. If the card says the request "arrived through Cloudflare, and nobody has told this site it is behind Cloudflare", lockouts are running, counted against Cloudflare's address and the visitor's together. Press **Yes, this site is behind Cloudflare** so a lockout follows the visitor alone.
- If it says "This site is set to trust Cloudflare, and this request did not arrive through it", press **Turn that off** if the site is no longer behind Cloudflare.

See [Proxies and Cloudflare](/docs/proxies-and-cloudflare/).

## You or a visitor is locked out

**What you see:** on the sign-in form, "Too many failed sign-ins from this connection." or "Sign-in is not available from this connection."

**Why:** the first is the attempt limit. By default, 5 wrong passwords in 15 minutes pause that connection for 15 minutes, twice as long for each repeat within a day, up to a day. The second means the connection matches an entry on **Never allowed to sign in**.

**What to do:**

- **Wait.** A pause ends on its own.
- **Use the unlock email.** An automatic notice is sent for a network the account used within the last 30 days, or when its correct password was used. On a new connection, choose **Email Me an Unlock Link** on the sign-in screen and enter your username or email address. When the lockout qualifies, open the link sent to the saved account address and press **Clear the Lockout**.
- **Release it from the dashboard.** An administrator can press **Release Now** in the **Lockouts** list on **Protection › Login & Access**. Add an address you trust to **Always allowed** and it is never locked out.
- **Refused outright?** Remove the matching entry from **Never allowed to sign in**.
- **Nobody can sign in to release you?** Use your recovery link, a recovery code or `wp bettershield recover`. See [Locked out](/docs/locked-out/).

## A fix is not available, did not apply or did not undo

Fixes live on **BetterShield › Protection › Hardening** and under **Signing in** on **Protection › Login & Access**.

| What you see | Why | What to do |
|---|---|---|
| **Not available here**, and the preview says **This cannot be applied on this site:** with a reason | This site cannot support the fix, for example .htaccess rules on nginx, or **Strict HTTPS (HSTS)** on a site whose address is plain HTTP. | Follow the reason given. For .htaccess, see the next section. |
| "There is no working way back in: the recovery link is missing, expired or was not delivered, and no recovery codes are left." | A fix that changes how people sign in needs a working way back first. | Generate a recovery link or issue recovery codes under **Protection › Recovery**, then apply the fix. **Go ahead anyway** applies it regardless, and is logged. |
| "The recovery options were last checked more than a day ago." | The same check, gone stale. | Press **Check readiness now** under **Protection › Recovery**. |
| "The change was not applied: the directory is not writable." (or "the file is not writable", "the file could not be written") | WordPress cannot write the file the fix needs. | Ask your host to let WordPress write to that folder or file, then apply again. |
| "The change was not applied because its undo record could not be written." | The undo record is written first, and without it nothing changes. | See the storage section below. |
| "The last undo did not complete:" with a reason, then "Undo again to retry." | The undo stopped part way. | Deal with the reason, then press undo again. |
| "The block in [file] has been edited since this plugin wrote it, so it was left alone rather than overwritten." | Someone changed BetterShield's rules in that file. | Keep anything that was added, then remove the block by hand. |
| "[Plugin] already does this with a setting of its own." | Another security plugin already does the same job. | Leave it to one of them. |

See [Hardening](/docs/hardening/).

## The .htaccess fixes do not work on nginx, IIS or OpenLiteSpeed

**What you see:** **Uploads code block** and **Private file protection** show **Not available here**, with "This site runs on nginx, which does not read .htaccess files, so this fix cannot work here." (or IIS). On a server that does not name itself, they apply with the note "This server did not identify itself as one that reads .htaccess files." On OpenLiteSpeed they say it "reads only rewrite rules from .htaccess files and skips the kind this fix writes", and on LiteSpeed they apply with a note that enforcement could not be confirmed.

**Why:** these two fixes work by writing .htaccess rules, which Apache and LiteSpeed's paid edition read, and nginx, IIS and OpenLiteSpeed ignore. BetterShield does not claim a protection the server will never enforce.

**What to do:** ask your host to add the same rules to the server configuration. The other fixes need no server rules, and **Folder listing protection** works on every server. Once BetterShield has kept a copy of a replaced file on nginx, IIS or OpenLiteSpeed, you may also see **Files in quarantine can still be opened from the web** (Medium): ask your host to deny web access to `wp-content/uploads/bettershield-quarantine/`. Nothing on your site loads from that folder. See [Server compatibility](/docs/server-compatibility/).

## Alert or recovery emails do not arrive

**What you see:** no alerts, a recovery link that never came, or "Alert could not be delivered" in the activity log.

**Why:** BetterShield sends through WordPress's own mail. If WordPress cannot send mail, neither can BetterShield.

**What to do:** on **BetterShield › Settings › Email Alerts**, press **Send a Test Alert** and read the answer.

| Answer | What to do |
|---|---|
| "Handed to WordPress for sending" | Look in spam. If it never arrives, mail is leaving WordPress but not reaching you: check your mail setup with your host. |
| "The mail server did not accept it." | The server's mail configuration is refusing it. Set up sending with your host or a mail plugin. |
| "Alerts are muted, so nothing was sent." | Unmute alerts, then test again. |
| "There is nowhere to send it: no address is named and the site has no admin email." | Add a recipient. |
| "A test was sent a moment ago." | Wait a few minutes. |

For recovery, if the recovery email cannot be sent, use recovery codes instead. On **Protection › Recovery**, "The link’s email could not be sent. Generate a new one and store it now. This cancels the old one." means you should generate a new link and store the copy shown on screen. Recovery codes work without email, and **Send a weekly recovery email check** tests the mail path once a week. See [Email alerts](/docs/email-alerts/) and [Locked out](/docs/locked-out/).

## The file check could not run

**What you see:** on **BetterShield › Activity › File changes**, messages such as "The official checksums for version [x] could not be fetched just now, so core files were not compared this time. This is usually temporary." or "The checksums could not be fetched. This is a connection problem, not a finding about your site." A file marked **Could not be read**. The finding **Your WordPress and plugin files haven’t been checked for changes yet** (Medium).

**Why:** the check compares your files with the copies WordPress.org publishes, so it needs to reach WordPress.org. A file the server will not let WordPress read cannot be compared. A check that could not run is never reported as clean.

**What to do:** allow the site to make outbound HTTPS requests to WordPress.org, then press **Check now**. For an unreadable file, ask your host to make it readable. On a large site the check runs in the background across several passes. See [File changes](/docs/file-changes/) and [What BetterShield contacts](/docs/what-bettershield-contacts/).

<span id="scheduled-work-has-stopped-running"></span>

## BetterShield’s background jobs have stopped running

**What you see:** the finding **BetterShield’s background jobs have stopped running** (Medium). Other signs: **Unusual for this site** on the Overview saying "An hourly task went [time] between runs", **Scheduled audits** on **Settings › General** saying an audit "has not run yet", or "The last email test is overdue. Check your WordPress scheduled tasks."

**Why:** WordPress runs scheduled tasks when a page load triggers them. A quiet site, or a host that turned that off without running the tasks another way, leaves them waiting: the daily audit, the file check, the weekly summary and housekeeping. BetterShield runs what it can when an administrator opens the dashboard and asks the site to run the rest, but that is a rescue, not a schedule.

**What to do:** ask your host to run WordPress's scheduled tasks every few minutes with a system cron, calling wp-cron.php or `wp cron event run --due-now`. If one is already set up and the finding stays, that cron job is failing, and its log says why.

<span id="safe-mode-is-on" aria-hidden="true"></span>

## Protection is paused

**What you see:** on the Overview and each Protect tab, **Protection is paused.** "Every protection that runs inside WordPress is paused until [time]. Settings are untouched." On Hardening it adds that the rules written into files stay in force. An incident response or an assistant's change to sign-in may be refused until safe mode ends.

**Why:** someone opened a recovery link or a recovery code, or ran `wp bettershield recover`. Safe mode pauses BetterShield's protections for one hour by default and changes no setting. Rules it wrote to .htaccess and index files stay in place.

**What to do:** if you expected it, let it end, or press **End safe mode now** on the Overview or a Protect tab (`wp bettershield recover --end` does the same; a pause from the network's link is ended from Network Admin, see [Locked out](/docs/locked-out/)). If you did not, look on **BetterShield › Activity** for "Safe mode started" to see when it began, then generate a new recovery link under **Protection › Recovery**. See [Locked out](/docs/locked-out/).

## Passkeys are unavailable

| What you see | What to do |
|---|---|
| "Passkeys need HTTPS. Browsers refuse to create or use one on an insecure connection, so this stays switched off while this site is addressed over plain HTTP." | Move the site to HTTPS. On a local site, localhost works. |
| "This server is missing the OpenSSL extension, which is needed to check a passkey signature." | Ask your host to enable PHP's OpenSSL extension. |
| "This browser does not support passkeys. Try a current version of Chrome, Safari, Edge or Firefox." | Use a current browser. |
| "The browser refused: passkeys need a trusted HTTPS connection to this exact address. Nothing was changed." | Open the dashboard at the site's own HTTPS address, not another domain that points to it. |
| "This device already holds a passkey for this account." | Use that passkey, or remove it and add it again. |
| "This account already has 10 passkeys, which is the limit." | Remove one before adding another. |

If no prompt appears after a while, look for a password manager such as LastPass, 1Password or Bitwarden asking in its own window. See [Passkeys](/docs/passkeys/).

## The sign-in puzzle is unavailable

**What you see:** under **Ask for a sign-in puzzle after wrong passwords** on **Protection › Login & Access**: "Needs HTTPS. Your site isn’t using HTTPS yet, so this is off." Sign-ins on ordinary HTTP skip this check. A visitor without JavaScript sees "That didn’t work. Check your username or email and your password. If they’re right, turn on JavaScript or wait a few minutes."

**Why:** browsers only run the calculation on HTTPS or localhost, and it needs JavaScript.

**What to do:** move the site to HTTPS. A visitor without JavaScript can wait a few minutes and try again: the check lifts on its own, and an unsolved puzzle never counts toward a lockout. See [Login & Access](/docs/login-and-access/).

## A two-factor code is refused

**What you see:** on the **Authenticator app code or backup code** screen, "That code does not match." or "Too many wrong codes for this account in the last hour, so codes are not being checked for now." or "That sign-in took too long, or was already used. Please sign in again from the beginning."

**Why:**

- **Time.** Codes change every thirty seconds. BetterShield accepts the code for the current thirty seconds and the ones either side, so a phone whose clock is further out than that shows codes the site does not accept.
- **Once per code.** A code that has already signed someone in is refused, and so is an older one. Wait for the next code.
- **The hourly cap.** After ten wrong codes for an account in an hour, codes for that account are not checked for a while, on the sign-in screen and the setup screens alike.

**What to do:** set the phone's date and time to automatic, then use the code it shows now, or an unused backup code. Still stuck? Your recovery link pauses the two-factor step, and an administrator can turn two-factor off for an account. See [Two-factor and passkeys](/docs/two-factor-and-passkeys/) and [Locked out](/docs/locked-out/).

## AI explanations are unavailable

**What you see:** no **Explain This in Plain Language** button on findings. The **Plain-language explanations** card on **Settings › General** says why.

| Message | What to do |
|---|---|
| "Requires WordPress 7.0." | Update WordPress. |
| "AI features are switched off for this site." | WordPress's own AI features are off on this site. Turn them on to use explanations. |
| "No AI provider is connected to this site." | Connect a provider under WordPress's **Settings › Connectors**. BetterShield never asks for or stores a key of its own. |
| "The explanation could not be generated. This is a problem with the AI provider, not with your site" | Check your provider's connection, then ask again. The finding is unchanged. |
| "That is as many explanations as one account asks for in an hour. Try again shortly." | The card shows the hourly limit for each account. |

## The assistant connection test fails

On **BetterShield › Agents › Connect**, under **Connection health**, **Test connection** makes one real call and says where it stopped.

| Result | What to do |
|---|---|
| "The connection is switched off here, so nothing answered." | Turn it on, then test again. |
| "Nothing is paired yet, so there was no credential to try." | Press **Connect**, then test again. |
| "This site could not reach its own address." | Your host refuses requests a site makes to itself. An assistant connecting from outside may still get through. |
| "The certificate could not be verified from this server." | Usual on a local or staging site. On a live site, check the certificate. |
| "The address redirected somewhere else." | Make the site address and WordPress address agree, both on https. |
| "The address answered with a page instead of the endpoint, so it is not routed to this plugin." | Save **Settings › Permalinks** once to rebuild the rules, then test again. |
| "The credential was refused." | Press **Rotate**, then put the new credential into your assistant. |
| "The round trip worked and nothing was offered." | Turn on read access for agents. |

If step 1 says **Requires WordPress 6.9.**, BetterShield's own copy of the Abilities API is missing: reinstall BetterShield from WordPress.org, or update WordPress to 6.9 or newer. With plain permalinks, the browser sign-in flow for Claude and ChatGPT is not available; the other clients still work. See [Connect an AI assistant](/docs/connect-an-ai-assistant/).

## Part of the plugin's storage is unavailable

**What you see:** an Overview card, **Part of this plugin's storage is unavailable**, naming what is affected (such as **The activity log** or **Sign-in lockouts**), with **Repair**. After an hour, the finding **Part of this plugin's storage has been unavailable for more than an hour** (High). If the affected part counts code guesses, the finding **Sign-ins are being refused because BetterShield cannot save to the database** (Critical) appears, and two-factor, backup and recovery codes are refused until it is fixed. The recovery link still works.

**Why:** a table or column BetterShield creates for itself is missing, usually after an update the database did not finish. The part that writes there stops; everything else carries on. BetterShield tries again every hour on its own.

**What to do:** press **Repair**. It only creates what is missing and touches no existing data. If it answers "The migration ran again and did not finish:" with the database's reply, ask your host whether the database account may create tables. Deactivating and reactivating BetterShield runs its installer again.

## Another security plugin is detected

**What you see:** Quick Setup shows **Another security plugin is active**, with one row for each job both plugins do. On Hardening, a fix may say "[Plugin] already does this with a setting of its own. Applying it here as well does the same job twice; leave it to one of them."

**Why:** two plugins limiting sign-in attempts can lock a person out twice, and two asking for a second factor ask twice. If BetterShield's **Sign-in attempt limits** is on and another active plugin also limits sign-in attempts without the choice being made here, the audit opens **Two plugins limit sign-in attempts** (Low).

**What to do:** choose who keeps each job, then press **Save choices**.

| Job | Choices | Default |
|---|---|---|
| Sign-in attempt limits | **Keep [plugin]** (BetterShield's own limit goes off) or **Use BetterShield** | Keep |
| Two-factor | **Keep [plugin]** (anyone already enrolled keeps signing in as now) or **Use BetterShield** | Keep |
| Activity log | **Keep both** or **Use BetterShield only** | Keep both |
| File change monitoring | **Keep both** or **Use BetterShield only** | Keep both |

Sign-in address, firewall and scanner rows are stated, not chosen. BetterShield never switches another plugin off: deactivate it yourself from the Plugins screen when you are ready. To change your answers later, press **Run Quick Setup again** on **Settings › General**. See [Run alongside another security plugin](/docs/alongside-another-security-plugin/).

## Related

- [Server compatibility](/docs/server-compatibility/)
- [Getting help](/docs/getting-help/)
- [Locked out](/docs/locked-out/)
- [Login & Access](/docs/login-and-access/)
- [Hardening](/docs/hardening/)
- [FAQ](/docs/faq/)
