Base Firewall
A small set of hardening rules that run inside WordPress: a login throttle, XML-RPC and REST hardening, bad-bot blocking and file-change alerts. Included on every plan at no extra charge, off until you turn it on, and honest about what it is not.
What it is, and what it is not
Base Firewall is a Bonus. It ships with every plan, Free included, there is no entitlement to buy and no upgrade prompt behind the tab. That also sets your expectations correctly: it is basic hardening, offered as-is with no warranty. WP Tender is not a security company, and this is not a replacement for a dedicated security vendor, for keeping WordPress, plugins and PHP current, or for having backups you have actually tested.
Bonus on every plan
Included at no extra charge, Free tier included. No entitlement to buy, no upgrade prompt — and offered as-is, with no warranty.
Hooks, not an inline WAF
Standard WordPress hooks inside your own site. Nothing sits in front of your web server, so traffic that never reaches WordPress is never seen.
Off, Learning, Enforce
Off is the default and is completely inert. Learning records what would have been blocked. Only Enforce blocks anything.
Fails open, by design
Every block path is wrapped so an internal error lets the request through. A broken rule can fail to match; it cannot take your site offline.
It is hooks, not an inline firewall
Every rule is an ordinary WordPress hook — authenticate, wp_login_failed, wp_login, xmlrpc_enabled, rest_authentication_errors, init and a WP-Cron event. Nothing is written to .htaccess, .user.ini or auto_prepend_file, and nothing sits in front of your web server.
The consequence is the single most important sentence on this page: traffic that never reaches WordPress is never seen by Base Firewall. A request served from a full-page cache, a static asset handed straight back by nginx, a request your host or CDN already turned away, a flood aimed at the network rather than at PHP — none of it reaches a hook, so none of it is counted, recorded or blocked. There is no request-body inspection and no virtual patching.
The other consequence is a good one: because it is only hooks, a rule that errors fails open. Every block path is wrapped so an internal error logs and lets the request through rather than denying it. A misbehaving rule cannot white-screen your site.
Enforce mode blocks real requests and can lock real people out — you, your client, or a service the site depends on. That is exactly what the IP allow-list is for. Add your own address before you switch to Enforce, and spend a few days in Learning first.
Where it lives in the Manager
Base Firewall is configured per site. Open Sites → the site → WP Tender Control Plane → Base Firewall (the tab carries a BONUS badge). There is no fleet-wide switch: each site has its own mode, its own rules and its own allow-list, because the traffic that is normal for one site is not normal for the next.
Every site starts at mode Off with every rule off. A site that has never been opened on this tab behaves exactly as it did before the feature existed.
Changes save the moment you click. Each save writes the setting in the Manager and then pushes it to the site over the existing paired channel. If the site is unpaired or unreachable the save still succeeds locally and you get a “Saved — not applied on the site yet” message — the rule is recorded in the Manager but is not live on the site. Save again once the site answers to push it across; nothing on the site side goes looking for the config on its own.
On a team, editing Base Firewall needs the Base Firewall capability, which is flagged critical in the team capability list precisely because Enforce mode turns traffic away. Anyone who can see the site can read the settings and the events; only holders of that capability can change them.
The three modes
The mode is the master switch, and it applies to every rule at once. Rule toggles keep their values when you go back to Off — turning the mode down is a pause, not a reset.
| Mode | What actually happens |
|---|---|
| Off | The default, and completely inert. Every rule callback reads the config and returns immediately. Nothing is counted, nothing is recorded, nothing is blocked, and the daily file check is unscheduled. |
| Learning | Rules are evaluated and every match is recorded as an event — but nothing is blocked. This is the mode to sit in for a few days so you can see what Enforce would have cost you. The one exception: with the XML-RPC rule on, WordPress already reports XML-RPC as unavailable. |
| Enforce | Rules block for real: locked-out logins get an error on the login form, XML-RPC POSTs and matched bots get a 403, anonymous hits on the REST user route get a 401. File-change alerts stay alert-only. This is the mode that can lock people out, which is why the allow-list matters. |
Off is not a flag the rules consult politely — every hook callback reads the config and returns immediately when the mode is Off, so an inert site pays nothing but an option read. The rule toggles and the allow-list only appear in the card once the mode is Learning or Enforce, because outside those two modes they do nothing.
One thing Learning mode does change
Learning blocks nothing, with a single honest exception. When Block XML-RPC is on in either active mode, WordPress is told through the xmlrpc_enabled filter that XML-RPC is unavailable — so a client that asks politely gets “no” even in Learning. What Learning holds back is the hard stop: direct POSTs to xmlrpc.php are recorded rather than refused. Everything else — the login throttle, REST hardening, bad bots — is purely observational in Learning.
The rules
Five toggles, each independent, each off by default. Turning one on does nothing until the mode is Learning or Enforce.
Brute-force login throttle
Counts failed logins per IP address and, in Enforce mode, refuses further login attempts from an address that has gone over the line. Three numbers, editable inline once the toggle is on:
- Max failures — how many failed logins an address may rack up before it is over the line. Default 20, stored anywhere from 1 to 1000.
- Lockout (mins) — the figure quoted back to the visitor on the login form (“try again in N minutes”). Default 30, stored from 1 to 1440.
- Count window (mins) — how long a failure is remembered. Default 240 (four hours), stored from 1 to 10080 (a week).
How it behaves, precisely:
- The counter is per IP address, not per username, and it lives in a WordPress transient whose lifetime is the count window. Each new failure resets that lifetime, so an address that keeps trying keeps its own counter alive.
- The check runs on the
authenticatefilter after WordPress’ own checks, and only for a request that actually carries a username — an empty-credentials probe trips nothing. - When it blocks, it returns a normal WordPress login error, so the visitor sees a message on the login form. There is no white screen and no
wp_dieon the login path. - A successful login clears that address’s counter, so someone who fumbles their password and then gets in is not left half-locked.
- Three things are never counted and never locked out: private and loopback address ranges, anything on your allow-list, and any request carrying a valid WP Tender agent token — which is why switching Enforce on cannot cut the Manager off from its own site.
Be clear about how long a lock lasts. The lock lifts when the failure counter expires, and that is governed by the count window measured from the last failed attempt — not by the Lockout (mins) figure, which is the number shown to the visitor. A blocked attempt is still a failed attempt, so an address that hammers the login form keeps refreshing its own window and stays locked for as long as it keeps trying. If you want to reason about a single number, set Lockout and Count window to the same value.
Block XML-RPC
xmlrpc.php is WordPress’ legacy remote-procedure endpoint. Most sites do not use it, and the ones that do usually know they do — the Jetpack app, some publishing clients, a few pingback flows. With the rule on:
- WordPress reports XML-RPC as disabled through
xmlrpc_enabled, in Learning and in Enforce alike. - A direct
POSTtoxmlrpc.phpis recorded in Learning, and refused with a plain 403 in Enforce. - The refusal happens only on that endpoint. No other URL on the site is touched by this rule.
- Requests carrying a valid WP Tender agent token pass through untouched.
Note the shape of the exemption: XML-RPC is the endpoint, not the visitor. This rule does not consult the IP allow-list — allow-listing your own address will not let you POST to xmlrpc.php on a site in Enforce. If something you rely on speaks XML-RPC, leave this rule off.
Harden the REST API
WordPress publishes its user list at /wp-json/wp/v2/users by default, which is a free list of valid usernames for anyone who asks. This rule closes that door to anonymous callers.
- It matches requests whose URI contains
/wp/v2/users— which covers both the pretty/wp-json/…form and the?rest_route=…fallback. - Logged-in users are never affected, so the block editor, user pickers and anything else inside WP admin keep working. Requests carrying a valid WP Tender agent token are likewise untouched, so the Manager’s own user management keeps working.
- In Enforce, an anonymous request to that route gets a 401 with an explicit message. In Learning it is recorded and allowed.
- If another plugin has already refused the request, this rule leaves that refusal alone rather than replacing it.
This is the narrowest rule in the set, on purpose: it covers the user route and nothing else. The rest of your REST API is untouched.
Block bad bots
Matches the request’s User-Agent against a small, deliberately conservative built-in list of automated probing tools — the ones that announce themselves by name, such as sqlmap, nikto, nmap, nessus, acunetix, dirbuster, gobuster and havij. Matching is case-insensitive.
- In Enforce a match is refused with a 403; in Learning it is recorded.
- An empty User-Agent is left alone. It looks suspicious, but plenty of legitimate uptime checks and health probes send nothing, and turning those away would cost more than it saves.
- Allow-listed addresses, private ranges and the WP Tender agent are exempt — so your own tooling is not caught by its own rule.
Two limits worth stating plainly. The list is fixed — the Manager does not currently expose a way to add your own patterns. And any tool that bothers to send a browser User-Agent walks straight past this rule. It removes noise; it does not remove a determined visitor.
File-change alerts
A daily WP-Cron job that hashes a short list of files, compares the hashes against the previous run and records an event for every difference. Alert only — it never blocks anything, never quarantines and never reverts a file.
What is watched:
wp-config.php— including the layout where it sits one directory above WordPress.- The main file of every active plugin.
- The active theme’s
functions.php— both the child theme’s and the parent’s when they differ.
That is the whole list. It is a handful of files that matter, not the tree — a change deep inside a plugin directory will not raise an event. Three kinds of difference are recorded: a watched file whose hash changed, a newly watched file, and a watched file that has gone away.
The job is scheduled about an hour after you turn the rule on, then runs daily, and is unscheduled the moment you turn the rule off or drop the mode to Off. It rides on WP-Cron, which only fires when someone visits the site — on a quiet site, turn on Server cron in the site’s System overview tab so it actually runs.
The first check after switching the feature back on is noisy, and that is correct. On a site that has never run a file check, the first run only records the hashes and reports nothing. But if a site already has a stored set of hashes from an earlier stint — for example the rule was on, then the feature went quiet across an upgrade, and you have now switched it back on — the first check compares against that older set and reports every change made in between. Plugin updates, theme edits and wp-config.php tweaks from that period all surface at once. Read them, take the ones you recognise as noise, and the next day’s run starts from the new baseline. Nothing is ever blocked on the strength of a file-change event.
The IP allow-list
The allow-list is the reason Enforce mode is safe to try. Type an address into the field, press Add, and it is stored against that site; the chips below remove themselves on click.
What it covers:
- The login throttle. An allow-listed address is never counted and never locked out.
- Bad-bot blocking. An allow-listed address is exempt even when its User-Agent matches.
What it does not cover, and you should know before you rely on it:
- XML-RPC and REST hardening ignore it. Those two rules are about the endpoint, not the caller — their exemptions are “a logged-in user” (REST) and “the WP Tender agent” (both). Allow-listing your address will not reopen
xmlrpc.php. - Matching is exact. The site keeps single addresses and compares them literally. The field will accept a CIDR range and the Manager will store it, but the site drops anything that is not a plain address, so a range never matches. List the addresses themselves.
- Fifty entries. The Manager de-duplicates the list, keeps the order you added them in and stores at most fifty.
Three things are exempt without you doing anything:
- Private and loopback ranges. Anything that resolves to a local or reserved address is treated as allowed.
- The WP Tender agent. A request carrying a valid agent token is never blocked, so backups, migrations, metric pulls and plugin updates keep working in Enforce.
- An address the site cannot read at all. If the client address is missing or unparseable the request is treated as allowed — the engine would rather let something through than lock out somebody it cannot identify.
The card warns you when Enforce is on with an empty allow-list, and it is not being dramatic. Add the public address you administer the site from before you switch modes. If your home or office address is dynamic, that is a reason to stay in Learning rather than a reason to skip the step.
Reading the events list
Once the mode is Learning or Enforce, the card shows the recent events for that site. The heading counts them, and it changes with the mode: “N would-be-blocks in recent events” in Learning, “N Base Firewall events recently” in Enforce.
The site keeps its own ring buffer of the last 200 events. The Manager polls it, stores what it has not seen before, and shows the newest 100, newest first. The list loads when you open the tab and when you press the refresh control in the card header; it does not poll on its own.
Each row is four columns: the action, the address, the rule and its detail, and how long ago.
- observed — the rule matched and nothing was done about it. This is what Learning mode produces, and it is also what a file-change alert always produces.
- blocked — the rule matched in Enforce mode and the request was actually turned away.
The rule column carries the kind:
| Kind | What it means |
|---|---|
brute_force | An address went past the failure threshold. The detail carries the failure count and the threshold, or the lockout figure when it actually blocked. |
xmlrpc | A direct POST to xmlrpc.php — recorded in Learning, refused with a 403 in Enforce. |
rest_users | An anonymous request to the REST user route. Logged-in users and the WP Tender agent never appear here. |
bad_bot | A User-Agent matched the built-in list. The detail names the pattern that matched. |
file_change | A watched file changed, appeared or went away. Always alert-only, and always without an address — the row shows a dash. |
How to read it in Learning. Every row is a request Enforce would have turned away. Work down the list and ask one question per row: would losing that request have cost me anything? Failed logins from addresses you do not recognise, probing tools announcing themselves by name, anonymous hits on the user route — that is exactly the traffic the rules exist for. Your own office address appearing in a brute_force row is the signal to add it to the allow-list before you go any further. A monitoring service or a client’s publishing tool appearing anywhere is the signal to leave that particular rule off.
An empty list means one of two things. Either nothing has matched — normal for a quiet site in its first hours — or the rules are not live on the site. Check the mode is not Off, and check your last save actually reached the site (see the troubleshooting note below).
If the site is unreachable when you open the tab, the Manager serves the events it already stored rather than showing an error, so the list stays readable while a site is down. A site running an agent build that predates the feature simply reports nothing.
Switching it on, in order
- Take a backup first. Not because Base Firewall writes anything to your site — it does not — but because the honest order of operations for any hardening change is to have a rollback before you make it.
- Set the mode to Learning. Nothing is blocked from this point until you change it yourself.
- Turn on the rules you want. If you are unsure, the login throttle and REST hardening are the two that upset the fewest sites. XML-RPC is the one most likely to be load-bearing somewhere.
- Add your own address to the allow-list. Yours, your colleagues’, and anything you administer from.
- Wait. A few days is enough to catch weekly jobs and the client who only logs in on Fridays.
- Read the events and adjust the rules or the allow-list until the list contains only traffic you are content to lose.
- Switch to Enforce — one site first, not the whole fleet on the same afternoon.
Limits worth knowing
Stated once, plainly, so nothing here surprises you later.
- It only sees WordPress. Cached pages, static files and anything stopped before PHP runs never reach a hook. See above.
- It reads the address your host gives it. The engine uses the connection’s own remote address and deliberately does not trust forwarded-for headers, because trusting them would let anyone claim to be on your allow-list. On a site behind a proxy or CDN where the host does not rewrite that address, every visitor looks like the proxy — and if the proxy sits in a private range, the exemption for private ranges means nothing is ever counted or blocked. If your events list stays empty on a busy site, this is the first thing to check with your host.
- The allow-list is exact addresses only, and it does not apply to the XML-RPC or REST rules.
- File-change alerts watch a short list of files, once a day, and never block.
- The bad-bot list is small and fixed, and only catches tools that identify themselves.
- Rules go live when a save reaches the site. A save made while the site is unreachable is stored in the Manager only.
- Offered as-is, with no warranty. We accept no liability for what it fails to stop, for legitimate traffic it turns away, or for any consequence of switching it on. It is basic hardening. Keep your software current, keep your backups, and if a site genuinely warrants a security product, buy one.
Troubleshooting
“Saved — not applied on the site yet”
The setting is stored in the Manager but the push to the site failed — the site is unpaired, down, or its REST route is blocked. The rule is not live. Fix the pairing (see the Agent notes), then open the tab and save the setting again to push it across.
I locked myself out of a site
Only the login throttle can do this, and only in Enforce. The lock is a WordPress transient keyed to your address and it lifts on its own once the count window passes without another failed attempt — so the first remedy is to stop trying and wait. If you cannot wait, set the site’s mode back to Off in the Manager: the block is evaluated live on every request, so the next attempt goes straight through. Then add your address to the allow-list before you turn Enforce back on.
The events list is empty on a busy site
In order: check the mode is not Off; check the rules you expect are actually on; check the last save reached the site; then check whether your host is passing the real visitor address through — see Limits. A proxy that presents every visitor as a private address will produce exactly this symptom.
Something legitimate is being blocked
Find it in the events list and read the kind. A brute_force row means an address for the allow-list. A bad_bot row means a tool announcing itself by name — add its address to the allow-list, or turn that rule off. An xmlrpc or rest_users row cannot be fixed with the allow-list: turn that rule off, because those two rules are about the endpoint rather than the caller.
A wave of file-change events after an update
Expected. Updating plugins changes their main files, and the next daily run reports each one. The events are alert-only; nothing was blocked and nothing was altered. Read them, confirm they line up with updates you ran, and the following day starts clean.
Does turning on Enforce break my backups?
No. Every rule exempts requests carrying a valid WP Tender agent token, so backups, restores, migrations, metric pulls and remote updates all keep working with Enforce on.
See also: Using the Manager · WP Tender Agent · FAQ.