# How to Set Up BetterShield Login Protection in xCloud

> Turn on free BetterShield login protection for a WordPress site in xCloud: failed-login limits, two-factor sign-in, passkeys, bot checks and server firewall bans.

**BetterShield** is the free login protection xCloud installs on WordPress sites. It replaces the WP Fail2Ban plugin and covers everything WP Fail2Ban did, and more. You turn it on per site with the switch on the **WP Fail2Ban & Login Protection** card under **Security → Overview & Firewall**, and xCloud installs the plugin, connects it to the server firewall and keeps its Cloudflare setting in sync.

[BetterShield](https://wordpress.org/plugins/bettershield/) is a free plugin by WPDeveloper on WordPress.org. Once it is active on your site, it:

-   **Limits failed logins** and pauses a connection after too many failed attempts.
-   Adds **two-factor sign-in** and **passkeys** for your WordPress users.
-   Stops bots with a hidden **bot check** and a **proof-of-work check** on the login and comment forms.
-   Runs a **security audit** of your WordPress configuration and keeps an **activity log**.
-   Reports each paused connection to the **server firewall**, so repeated failures are also blocked before they reach WordPress.

xCloud installs the **latest version** from WordPress.org. If BetterShield is already installed on the site, xCloud keeps that copy and activates it, but still applies its strict protection settings. Settings you tuned in that copy before, such as the failed-login numbers, go back to xCloud's defaults: 5 failures within 15 minutes pause sign-in for 15 minutes.

_Does your site still run WP Fail2Ban? Follow [Migrate from WP Fail2Ban to BetterShield](/docs/migrate-from-wp-fail2ban-to-bettershield-in-xcloud/) instead. The same switch removes WP Fail2Ban, then installs BetterShield._

## Before you start

BetterShield is available for WordPress sites only. The site must meet these requirements, or the switch stays greyed out:

| Requirement | Detail |
| --- | --- |
| Site type | WordPress. Other site types do not show the **WP Fail2Ban & Login Protection** card. |
| WordPress | 6.7 or newer. xCloud checks again on the server before it installs. |
| PHP | 8.0 or newer for the site. |
| Server | Connected to xCloud. |
| Your role | Allowed to change the site's security settings. |

When the switch is greyed out, hover over it to see which requirement is missing. A site that cannot run BetterShield shows **Login protection is not available** with the reason on the card.

## Step 1: Open Overview & Firewall

From your xCloud dashboard, open the site. In the site menu, click **Security**, then **Overview & Firewall**. The **WP Fail2Ban & Login Protection** card, marked **Powered by BetterShield**, sits below **Security Settings**.

The same **Security** menu also holds **Vulnerability Scan**, **Patchstack**, **Integrity Monitor**, **IP Management** and **Basic Authentication**.

![The Security menu of a WordPress site in xCloud, with Overview & Firewall open and the WP Fail2Ban & Login Protection card below Security Settings](/_landing/docs/set-up-bettershield-login-protection-in-xcloud-overview-and-firewall.png)

The **Login protection** tile at the top of the page shows the current state at a glance: **Off**, **BetterShield on**, **WP Fail2Ban, retired** or **Needs attention**.

## Step 2: Turn on BetterShield

While BetterShield is off, the card shows **Protect your WordPress logins** with **Off** and **Free** badges, and what BetterShield adds: failed sign-in limits, server firewall bans, two-factor and passkeys, username protection, bot checks and Cloudflare awareness.

Turn on the **switch in the card header**, to the left of **WP Fail2Ban & Login Protection**.

![The WP Fail2Ban & Login Protection card while BetterShield is off, with the switch in the card header highlighted](/_landing/docs/set-up-bettershield-login-protection-in-xcloud-turn-on.png)

The **Turn on login protection** window opens. It says BetterShield emails your WordPress admin address a recovery link you can use if you are ever locked out. Click **Turn On** to start the install, or **Cancel** to close the window. While xCloud accepts the request, the button shows **Processing...** and the window stays open.

![The Turn on login protection window with the Cancel and Turn On buttons, Turn On highlighted](/_landing/docs/set-up-bettershield-login-protection-in-xcloud-confirm.png)

There is nothing to choose before the install. BetterShield starts with **strict protection**: every protection in [Protection settings](#adjust-protection-settings) is on, including the proof-of-work check, and the failed-login limit uses BetterShield's own numbers (a pause after 5 failed attempts within 15 minutes, for 15 minutes). You can turn off any protection afterwards.

When BetterShield activates, it emails the **WordPress admin address** of the site a single-use recovery link. Keep that email: you can use the link to get back in if you are ever locked out. Make sure the admin address under **Settings → General** in WordPress is one you can read.

The card shows **Turning on BetterShield** while xCloud works on the server. It takes about a minute and runs in the background, so you can leave the page. The card turns **Active** on its own when the install finishes, without a page reload. The install also appears in **Recent Events**, the activity icon at the top right of xCloud, like any other xCloud task.

### What xCloud also fixes when it turns BetterShield on

BetterShield's security audit gives the site a score out of 100. During the install, xCloud also closes these findings, each of which is safe on any site:

| What xCloud does | When | BetterShield finding it closes |
| --- | --- | --- |
| Sets `wp-config.php` to `640`, so other accounts on the server cannot change it and only the site's own user and group can read it. | Only when the site's own user owns the file and it is writable by the group or has any permission for others. A stricter file is left as it is. | **wp-config.php can be written by other accounts on the server** (12 points) |
| Changes the WordPress address (Home and Site URL) from `http://` to `https://`. | Only when the site has a working SSL certificate in xCloud and the address is exactly `http://` and your domain. xCloud then opens the login page over HTTPS; if it does not load, both addresses go back to `http://`. | **The site address is not HTTPS** (25 points) |
| Turns off the theme and plugin file editor in WP Admin. | Always, through BetterShield's own fix. | **The dashboard file editor is enabled** (7 points) |
| Adds security response headers. | Always, through BetterShield's own fix. | **Common security response headers are missing** (3 points) |
| Stops the uploads folders listing their files, with an empty index file in each. | Always, through BetterShield's own hardening. | None. This is BetterShield's hardening item **Stop uploads directories listing their contents**; the audit has no finding for it. |
| Hides the WordPress version from visitors. | Always, through BetterShield's own fix. | **The WordPress version is published in every page** (3 points) |
| Runs BetterShield's first **file check**, which compares WordPress core and plugin files with the official copies. It only reads files. | Always. On a large site it can take longer than the install; BetterShield then finishes it on its own within the hour. | **Files have not been checked against the official copies** (7 points) |

The four fixes in the middle go through BetterShield's own hardening, so you can undo each one in WP Admin. None of them writes `wp-config.php` or needs `.htaccess`, so they work on Nginx and OpenLiteSpeed alike.

The address is not changed for a site behind Cloudflare without a certificate on the server (Flexible mode), for a `www` address or WordPress in a subfolder, or when `WP_HOME` or `WP_SITEURL` in `wp-config.php` sets the address. Installing SSL in xCloud already switches the address to HTTPS, so most sites with SSL have it closed before BetterShield is turned on.

## Step 3: Check the status

When the install finishes, the card shows **Your logins are protected** with an **Active** badge, the **Server firewall connected** chip, the installed **Version**, the **Open BetterShield in WordPress** button and the site's **Protection settings**. A site behind Cloudflare also shows a **Cloudflare aware** chip.

![The WP Fail2Ban & Login Protection card with BetterShield Active, the Server firewall connected and Version chips, and the Open BetterShield in WordPress button](/_landing/docs/set-up-bettershield-login-protection-in-xcloud-active.png)

The card can show one of these states:

| Card title | What it means |
| --- | --- |
| **Protect your WordPress logins** (Off) | BetterShield is not on. Turn on the switch in the card header. |
| **Turning on BetterShield** (Installing) | xCloud is installing and activating the plugin. |
| **Your logins are protected** (Active) | BetterShield runs on the site. |
| **Turning off BetterShield** (Removing) | xCloud is removing BetterShield. |
| **BetterShield needs attention** or **BetterShield is not on** (Needs attention) | The last change did not finish. The card shows the reason, for example that WordPress is too old. Fix the cause, then click **Try Again**. |
| **Login protection is not available** (Unavailable) | The site does not meet a requirement. The card shows which one. |

If the card says **the server firewall is not connected, so lockouts stay inside WordPress**, and the chip reads **Server firewall not connected**, BetterShield still pauses failed logins inside WordPress, but paused connections are not banned at the firewall. There are two causes:

-   The server's firewall (Fail2Ban) did not accept the new rule during the install, for example because Fail2Ban or iptables is missing, or WordPress cannot write to the server's log. Saving settings does not fix this. Turn BetterShield off and on again with the card's switch to retry; this also resets its settings to strict protection. If it still says not connected, contact support.
-   BetterShield's own **Server firewall** setting was switched off in WP Admin. Change any setting on the card and click **Save Settings** to turn it back on. **Save Settings** stays disabled until you change something (see [How the server firewall blocks repeated failures](#how-the-server-firewall-blocks-repeated-failures)).

A change that does not report back within 30 minutes turns into a failure you can try again.

## Adjust protection settings

Once BetterShield is active, the **Protection settings** section of the card shows the settings xCloud read from the site, and when it read them (**Read from the site** and the time). The settings come in three groups.

![The Protection settings section with the Sign-in defence, Protected forms and Hardening groups, the Sync button and the save bar](/_landing/docs/set-up-bettershield-login-protection-in-xcloud-settings.png)

| Group | Setting | What it does |
| --- | --- | --- |
| **Sign-in defence** | **Limit failed sign-ins** | Pauses a connection after too many failed logins. Each pause also bans the connection's whole network at the server firewall for 15 minutes. How many failures, within how long and for how long are set in WP Admin (see the table below). |
| **Sign-in defence** | **Bot check** | Adds a hidden field to the login and comment forms that bots fill in and people never see. |
| **Sign-in defence** | **Proof-of-work check** | After 2 failed logins from one connection, the login form asks the browser to solve a small puzzle, which takes a fraction of a second. A browser without JavaScript cannot solve it and has to wait until the counting window passes. An unsolved attempt does not count toward the pause. |
| **Protected forms** | **Comments** | Limits and checks comment posting. |
| **Protected forms** | **Password resets** | Stops reset-link flooding and account probing on the lost password form. |
| **Protected forms** | **Registration** | Stops bulk fake sign-ups on the registration form. |
| **Hardening** | **Block common usernames** | A sign-in as `admin`, `administrator`, `user`, `user1` or `demo` pauses that connection on the first try, and the server firewall bans it. A name that is a real account on your site is never blocked. Turning it off removes only the names xCloud added; names you blocked yourself in BetterShield stay. |
| **Hardening** | **Block public user listing** | Hides your usernames from signed-out visitors: the REST API user list, `?author=` links, author sitemaps and author names in embeds. The login and lost password forms also stop telling a wrong username from a wrong password. Turn it off if your theme shows author profiles. |
| **Hardening** | **Block pingbacks** | Turns off XML-RPC pingbacks, which attackers use to flood other sites. Other XML-RPC features, such as Jetpack and the WordPress mobile apps, keep working. If you turned off all of XML-RPC in BetterShield, this shows as on, and you change it in WP Admin. |

The **Hardening** group is marked **Was in WP Fail2Ban**: these are the options WP Fail2Ban users know.

Change a setting, and a save bar appears at the bottom of the card with **You have unsaved changes.** Click **Save Settings**, or **Discard** to go back to the values read from the site. The card shows **Saving settings…** while xCloud applies the change. xCloud saves through BetterShield's own settings and security fixes, so the plugin checks the values and keeps its own change history and undo, and then xCloud reads the settings back to confirm.

A save sends every setting xCloud last read from the site, including the failed-login numbers, which the card does not show. If you changed settings in WP Admin since then, click **Sync** next to **Protection settings** first, or the save puts back the values xCloud read before. **Sync** only reads; it does not change anything on the site. The allowed and blocked IP lists are not touched by a save. If a save or a read fails, the card shows the reason, and the settings stay as they were.

The same settings are in WP Admin under **BetterShield → Protect**, sometimes with a different name. The failed-login numbers are only there, not in the xCloud dashboard:

| In xCloud | In WP Admin |
| --- | --- |
| **Limit failed sign-ins** | **Login & Access** tab: **Pause sign-in after repeated failures** |
| Failed logins allowed (3 to 100, default 5) | **Login & Access** tab: **Failures allowed** |
| Minutes the failures are counted within (1 to 1440, default 15) | **Login & Access** tab: **Counted within (minutes)** |
| Minutes the pause lasts (1 to 1440, default 15) | **Login & Access** tab: **Pause lasts (minutes)** |
| **Block public user listing** | **Hardening** tab, **What the site reveals**: **Block public user listing** |
| **Block pingbacks** | **Hardening** tab, **What can run**: **Disable XML-RPC**, set to **Pingbacks only** |

You can also change the three numbers with the [xCloud API](#manage-bettershield-with-the-xcloud-api).

## Configure BetterShield in WordPress

xCloud installs and connects BetterShield, and manages the settings above and its **Server firewall** setting. Everything else, such as two-factor sign-in, passkeys, the audit and the activity log, is set up inside WordPress. Leave **Server firewall** on in BetterShield's settings: it is what lets the server ban repeated failures.

BetterShield's file check reports any change to `wp-config.php` as critical and emails the site's admin address. Changes xCloud makes to that file, such as a save in the wp-config editor, a database password change, SMTP, caching or WP debug, are told to BetterShield, so they do not raise that alert. A change made any other way, for example over SFTP or by a plugin, is still reported.

Click **Open BetterShield in WordPress** on the card. It signs you in to WP Admin with xCloud's [Magic Login](/docs/magic-login-in-xcloud/) as the default user and opens the BetterShield page (`/wp-admin/admin.php?page=bettershield`), so you do not need your WordPress password. If you do not have Magic Login permission for the site, the button opens the WordPress login page instead, and you land on the BetterShield page after you sign in.

## How the server firewall blocks repeated failures

BetterShield first applies its own failed-login limit inside WordPress. When it pauses a connection, it writes one line to the server's log that names the **network**, never an IP address or a username. xCloud's firewall rule reads that line and bans:

-   the whole network: an IPv4 **/24** or an IPv6 **/64**,
-   on the **HTTP and HTTPS** ports only, so SSH and other services are not affected,
-   for **15 minutes**, after which the ban lifts on its own.

Every pause leads to a ban, and the ban always lasts 15 minutes, whatever **Pause lasts** is set to in WP Admin. Because the ban covers a network, other visitors on the same office or mobile network are blocked for those 15 minutes too. For example, one person typing a blocked username such as `admin` can lock the whole office out of the site.

BetterShield only writes that line while its own **Server firewall** setting is on. xCloud turns the setting on when it installs BetterShield, through BetterShield's own settings, so no extra plugin or code is added to your site. xCloud turns it on again each time you save a change from the card. If someone switches it off in WP Admin, **Sync** shows it: the card then says the server firewall is not connected, until the next save from xCloud.

xCloud never bans:

-   Cloudflare's edge networks,
-   localhost,
-   a network that contains the server's own IP address or one of xCloud's IP addresses,
-   a network that contains an IP address you whitelisted for a site on the server under **Security → IP Management**. See [whitelist or blacklist an IP address](/docs/whitelist-or-blacklist-any-ip-address-for-website/).

xCloud checks the whole banned network against these addresses, not just a single IP. A whitelist change applies to the next ban without reinstalling anything.

### Lift a ban early

If a ban blocks someone it should not, such as your own office, you can lift it before the 15 minutes are up. You need permission to manage the server's firewall:

1.  Open the server and go to **Security → Firewall Management**.
2.  Under **Fail2Ban Management**, click **Fetch Now**. A BetterShield ban is listed as a network followed by **bettershield**, for example **203.0.113.0/24 — bettershield**.
3.  Click the **Unban** icon on that row and confirm with **Yes, Unban**.

xCloud lifts the whole network. If you only know the visitor's IP address, unbanning that address also lifts the BetterShield network that contains it, so you do not need to work out the /24 or /64 yourself. The same applies to the xCloud API: `DELETE /servers/{uuid}/fail2ban/banned-ips/{ip}` lifts the address and its BetterShield network.

This lifts the server ban only. BetterShield's own sign-in pause inside WordPress keeps running until **Pause lasts** runs out, so the person may still see that sign-in is paused. To let them in sooner, release the lockout in BetterShield in WP Admin, or use WP-CLI from the site's [web terminal](/docs/access-web-terminal-in-xcloud/) or over SSH. First find the lockout's ID, then release it:

```bash
wp bettershield lockouts list --active --surface=signin
wp bettershield lockouts release LOCKOUT_ID --user=YOUR_WP_ADMIN_USER
```

Avoid `--all`: it releases every running sign-in lockout, attackers included.

One firewall rule serves every BetterShield site on a server. xCloud adds it with the first BetterShield site and removes it when the last one turns BetterShield off. Before it loads the rule, xCloud tests the firewall configuration and rolls back if the test fails, so your other Fail2Ban protections, such as SSH, keep running. For server-level bans in general, see [Fail2Ban in xCloud](/docs/fail2ban-in-xcloud-enhancing-server-security-with-ease/).

## Sites behind Cloudflare

When visitors reach your site through Cloudflare's proxy, BetterShield needs to read the visitor's real IP address from Cloudflare's header. It trusts that header only when its **Cloudflare** setting is on and the request really comes from Cloudflare's networks.

When it installs BetterShield, xCloud turns the setting on if the site uses Cloudflare SSL, [Cloudflare Enterprise](/docs/how-to-activate-the-cloudflare-enterprise-addon/) or an xCloud staging domain, and the card shows the **Cloudflare aware** chip. Otherwise it leaves the setting as it is, so a value you or the plugin already set is kept.

xCloud updates the setting again when you change the site's domain or SSL provider, or when a Cloudflare Enterprise domain for the site is added, removed or changes state. It only turns the setting off if xCloud turned it on.

Behind the proxy, the server firewall sees Cloudflare's addresses instead of the visitor's, and those are never banned. The firewall ban therefore only applies to traffic that reaches the server directly. BetterShield still locks out the visitor at the WordPress login, so proxied sites remain protected.

## Turn off BetterShield

Turn off the switch in the card header and confirm with **Turn Off**. The card shows **Turning off BetterShield** and switches to **Off** on its own when xCloud finishes; you do not need to reload the page. xCloud deactivates and deletes the plugin, so its two-factor sign-in, passkeys and login limits stop working. BetterShield keeps its data on delete unless you chose otherwise in its own settings.

## Manage BetterShield with the xCloud API

The [xCloud Public API](/docs/xcloud-api/) has the same controls. Use a token with the `read:sites` ability to read the state, and `write:sites` to change it. See [How to Access the xCloud API](/docs/how-to-access-the-xcloud-api/) to create one.

| Method and path | What it does | Token ability |
| --- | --- | --- |
| `GET /sites/{uuid}/bettershield` | Returns the state: `enabled`, `status`, `version`, `server_firewall`, `cloudflare`, `legacy_wp_fail2ban`, `eligible`, `ineligible_reason`, `upgrade_eligible`, `upgrading`, `last_error` (the reason the last change or switch failed), and the protection `settings` with `settings_read_at`, `settings_status` and `settings_error`. | `read:sites` |
| `PUT /sites/{uuid}/bettershield` | Turns BetterShield on with every protection on. | `write:sites` |
| `POST /sites/{uuid}/bettershield/upgrade` | Removes WP Fail2Ban, then installs BetterShield. See [Migrate from WP Fail2Ban to BetterShield](/docs/migrate-from-wp-fail2ban-to-bettershield-in-xcloud/). | `write:sites` |
| `PUT /sites/{uuid}/bettershield/settings` | Changes any of the protection settings, including the failed-login numbers. | `write:sites` |
| `POST /sites/{uuid}/bettershield/settings/refresh` | Reads the settings from the site again, like **Sync**. | `write:sites` |
| `DELETE /sites/{uuid}/bettershield` | Turns BetterShield off. | `write:sites` |
| `DELETE /sites/{uuid}/wp-fail2ban` | Only uninstalls the retired WP Fail2Ban plugin. | `write:sites` |
| `POST /sites/{uuid}/magic-login` | Returns a one-time Magic Login URL. Send `{"redirect_to": "admin.php?page=bettershield"}` to open the BetterShield page after login; only pages inside `/wp-admin` are accepted. Needs the Magic Login permission for the site. | `write:sites` |

The write requests run in the background and answer `202 Accepted`. Poll the `GET` request until `status` is `active` or `failed`, until `legacy_wp_fail2ban` is `false` after an uninstall, or until `settings_status` is `null` after a settings change (check `settings_error`). A request answers `409` while another change runs on the site, and `422` when the site cannot take the change, with the reason in `message`.

```bash
curl -X PUT https://app.xcloud.host/api/v1/sites/SITE_UUID/bettershield \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
```

The settings use these fields: `attempt_limit`, `protect_comments`, `protect_password_reset`, `protect_registration`, `bot_check`, `proof_of_work`, `block_common_usernames`, `block_user_enumeration` (shown as **Block public user listing**) and `block_pingbacks` (true or false), and `threshold` (3 to 100), `window_minutes` and `lockout_minutes` (1 to 1440). Send only the fields you want to change. A value out of range answers `422` and nothing is changed.

```bash
curl -X PUT https://app.xcloud.host/api/v1/sites/SITE_UUID/bettershield/settings \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"proof_of_work": false, "lockout_minutes": 30}'
```

For a token that can reach more than one team, add the `X-Team-Id` header. See [multi-team API tokens](/docs/multi-team-api-tokens-and-mcp-access/).

## Frequently asked questions

### Is BetterShield free?

Yes. BetterShield is a free plugin by WPDeveloper, and xCloud installs and manages it at no cost. There is nothing to buy or add to your plan.

### Why is the switch on the WP Fail2Ban & Login Protection card greyed out?

The site does not meet a requirement yet, the server is disconnected, or your role cannot change the site's security settings. Hover over the switch to see the reason, for example WordPress older than 6.7 or PHP older than 8.0.

### Can WP Fail2Ban and BetterShield run on the same site?

No. When a site still runs WP Fail2Ban, turning on the switch removes WP Fail2Ban first and then installs BetterShield, so two login limiters never lock visitors out twice. See [Migrate from WP Fail2Ban to BetterShield](/docs/migrate-from-wp-fail2ban-to-bettershield-in-xcloud/).

### Which protection level does xCloud set?

One setup with every protection on: the failed-login limit, the bot check, the proof-of-work check, comment, password reset and registration protection, and the three hardening options. You can turn any of them off afterwards under [Protection settings](#adjust-protection-settings).

### How long does a server firewall ban last?

15 minutes. The ban covers the whole network BetterShield reported (an IPv4 /24 or an IPv6 /64) on the HTTP and HTTPS ports, then lifts on its own.

### Can I lift a server firewall ban before it expires?

Yes. On the server's **Security → Firewall Management** page, fetch the banned list under **Fail2Ban Management** and click **Unban** on the network. Unbanning a single IP address also lifts the BetterShield network that contains it. This only lifts the server ban: BetterShield's own sign-in pause inside WordPress keeps running until **Pause lasts** runs out, unless you release it in BetterShield too. See [Lift a ban early](#lift-a-ban-early).

### Will BetterShield email me when xCloud changes wp-config.php?

No. When xCloud changes `wp-config.php`, for example from the wp-config editor, a database password change, SMTP, caching or WP debug, it tells BetterShield, so the file check does not report it. A change made any other way, such as over SFTP or by a plugin, is still reported and emailed.

### Does the server firewall block attackers when my site is behind Cloudflare?

No. Proxied traffic reaches the server from Cloudflare's addresses, which are never banned. BetterShield still locks the visitor out at the WordPress login when its **Cloudflare** setting is on. xCloud turns that setting on for sites with Cloudflare SSL, Cloudflare Enterprise or an xCloud staging domain. If you proxy the site through your own Cloudflare account another way, turn on the **Cloudflare** setting in BetterShield in WP Admin. See [Sites behind Cloudflare](#sites-behind-cloudflare).

Still stuck? Contact our [**support team**](/support) for any of your queries.
