PBJ SEO & Security — Changelog
PBJ SEO is our lightweight, first-party SEO plugin — a single-file replacement for heavyweight suites like Yoast or Rank Math. It outputs document titles, meta descriptions, canonical URLs, robots directives, Open Graph and Twitter Card tags, JSON-LD structured data, and sitemap <lastmod> dates, all with zero third-party dependencies. Here’s what has shipped in every version.
4.7.1
One fix. “Hide the WordPress version” was stripping other plugins’ cache-busters. The check matched ver= as a substring, so on WordPress 7.1 any asset stamped ver=7.1… lost its version — and a browser went on serving the previous release’s CSS and JavaScript after an update, which looks exactly like an update that did not install. The parameter is now parsed out of the query and removed only when it equals the WordPress version exactly. Nothing else changed, and the setting still hides the version everywhere it did before.
4.7.0
A security release, and the biggest one since 4.0.0. It closes 19 findings from a full audit of the security modules — including three that mattered: the firewall never actually read JSON request bodies (so the entire REST API was pattern-matched against nothing), ?rest_route= walked past every path rule, and ANY signed-in account — a subscriber on a site with open registration — switched the whole firewall off for its own requests. Anonymous calls to the REST batch endpoint the wp2shell exploit chain uses (CVE-2026-63030 / CVE-2026-60137) are now refused by default. Request bodies are now inspected at both ends rather than skipped when large, without ever loading the whole upload into memory. Seven new hardening switches (including one that stops readme.html and license.txt handing out your exact WordPress version), email alerts, and a wider malware sweep that finally looks in mu-plugins, the wp-content drop-ins and the site root. Every new switch ships OFF so upgrading changes nothing about how your site behaves — except the three pure fixes above and the batch-endpoint block, which are on. The admin screens are also now plain WordPress admin styling rather than borrowing PBJ CRM’s theme. No schema change beyond a settings migration that carries your XML-RPC setting into its new three-way form.
Firewall — three real holes, closed.
- Fixed: JSON request bodies were never inspected. The body the pattern engine matched against was
wp_json_encode( $_POST ), and PHP does not populate$_POSTforContent-Type: application/json. So every REST request — which is to say the whole surface modern WordPress exploits actually use — was matched against an empty string. The rawphp://inputstream is now read when the content type says JSON, or when a body arrived that PHP chose not to parse. - Fixed:
?rest_route=bypassed every path rule. WordPress accepts every REST route twice:/wp-json/xand/?rest_route=/x. Path rules matched the first spelling and none of them matched the second, which made the plain spelling a bypass for the entire path engine including the known-probe list. The route is now folded into the path before matching, so one rule answers both. - Fixed: any signed-in user switched the firewall off.
request_is_authenticated()trustedis_user_logged_in()with no capability test, so on a site with open registration one free subscriber account was one free firewall exemption. New setting Who is trusted enough to skip the firewall: any signed-in user (the default, so upgrading changes nothing), contributors and above, or administrators only. Filterpbj_seo_waf_trusted_cap. Application-password traffic — PBJ Studio Sync, the MCP connector — stays trusted as before. - New: anonymous calls to
/wp-json/batch/v1are refused (settingwaf_block_anon_batch, on by default). This is the endpoint the wp2shell chain uses; core only ever calls it as a signed-in editor, so a logged-out caller has no legitimate business there. Both URL spellings are covered, and the same block is repeated atrest_pre_dispatchwhere the route is the router’s own answer rather than a guess. A user-agent rule catches thewp2shelltooling by name. - Fixed: a request body over 256KB was SKIPPED entirely, which is a rule an attacker satisfies with padding. Bodies are now inspected in TWO 256KB windows — the first bytes and the last bytes — so padding either end costs the attacker the payload, and the log records which end it matched in. The body is never held in memory whole: the stream seeks to its own end for the tail (or is walked in 64KB chunks keeping a rolling tail where the stream will not seek), so an 8MB upload costs about half a megabyte to inspect rather than 8MB. A body small enough to fit in two windows is simply inspected whole, so there is no seam in the middle of an ordinary request.
- Fixed: double-encoded payloads (
%252e%252e%252f) matched neither the raw nor the once-decoded rule. A third URI view decodes twice, capped at two passes. - Fixed:
waf_country_check()decided “is this the login screen” withstripos( $uri, 'wp-login.php' )— a substring test standing in for an endpoint test, so/?x=wp-login.phpcounted and a subdirectory install did not. Same for the body-exemption list, which compared its prefixes against the whole URI including the query string. Both now compare the decoded path. - New:
/readme.html,/license.txtand/wp-content/debug.logare answered as known probes (on by default with the rest of the probe list). Note that a firewall written in PHP can only answer a request PHP is given: whilereadme.htmlactually exists, Apache serves it off disk without starting WordPress, so the probe rule covers the file-absent case and non-Apache hosts — and the new Hide server files & directory listings switch below refuses it at the webserver, which is the half that works when the file is really there. - New: optional per-IP rate limit on logged-out
admin-ajaxcalls, 60 a minute (waf_nopriv_ajax_throttle, off by default — plenty of themes load real content that way). - The
pbj_seo_request_is_authenticatedfilter is now documented as privileged, and writes onewaf_filter_bypasslog line whenever a callback changes the verified answer.
Hardening — seven new switches, every one off by default.
- Stop user enumeration — removes the
/wp-json/wp/v2/usersroutes for logged-out callers, 404s?author=N(numeric only;?author_name=and pretty author archives are untouched), makes login errors generic, and drops the users provider from the XML sitemap. That last part is also the author-sitemap removal that has been on the roadmap since 4.0.0. - Refuse to run PHP inside uploads — writes
<uploads>/.htaccessdenying.php/.phtml/.pharwith Apache 2.4 syntax and the 2.2 fallback, on Apache and LiteSpeed only, with our own markers, removed again on toggle-off and on deactivation. Alongside it, always on and with no switch: an upload whose FILENAME disguises a PHP file (shell.php.jpg) is refused outright. - Hide server files & directory listings — a PBJ SEO-owned
.htaccessblock carryingOptions -Indexesplus aFilesMatchthat refusesreadme.htmlandlicense.txt. Those two name your exact WordPress version to anybody who asks, which is the first thing a scanner checks before deciding a site is worth an exploit attempt, and because they are real files only the webserver can refuse them. Off by default because a few hosts forbidOptionsin.htaccess, where it is a 500 rather than an ignored line; your file is copied first and switching it back off removes the block. - XML-RPC now has a middle setting — Restricted keeps the endpoint working and removes
system.multicall(the brute-force amplifier) and the two pingback methods (reflected DDoS, internal port scanning). Your existing on/off setting is migrated into the new three-way form on upgrade, preserving exactly what it did. - Block plugin and theme installation — denies the install/upload/update capabilities. Flagged clearly because it also blocks updates, and
DISALLOW_FILE_MODSin wp-config.php is the stronger form. - Disable application passwords — with an explicit warning that PBJ Studio Sync authenticates with one.
- Throttle comments and registrations — 5 per 10 minutes per address plus a honeypot field. Nothing in the comment text itself is examined; a firewall that pattern-matches prose deletes the comments of anybody who writes about code.
Monitoring.
- New: email alerts. One address, plain text, at most one message per event type per hour: an automatic ban, a modified core file, a malware finding, a new administrator, a plugin or theme installed. Empty address means alerting is off — which is the default. Hangs off the log writer, so anything recorded can be alerted on and the two can never disagree.
- New: always-on install and theme-change auditing.
upgrader_process_completeandswitch_themeare now recorded regardless of the activity-log setting, with who did it, what changed, the version and the source URL — as is any new administrator. Everything an attacker does after they are in goes through the installer, and the activity log ships off, so on a default install the single most important event on the site was recorded nowhere at all. - New: known-vulnerable core version warning. A banner on the Security tab and a Checklist row when this install sits inside the wp2shell range (fixed in 7.0.2, 6.9.5 and 6.8.6; releases before 6.8 are unaffected). Advisory only — it never blocks anything — and it names wp2shell explicitly. Reads WordPress’s own update data; no new external call.
- Security headers are now also sent on wp-login.php and wp-admin. They were registered on
send_headers, which those two screens never fire — so the login form and the entire dashboard, the only pages where a clickjacking frame costs somebody their session, were the only pages shipping none of them.X-Frame-Options: DENYon the login screen specifically,SAMEORIGINelsewhere, plus a newPermissions-Policyswitching off geolocation, camera and microphone.frame-ancestors 'self'is unchanged.
Malware scan.
- The signature sweep now also covers mu-plugins (recursive), the wp-content drop-ins (
object-cache.php,advanced-cache.php,db.php,sunrise.php) and PHP files in the site root. Those are the three places a backdoor actually lives, and none of them was being looked at. Core’s own root files are excluded by comparing against the official checksum manifest the integrity scan already fetches — so no extra network call, andwp-config.php, which is not in that manifest, is flagged only if it genuinely matches a signature.
Admin screens.
- The PBJ CRM theming bridge has been removed. 4.5.0 wrote this plugin’s admin stylesheet entirely in
var(--pbjcrm-*, <literal>)and published PBJ CRM’s theme variables onto the page. That made a security plugin’s appearance depend on another plugin being installed — and most sites run PBJ SEO on its own, so most sites got a screen styled by fallbacks rather than designed. It also meant any CRM release that renamed a variable would silently take a colour off a screen nobody would think to test. The screens are now plain WordPress admin —.postboxcards,.nav-tab-wrappertabs,.widefat.stripedtables, core buttons with no colour overrides, and WordPress’s own status colours — so they look the same with the CRM installed and without it, and they follow the user’s own admin colour scheme. - Unchanged on purpose: the firewall block page, the 2FA and login-screen chrome, and every email body. None of those is a wp-admin screen and none of them ever carried this styling.
Migration. DB_VERSION moves to 4.7.0 to run one settings migration: the old harden_xmlrpc boolean is carried into harden_xmlrpc_mode (on stays fully off, off stays off). No table or column changes.
4.6.1
Finishes what 4.6.0 started. If the scan had already recorded findings in a place that 4.6.0 excludes, those entries stayed on your screen with no button to clear them — because they were already excluded, so there was nothing left to allowlist. Updating now clears them once, by itself. And the Scanner exclusions panel has gained a Clear button that tells you exactly how many old entries it will remove, for any that turn up later. Nothing about your settings, your scans or your logs changes otherwise.
4.6.0
An honest answer when the scan is wrong about your own plugin. The malware scan matches patterns, not intentions. A perfectly legitimate plugin that compresses its templates, encodes a licence key, or ships a block of encoded data looks, to a pattern, exactly like the thing being hunted for. That bluntness is what makes the scan worth running — and it means honest plugins get flagged sometimes. Until now the only answers were to quarantine a file you knew was fine, or switch the scan off.
Every finding now carries an Allowlist button beside Quarantine, in the scan results and in the Logs tab. If the finding sits inside a plugin, it offers the whole plugin folder; if it does not, it offers just that one file. Allowlisting also clears the entries that finding has already produced, so the screen matches the decision you just made.
Everything you allowlist is listed on the Security tab under Scanner exclusions — what it is, when it was added, why, and a Remove button on every row. An exception is always something you can see and take back. It is never a hidden rule, and never a switch that quietly turns off part of the scan, because a scanner that has been silently told to stop reporting looks exactly like a clean site.
Two smaller things come with it. Excluded plugins are dropped when the scan builds its file list, so they are never opened at all and the sweep gets faster as well as quieter. And a plugin you allowlist is covered wherever a copy of it lives — a staging folder, a backup, a migration dump — so you make the decision once rather than once per folder.
4.5.1
A tidy-up on top of 4.5.0. With the new Country column and the extra buttons beside each log entry, the security log table could end up slightly wider than the card holding it, so the last button appeared to hang outside the border. The buttons now stack neatly instead, and every panel on these screens has a hard limit so nothing can spill out of it again. Nothing else changed — if you are already on 4.5.0, everything works; this just looks right.
4.5.0
Security fix — please update. Sending one extra header with a request was enough to skip the firewall entirely. The exemption that lets your own logged-in traffic and your API integrations through was checking whether an Authorization header was present, and never whether the credential in it was real. So anyone who knew could send Authorization: Bearer anything and walk past the IP block list, country blocking, reputation checks, every exploit rule and every custom rule you had written. Your WordPress login was never affected — nobody could get into anything — but the firewall could be told to look the other way. The credential is now actually verified before it earns an exemption, and a header that is not a real WordPress credential earns nothing.
Three more holes closed while we were in there. The “email me a way back in” escape hatch was matching a piece of text anywhere in the address rather than the actual page, so a crafted URL could use it to skip pattern checks. Country blocking could be walked past by any request claiming to be Googlebot, because a user agent is simply typed by whoever is sending the request — crawlers are now verified properly, by looking up the visitor address and checking it really does belong to Google, Bing or whoever it claims. And a malformed entry in the trusted-proxy list could quietly mark every address on the internet as trusted; that is now rejected.
Also: a temporary Spamhaus outage no longer buys a bad address a free pass. A failed lookup and a genuine “this address is fine” were being remembered identically for twelve hours — including the “you have used up your free quota” answer that a busy site is most likely to get. A failed lookup is now forgotten after five minutes.
New: three strikes, then a ban. Two separate counts are kept for every address — firewall blocks, and sign-in lockouts. Whichever reaches three first (within a day, both adjustable) adds that address to your block list. They are deliberately never added together: tripping two different things twice each is far more likely to be a real person having a bad day than an attack. Every ban made this way is listed on the Security tab with what it was banned for and a Remove button, because a list you cannot tell apart from your own typing is a list nobody dares touch. Addresses on your always-allow list can never be banned this way.
New: the background scanning stops filling your log. Every public site gets a constant trickle of requests for software you do not run — another content system, a database console, a leftover backup file, the usual dropped scripts. Those are now turned away immediately, before the heavier rules run. And when the same thing is tried hundreds of times, you get one line in the log with a count on it rather than hundreds of identical lines burying everything worth reading.
New: a Country column in the security log, and one-click blocking. Where your CDN tells us the country, it is shown beside each entry at no cost. If you also run PBJ Geo Viewer 1.7.0, any missing countries can be looked up on demand, and every firewall or sign-in row gains Block this IP and Block RU (or whichever country it is) buttons that hand the block straight to Geo Viewer. Both ask you to confirm first, because blocking a whole country from one log row is a big thing to do by accident.
New: a six-step setup wizard, the first time you activate. The same shape as the PBJ Invoicing one: short questions, every step skippable, nothing blocked if you close the tab, and you can run it again any time from the Checklist tab. It covers your business details, what the plugin publishes, your Google and Bing codes, sign-in security and the firewall. One step is worth singling out — it offers to add the address you are sitting at to the never-lock-me-out list, already ticked. That is the cheapest insurance there is against locking yourself out of your own site.
Changed: these screens now follow your PBJ CRM theme, and work on a phone. With PBJ CRM installed, the SEO and Security screens pick up whichever look you chose over there — including the dark ones. Without it, they look exactly as they always did. And the whole admin area now reflows properly on a phone: the security log becomes one readable card per entry instead of a table you have to drag sideways.
4.4.1
Maintenance only: the bundled licence-update client was refreshed so it reports its own version and warns when an older copy has loaded first on a site running several PBJ plugins. No change to the plugin itself.
4.4.0
If you have ever switched on the idle session timeout, this release is the one you want. With that setting turned on, signing in could log you straight back out again — every time, with no way through. The plugin was comparing the clock against a timestamp left over from your previous visit, so your brand-new session looked hours old the instant it began. It now starts the clock when you sign in, and treats a missing timestamp as fresh rather than ancient. The timeout still works normally once you are actually idle.
New: an always-allow list, so you can never lock yourself out. Addresses on it bypass every gate — the firewall, login rate limiting, the idle timeout, country blocking, and the secret login address. You can set it three ways: on the Security tab, in code with a filter, or — the one that matters — with a PBJ_SEO_ALWAYS_ALLOW line in wp-config.php, because that file can still be edited over FTP while you are locked out of the site.
New: a lockout recovery email. If the firewall or the rate limiter has shut you out, the login screen now offers to email you a way back in. The email only ever goes to the site administrator’s address already on file — never to whatever address is typed into the form — so it cannot be used against you. The link works once, only from the address that asked for it, expires in ten minutes, and lets that one address back through for about an hour. It never switches your protection off.
Blocked pages now explain themselves in plain English and offer that recovery form, instead of a bare “403 Forbidden”. The login screen also tells you when you have been signed out for being idle, rather than just appearing.
4.3.0 — August 2, 2026
A Caching tab, and pages that are never saved. Everything about saved copies of your pages now lives in one place — the on/off switch, the button that clears saved copies, and a new list of pages that must never be saved. The switch and the button moved here from the Options tab, which points you across.
The new part is the never-save list. Some pages are different for every single person who opens them — somebody’s customer list, their support tickets, their invoice. If the server saved one of those and handed the copy to the next visitor, that visitor would be looking at someone else’s information. PBJ SEO now recognises those pages in PBJ CRM, PBJ Helpdesk, PBJ Invoicing, PBJ Access Control and PBJ Form Builder and keeps them out of the saving automatically. Rows only appear for plugins you actually have, and they start switched on — you do not have to find the tab to be protected.
You can also tick any page of your own, or type web addresses one per line; anything starting with what you type is covered. Pasting a full web address works, it gets trimmed for you. Saving the tab clears the copies already saved, so a page you have just protected stops being served from an old one straight away.
For developers: other plugins can add their own row with the new pbj_seo_cache_exclusions filter.
4.2.0 – 4.2.2 — August 2, 2026
Three fixes to how saved copies are handled, which belong together. 4.2.0: turning caching off used to leave an instruction behind in .htaccess, so the server kept saving copies to its own rules while the plugin had stopped marking personal pages as never-save — off now removes the block properly. It also added the manual “Purge cached pages now” button, and made the cache clear itself after any plugin or theme install, update, delete or switch.
4.2.1: on a LiteSpeed server with no LiteSpeed Cache plugin, the server had no way of knowing which visitors were signed in, so a page saved for a passer-by could be handed to signed-in staff. The instruction block now tells the server to skip its saved copies for anyone carrying a sign-in, comment, page-password or shopping-cart cookie.
4.2.2: that instruction was being written to the bottom of .htaccess, underneath WordPress’s own block — and WordPress’s block ends with a rule that stops the server reading any further, so it was never once looked at. It is now written above WordPress’s block, where it works. Everything else in the file is left exactly as it was.
4.1.0 — July 28, 2026
The master SEO checklist. The Checklist tab is now the complete SEO to-do list for a site, not just the page audit.
- Five sections — SEO Basics, Keyword Research, Technical SEO, On-Site & Content, Off-Site SEO — with live progress counts per section and overall.
- Auto-verified items turn green the moment the site satisfies them: HTTPS, one canonical version, sitemap, robots.txt, schema, canonicals, permalinks, analytics, Search Console / Bing verification, title/description health, a clean 404 log, and more.
- Every fixable item carries a Fix button to the exact tab or screen — and items solved by a plugin setting get a one-click Enable now button.
- External jobs (Search Console setup, sitemap submission, keyword research, backlinks, Google Business Profile…) come with numbered instructions, a link to the right tool, and a Mark done button whose state is remembered. Undo any time.
- The per-page audit remains below as “Pages that need attention” and feeds the title/description items.
Version 4.0.1 — hardening the security modules
Released July 22, 2026. A robustness and security pass over the 4.0.0 modules — everything still ships off by default.
- Fail-proof database-prefix rename. A full database backup must be downloaded before the rename unlocks; it verifies wp-config.php can be rewritten before touching the database, and fully self-restores (tables and role keys) if anything fails — it can never lock you out.
- Firewall false positives fixed. Code and path rules match the URL only, so an ordinary blog comment or contact message that merely mentions code is no longer blocked.
- Login hardening. Two-factor now issues single-use recovery codes (shown once at setup) so a lost authenticator or broken mail server can’t lock you out, plus a wrong-code limit and replay protection, and a fix for a fatal on front-end logins.
- Accurate visitor IPs. The Cloudflare visitor-IP header is trusted only when the request really arrives through Cloudflare, closing a spoofing gap that could bypass the allowlist or login lockout.
- The admin sidebar is now labeled SEO & Security with a lock icon.
Version 4.0.0 — PBJ SEO becomes PBJ SEO & Security
Released July 22, 2026. Our biggest release yet: the plugin is renamed PBJ SEO & Security and gains a whole security half alongside the SEO tools you already have. Every security feature ships OFF — upgrading changes nothing until you turn something on — and nothing contacts an external service unless you enable both a master switch and the feature.
- Web Application Firewall. Inspects anonymous requests before WordPress routes them and blocks SQL injection, XSS, directory traversal, file inclusion, and PHP-injection probes. Manual IP block/allow lists, header-based country blocking, a log-only dry-run mode, and custom virtual-patch rules. Logged-in users and your own API traffic are never inspected.
- Malware scanning & remediation. Core-file integrity against the official WordPress.org checksums, a local signature sweep of plugin, theme, and upload PHP, one-click repair of modified core files, rogue-file quarantine, and Spamhaus domain-blocklist monitoring. Run on demand or schedule daily or weekly.
- Login security. Failed-login rate limiting and IP lockout, two-factor authentication (an authenticator app or an emailed code — no SMS gateway), a secret custom login URL, and password length, complexity, and expiry rules.
- Site hardening. Disable the dashboard file editor, disable XML-RPC, hide the WordPress version, and an opt-in database-prefix rename that is fully backed up and reversible.
- Auditing & monitoring. A new Logs tab with a 30-day security event log — scan findings, firewall blocks, login lockouts, and site activity — filterable by type, plus a lightweight live-traffic view and idle-session timeout.
- Private by default. The only external services any feature can contact are WordPress.org (core checksums) and Spamhaus (DNS reputation and blocklist), each disabled unless you turn on both the master switch and that feature, and each disclosed in plain language.
- Also on the SEO side: the 404 log now keeps not-found URLs for 30 days, and you can select several at once and redirect them all to one page — or dismiss them — in a single action.
Version 3.4.0 — the cache purges itself
Released July 14, 2026.
- Automatic cache purge after a Studio Sync push. When a push commits on the live site, PBJ SEO now purges its LiteSpeed edge cache automatically — no more stale pages hanging around until the cache TTL runs out. It works with every Studio Sync version, and pairs with Studio Sync 3.4.0’s new
pbj_sync_after_commithook. - Automatic cache purge on every dashboard save. Saving any SEO tab — Options, Business, Robots & Sitemaps, Google Tools, 404s & Redirects — purges the edge cache immediately, so changed meta tags, schema, robots rules and redirects take effect right away.
Version 3.3.0 — Google Analytics and Search Console, built in
Connecting a WordPress site to Google’s free tools usually means installing yet another plugin (for the GA tag) and puzzling through a verification flow (for Search Console). 3.3.0 folds both into PBJ SEO — with instructions written for humans:
- New Google Tools tab. Plain-English, step-by-step walkthroughs for connecting your site to Google Analytics and Google Search Console, with direct links into each console. No jargon, no guessing which of Google’s five setup paths applies to you.
- GA4 without another plugin. Paste your Measurement ID (
G-XXXXXXXXXX) and PBJ SEO outputs the Google tag on every page. Logged-in editors and admins are never tracked, so your own work doesn’t skew the numbers. - Search Console verification built in. Paste Google’s HTML-tag token — or lazily paste the whole tag, both work — and the site emits the
google-site-verificationmeta. A Bing Webmaster Tools field is included too. - Sitemap controls on the Robots & Sitemaps tab: choose exactly what the XML sitemap includes — per-post-type toggles, category/tag archives, author archives, and an exclude-by-ID list. Defaults match WordPress core exactly, so nothing changes until you untick something.
- “Refresh sitemap now” button — rebuilds the sitemap routes, clears any cached copy, then fetches the live sitemap and reports how many URLs crawlers currently see.
- The Checklist tab now includes Google Analytics and Search Console rows, each with a Fix link straight to the new tab.
Version 3.2.0 — the Business tab: no more baked-in defaults
Until now, PBJ SEO shipped with our own business data as its defaults — our LocalBusiness name, address, and phone number, our service-page schema map, our service-area city list, even our old-URL redirect map. That was fine when the plugin only ran on pbj.tech; it’s wrong for everyone else. 3.2.0 removes every baked-in default and replaces them with a proper home for your data:
- New Business tab. Everything the plugin can say about your business is now editable at SEO → Business: name, schema.org type, phone, email, price range, image, description, full postal address, map coordinates, your service-area list (one city or county per line), and your service pages (one
slug | Name | Descriptionline each). Fill in a field and the matching output turns on — the front-page LocalBusiness node, Service nodes with your business as provider, areaServed lists, and geo meta tags. Leave a field empty and that output simply doesn’t exist. - Developers keep full control. A new
pbj_seo_business_infofilter runs over the saved Business-tab values, so a site-specific mu-plugin or theme can override or extend any of it in code. The existingpbj_seo_service_schema_mapandpbj_seo_redirect_mapfilters work exactly as before — they just start from empty maps now. - Upgrading? If your site relied on the old defaults (that’s essentially just pbj.tech), fill in the Business tab after updating. Legacy URL redirects are best managed on the 404s & Redirects tab, which has been the redirect home since 3.0.0 anyway.
Version 3.1.0
- Added a License tab where you enter your PBJ license key to unlock automatic updates. The plugin is fully functional without a key; a key covers up to 3 sites.
Version 3.0.1
- The admin dashboard moved from Settings → PBJ SEO to its own top-level SEO item in the sidebar. No settings or behavior changed.
Version 3.0.0 — the admin dashboard
Our biggest PBJ SEO release yet, and a deliberate reversal of the plugin’s original “one small file, no dashboard” design. PBJ SEO now has a full settings screen at Settings → PBJ SEO with four tabs:
- Checklist. A page-by-page SEO audit — every published page and post is scanned for a missing meta description, an over-long or too-short title, no featured image for social sharing, a noindex flag, or thin content — with an Edit button on each. Alongside it, a site-wide checklist covers permalinks, search-engine visibility, tagline, HTTPS, logo/icon, the front-page share image, and the XML sitemap, plus a functional check that PBJ SEO’s own output is actually running and a warning if a second SEO plugin is fighting it.
- Options. Every feature — force-HTTPS, security headers, LiteSpeed edge caching, geo meta, legacy redirects, custom redirects, 404 logging, attachment redirects, feed noindex, head cleanup, and performance tweaks — is now an on/off switch with a plain-English explanation. Everything defaults on, so upgrading changes nothing until you decide it should.
- Robots & Sitemaps. See your sitemap URL and status, preview the effective robots.txt, and add your own robots rules — saved and served automatically.
- 404s & Redirects. A lightweight logger records the URLs visitors hit that don’t exist, with hit counts and referrers, and one click turns any of them into a redirect. A full 301/302 redirect manager replaces the old code-only map.
Version 2.0.0
- Merged PBJ Site Essentials. Security headers, legacy and duplicate-slug 301 redirects, geo meta tags, Service/FAQPage/LocalBusiness schema, meta URL absolutizing, performance tweaks, and the
2026shortcode now live inside PBJ SEO; the standalone Site Essentials plugin is retired (and while it’s still active, the merged module stands down — no double output). Service/FAQ/LocalBusiness schema now ride in the main JSON-LD graph instead of separate script tags, with newpbj_seo_service_schema_mapandpbj_seo_redirect_mapfilters. - New: block editor “PBJ SEO” sidebar. Per-post SEO title, meta description, and noindex fields (previously only settable via WP-CLI), plus a live SEO best-practices checklist with click-to-fix actions, a site-wide checklist, and a pre-publish review panel.
- New output: attachment pages 301 to their parent post, feeds send
X-Robots-Tag: noindex, follow, robots.txt gains aSitemap:line, article published/modified-time Open Graph tags,og:imagewidth/height/alt andtwitter:image:alt, and shortlink/RSD head links are removed. - Changed: force-HTTPS is now on by default (was opt-in). Same safety rails — it only ever redirects when the home URL itself is HTTPS.
- Fixed: LiteSpeed edge-cache headers moved to
template_redirect— previously they fired before the main query, so search/404/preview pages could be cached as public.
Version 1.4.0
- LiteSpeed edge caching. Anonymous front-end pages now send
X-LiteSpeed-Cache-Control: public(6-hour TTL, adjustable via thepbj_seo_cache_ttlfilter) so LiteSpeed servers full-page-cache them with no separate cache plugin. Personalized requests — logged-in users, cart/checkout, comment cookies, search, 404s, previews, feeds — are marked no-cache, with apbj_seo_cacheablefilter for site-specific rules. Content changes send a full-site purge. Both headers are ignored on non-LiteSpeed servers, so there’s zero behavior change elsewhere.
Version 1.3.1
- The posts page (blog index) now honors its own meta description and featured image for the description and
og:image, instead of always falling back to the site tagline and default image.
Version 1.3.0
- Added an opt-in force-HTTPS redirect (
PBJ_SEO_FORCE_HTTPSconstant orpbj_seo_force_httpsfilter). 301s to the canonical HTTPS home host and collapses www in the same hop; no-op when the home URL isn’t HTTPS, under WP-CLI/cron, or behind a TLS-terminating proxy.
Version 1.2.0
- Added an Article JSON-LD node (headline, dates, author, publisher) on posts, with
pbj_seo_is_article,pbj_seo_article_type, andpbj_seo_article_authorfilters. - Added the
pbj_seo_schema_graphfilter so themes can append site-specific schema nodes, andpbj_seo_default_imagefor a site-wide fallback share image. - Added
<lastmod>to core XML sitemap entries and removed the WordPress version meta generator tag.
Version 1.1.0
- Added a per-post custom title override via the
_pbj_seo_titlepost meta (front page or any singular page).
Version 1.0.0
- Initial release. Replaced Rank Math with first-party titles, meta descriptions, canonicals, robots directives, Open Graph/Twitter Cards, Organization/WebSite/WebPage/BreadcrumbList/Product schema, and image alt-text fallbacks.

PBJ SEO is our lightweight, first-party SEO plugin — a single-file replacement for heavyweight suites like Yoast or Rank Math. It outputs document titles, meta descriptions, canonical URLs, robots directives, Open Graph and Twitter Card tags, JSON-LD structured data, and sitemap <lastmod> dates, all with zero third-party dependencies. Here’s what has shipped in every version.
4.7.1
One fix. “Hide the WordPress version” was stripping other plugins’ cache-busters. The check matched ver= as a substring, so on WordPress 7.1 any asset stamped ver=7.1… lost its version — and a browser went on serving the previous release’s CSS and JavaScript after an update, which looks exactly like an update that did not install. The parameter is now parsed out of the query and removed only when it equals the WordPress version exactly. Nothing else changed, and the setting still hides the version everywhere it did before.
4.7.0
A security release, and the biggest one since 4.0.0. It closes 19 findings from a full audit of the security modules — including three that mattered: the firewall never actually read JSON request bodies (so the entire REST API was pattern-matched against nothing), ?rest_route= walked past every path rule, and ANY signed-in account — a subscriber on a site with open registration — switched the whole firewall off for its own requests. Anonymous calls to the REST batch endpoint the wp2shell exploit chain uses (CVE-2026-63030 / CVE-2026-60137) are now refused by default. Request bodies are now inspected at both ends rather than skipped when large, without ever loading the whole upload into memory. Seven new hardening switches (including one that stops readme.html and license.txt handing out your exact WordPress version), email alerts, and a wider malware sweep that finally looks in mu-plugins, the wp-content drop-ins and the site root. Every new switch ships OFF so upgrading changes nothing about how your site behaves — except the three pure fixes above and the batch-endpoint block, which are on. The admin screens are also now plain WordPress admin styling rather than borrowing PBJ CRM’s theme. No schema change beyond a settings migration that carries your XML-RPC setting into its new three-way form.
Firewall — three real holes, closed.
- Fixed: JSON request bodies were never inspected. The body the pattern engine matched against was
wp_json_encode( $_POST ), and PHP does not populate$_POSTforContent-Type: application/json. So every REST request — which is to say the whole surface modern WordPress exploits actually use — was matched against an empty string. The rawphp://inputstream is now read when the content type says JSON, or when a body arrived that PHP chose not to parse. - Fixed:
?rest_route=bypassed every path rule. WordPress accepts every REST route twice:/wp-json/xand/?rest_route=/x. Path rules matched the first spelling and none of them matched the second, which made the plain spelling a bypass for the entire path engine including the known-probe list. The route is now folded into the path before matching, so one rule answers both. - Fixed: any signed-in user switched the firewall off.
request_is_authenticated()trustedis_user_logged_in()with no capability test, so on a site with open registration one free subscriber account was one free firewall exemption. New setting Who is trusted enough to skip the firewall: any signed-in user (the default, so upgrading changes nothing), contributors and above, or administrators only. Filterpbj_seo_waf_trusted_cap. Application-password traffic — PBJ Studio Sync, the MCP connector — stays trusted as before. - New: anonymous calls to
/wp-json/batch/v1are refused (settingwaf_block_anon_batch, on by default). This is the endpoint the wp2shell chain uses; core only ever calls it as a signed-in editor, so a logged-out caller has no legitimate business there. Both URL spellings are covered, and the same block is repeated atrest_pre_dispatchwhere the route is the router’s own answer rather than a guess. A user-agent rule catches thewp2shelltooling by name. - Fixed: a request body over 256KB was SKIPPED entirely, which is a rule an attacker satisfies with padding. Bodies are now inspected in TWO 256KB windows — the first bytes and the last bytes — so padding either end costs the attacker the payload, and the log records which end it matched in. The body is never held in memory whole: the stream seeks to its own end for the tail (or is walked in 64KB chunks keeping a rolling tail where the stream will not seek), so an 8MB upload costs about half a megabyte to inspect rather than 8MB. A body small enough to fit in two windows is simply inspected whole, so there is no seam in the middle of an ordinary request.
- Fixed: double-encoded payloads (
%252e%252e%252f) matched neither the raw nor the once-decoded rule. A third URI view decodes twice, capped at two passes. - Fixed:
waf_country_check()decided “is this the login screen” withstripos( $uri, 'wp-login.php' )— a substring test standing in for an endpoint test, so/?x=wp-login.phpcounted and a subdirectory install did not. Same for the body-exemption list, which compared its prefixes against the whole URI including the query string. Both now compare the decoded path. - New:
/readme.html,/license.txtand/wp-content/debug.logare answered as known probes (on by default with the rest of the probe list). Note that a firewall written in PHP can only answer a request PHP is given: whilereadme.htmlactually exists, Apache serves it off disk without starting WordPress, so the probe rule covers the file-absent case and non-Apache hosts — and the new Hide server files & directory listings switch below refuses it at the webserver, which is the half that works when the file is really there. - New: optional per-IP rate limit on logged-out
admin-ajaxcalls, 60 a minute (waf_nopriv_ajax_throttle, off by default — plenty of themes load real content that way). - The
pbj_seo_request_is_authenticatedfilter is now documented as privileged, and writes onewaf_filter_bypasslog line whenever a callback changes the verified answer.
Hardening — seven new switches, every one off by default.
- Stop user enumeration — removes the
/wp-json/wp/v2/usersroutes for logged-out callers, 404s?author=N(numeric only;?author_name=and pretty author archives are untouched), makes login errors generic, and drops the users provider from the XML sitemap. That last part is also the author-sitemap removal that has been on the roadmap since 4.0.0. - Refuse to run PHP inside uploads — writes
<uploads>/.htaccessdenying.php/.phtml/.pharwith Apache 2.4 syntax and the 2.2 fallback, on Apache and LiteSpeed only, with our own markers, removed again on toggle-off and on deactivation. Alongside it, always on and with no switch: an upload whose FILENAME disguises a PHP file (shell.php.jpg) is refused outright. - Hide server files & directory listings — a PBJ SEO-owned
.htaccessblock carryingOptions -Indexesplus aFilesMatchthat refusesreadme.htmlandlicense.txt. Those two name your exact WordPress version to anybody who asks, which is the first thing a scanner checks before deciding a site is worth an exploit attempt, and because they are real files only the webserver can refuse them. Off by default because a few hosts forbidOptionsin.htaccess, where it is a 500 rather than an ignored line; your file is copied first and switching it back off removes the block. - XML-RPC now has a middle setting — Restricted keeps the endpoint working and removes
system.multicall(the brute-force amplifier) and the two pingback methods (reflected DDoS, internal port scanning). Your existing on/off setting is migrated into the new three-way form on upgrade, preserving exactly what it did. - Block plugin and theme installation — denies the install/upload/update capabilities. Flagged clearly because it also blocks updates, and
DISALLOW_FILE_MODSin wp-config.php is the stronger form. - Disable application passwords — with an explicit warning that PBJ Studio Sync authenticates with one.
- Throttle comments and registrations — 5 per 10 minutes per address plus a honeypot field. Nothing in the comment text itself is examined; a firewall that pattern-matches prose deletes the comments of anybody who writes about code.
Monitoring.
- New: email alerts. One address, plain text, at most one message per event type per hour: an automatic ban, a modified core file, a malware finding, a new administrator, a plugin or theme installed. Empty address means alerting is off — which is the default. Hangs off the log writer, so anything recorded can be alerted on and the two can never disagree.
- New: always-on install and theme-change auditing.
upgrader_process_completeandswitch_themeare now recorded regardless of the activity-log setting, with who did it, what changed, the version and the source URL — as is any new administrator. Everything an attacker does after they are in goes through the installer, and the activity log ships off, so on a default install the single most important event on the site was recorded nowhere at all. - New: known-vulnerable core version warning. A banner on the Security tab and a Checklist row when this install sits inside the wp2shell range (fixed in 7.0.2, 6.9.5 and 6.8.6; releases before 6.8 are unaffected). Advisory only — it never blocks anything — and it names wp2shell explicitly. Reads WordPress’s own update data; no new external call.
- Security headers are now also sent on wp-login.php and wp-admin. They were registered on
send_headers, which those two screens never fire — so the login form and the entire dashboard, the only pages where a clickjacking frame costs somebody their session, were the only pages shipping none of them.X-Frame-Options: DENYon the login screen specifically,SAMEORIGINelsewhere, plus a newPermissions-Policyswitching off geolocation, camera and microphone.frame-ancestors 'self'is unchanged.
Malware scan.
- The signature sweep now also covers mu-plugins (recursive), the wp-content drop-ins (
object-cache.php,advanced-cache.php,db.php,sunrise.php) and PHP files in the site root. Those are the three places a backdoor actually lives, and none of them was being looked at. Core’s own root files are excluded by comparing against the official checksum manifest the integrity scan already fetches — so no extra network call, andwp-config.php, which is not in that manifest, is flagged only if it genuinely matches a signature.
Admin screens.
- The PBJ CRM theming bridge has been removed. 4.5.0 wrote this plugin’s admin stylesheet entirely in
var(--pbjcrm-*, <literal>)and published PBJ CRM’s theme variables onto the page. That made a security plugin’s appearance depend on another plugin being installed — and most sites run PBJ SEO on its own, so most sites got a screen styled by fallbacks rather than designed. It also meant any CRM release that renamed a variable would silently take a colour off a screen nobody would think to test. The screens are now plain WordPress admin —.postboxcards,.nav-tab-wrappertabs,.widefat.stripedtables, core buttons with no colour overrides, and WordPress’s own status colours — so they look the same with the CRM installed and without it, and they follow the user’s own admin colour scheme. - Unchanged on purpose: the firewall block page, the 2FA and login-screen chrome, and every email body. None of those is a wp-admin screen and none of them ever carried this styling.
Migration. DB_VERSION moves to 4.7.0 to run one settings migration: the old harden_xmlrpc boolean is carried into harden_xmlrpc_mode (on stays fully off, off stays off). No table or column changes.
4.6.1
Finishes what 4.6.0 started. If the scan had already recorded findings in a place that 4.6.0 excludes, those entries stayed on your screen with no button to clear them — because they were already excluded, so there was nothing left to allowlist. Updating now clears them once, by itself. And the Scanner exclusions panel has gained a Clear button that tells you exactly how many old entries it will remove, for any that turn up later. Nothing about your settings, your scans or your logs changes otherwise.
4.6.0
An honest answer when the scan is wrong about your own plugin. The malware scan matches patterns, not intentions. A perfectly legitimate plugin that compresses its templates, encodes a licence key, or ships a block of encoded data looks, to a pattern, exactly like the thing being hunted for. That bluntness is what makes the scan worth running — and it means honest plugins get flagged sometimes. Until now the only answers were to quarantine a file you knew was fine, or switch the scan off.
Every finding now carries an Allowlist button beside Quarantine, in the scan results and in the Logs tab. If the finding sits inside a plugin, it offers the whole plugin folder; if it does not, it offers just that one file. Allowlisting also clears the entries that finding has already produced, so the screen matches the decision you just made.
Everything you allowlist is listed on the Security tab under Scanner exclusions — what it is, when it was added, why, and a Remove button on every row. An exception is always something you can see and take back. It is never a hidden rule, and never a switch that quietly turns off part of the scan, because a scanner that has been silently told to stop reporting looks exactly like a clean site.
Two smaller things come with it. Excluded plugins are dropped when the scan builds its file list, so they are never opened at all and the sweep gets faster as well as quieter. And a plugin you allowlist is covered wherever a copy of it lives — a staging folder, a backup, a migration dump — so you make the decision once rather than once per folder.
4.5.1
A tidy-up on top of 4.5.0. With the new Country column and the extra buttons beside each log entry, the security log table could end up slightly wider than the card holding it, so the last button appeared to hang outside the border. The buttons now stack neatly instead, and every panel on these screens has a hard limit so nothing can spill out of it again. Nothing else changed — if you are already on 4.5.0, everything works; this just looks right.
4.5.0
Security fix — please update. Sending one extra header with a request was enough to skip the firewall entirely. The exemption that lets your own logged-in traffic and your API integrations through was checking whether an Authorization header was present, and never whether the credential in it was real. So anyone who knew could send Authorization: Bearer anything and walk past the IP block list, country blocking, reputation checks, every exploit rule and every custom rule you had written. Your WordPress login was never affected — nobody could get into anything — but the firewall could be told to look the other way. The credential is now actually verified before it earns an exemption, and a header that is not a real WordPress credential earns nothing.
Three more holes closed while we were in there. The “email me a way back in” escape hatch was matching a piece of text anywhere in the address rather than the actual page, so a crafted URL could use it to skip pattern checks. Country blocking could be walked past by any request claiming to be Googlebot, because a user agent is simply typed by whoever is sending the request — crawlers are now verified properly, by looking up the visitor address and checking it really does belong to Google, Bing or whoever it claims. And a malformed entry in the trusted-proxy list could quietly mark every address on the internet as trusted; that is now rejected.
Also: a temporary Spamhaus outage no longer buys a bad address a free pass. A failed lookup and a genuine “this address is fine” were being remembered identically for twelve hours — including the “you have used up your free quota” answer that a busy site is most likely to get. A failed lookup is now forgotten after five minutes.
New: three strikes, then a ban. Two separate counts are kept for every address — firewall blocks, and sign-in lockouts. Whichever reaches three first (within a day, both adjustable) adds that address to your block list. They are deliberately never added together: tripping two different things twice each is far more likely to be a real person having a bad day than an attack. Every ban made this way is listed on the Security tab with what it was banned for and a Remove button, because a list you cannot tell apart from your own typing is a list nobody dares touch. Addresses on your always-allow list can never be banned this way.
New: the background scanning stops filling your log. Every public site gets a constant trickle of requests for software you do not run — another content system, a database console, a leftover backup file, the usual dropped scripts. Those are now turned away immediately, before the heavier rules run. And when the same thing is tried hundreds of times, you get one line in the log with a count on it rather than hundreds of identical lines burying everything worth reading.
New: a Country column in the security log, and one-click blocking. Where your CDN tells us the country, it is shown beside each entry at no cost. If you also run PBJ Geo Viewer 1.7.0, any missing countries can be looked up on demand, and every firewall or sign-in row gains Block this IP and Block RU (or whichever country it is) buttons that hand the block straight to Geo Viewer. Both ask you to confirm first, because blocking a whole country from one log row is a big thing to do by accident.
New: a six-step setup wizard, the first time you activate. The same shape as the PBJ Invoicing one: short questions, every step skippable, nothing blocked if you close the tab, and you can run it again any time from the Checklist tab. It covers your business details, what the plugin publishes, your Google and Bing codes, sign-in security and the firewall. One step is worth singling out — it offers to add the address you are sitting at to the never-lock-me-out list, already ticked. That is the cheapest insurance there is against locking yourself out of your own site.
Changed: these screens now follow your PBJ CRM theme, and work on a phone. With PBJ CRM installed, the SEO and Security screens pick up whichever look you chose over there — including the dark ones. Without it, they look exactly as they always did. And the whole admin area now reflows properly on a phone: the security log becomes one readable card per entry instead of a table you have to drag sideways.
4.4.1
Maintenance only: the bundled licence-update client was refreshed so it reports its own version and warns when an older copy has loaded first on a site running several PBJ plugins. No change to the plugin itself.
4.4.0
If you have ever switched on the idle session timeout, this release is the one you want. With that setting turned on, signing in could log you straight back out again — every time, with no way through. The plugin was comparing the clock against a timestamp left over from your previous visit, so your brand-new session looked hours old the instant it began. It now starts the clock when you sign in, and treats a missing timestamp as fresh rather than ancient. The timeout still works normally once you are actually idle.
New: an always-allow list, so you can never lock yourself out. Addresses on it bypass every gate — the firewall, login rate limiting, the idle timeout, country blocking, and the secret login address. You can set it three ways: on the Security tab, in code with a filter, or — the one that matters — with a PBJ_SEO_ALWAYS_ALLOW line in wp-config.php, because that file can still be edited over FTP while you are locked out of the site.
New: a lockout recovery email. If the firewall or the rate limiter has shut you out, the login screen now offers to email you a way back in. The email only ever goes to the site administrator’s address already on file — never to whatever address is typed into the form — so it cannot be used against you. The link works once, only from the address that asked for it, expires in ten minutes, and lets that one address back through for about an hour. It never switches your protection off.
Blocked pages now explain themselves in plain English and offer that recovery form, instead of a bare “403 Forbidden”. The login screen also tells you when you have been signed out for being idle, rather than just appearing.
4.3.0 — August 2, 2026
A Caching tab, and pages that are never saved. Everything about saved copies of your pages now lives in one place — the on/off switch, the button that clears saved copies, and a new list of pages that must never be saved. The switch and the button moved here from the Options tab, which points you across.
The new part is the never-save list. Some pages are different for every single person who opens them — somebody’s customer list, their support tickets, their invoice. If the server saved one of those and handed the copy to the next visitor, that visitor would be looking at someone else’s information. PBJ SEO now recognises those pages in PBJ CRM, PBJ Helpdesk, PBJ Invoicing, PBJ Access Control and PBJ Form Builder and keeps them out of the saving automatically. Rows only appear for plugins you actually have, and they start switched on — you do not have to find the tab to be protected.
You can also tick any page of your own, or type web addresses one per line; anything starting with what you type is covered. Pasting a full web address works, it gets trimmed for you. Saving the tab clears the copies already saved, so a page you have just protected stops being served from an old one straight away.
For developers: other plugins can add their own row with the new pbj_seo_cache_exclusions filter.
4.2.0 – 4.2.2 — August 2, 2026
Three fixes to how saved copies are handled, which belong together. 4.2.0: turning caching off used to leave an instruction behind in .htaccess, so the server kept saving copies to its own rules while the plugin had stopped marking personal pages as never-save — off now removes the block properly. It also added the manual “Purge cached pages now” button, and made the cache clear itself after any plugin or theme install, update, delete or switch.
4.2.1: on a LiteSpeed server with no LiteSpeed Cache plugin, the server had no way of knowing which visitors were signed in, so a page saved for a passer-by could be handed to signed-in staff. The instruction block now tells the server to skip its saved copies for anyone carrying a sign-in, comment, page-password or shopping-cart cookie.
4.2.2: that instruction was being written to the bottom of .htaccess, underneath WordPress’s own block — and WordPress’s block ends with a rule that stops the server reading any further, so it was never once looked at. It is now written above WordPress’s block, where it works. Everything else in the file is left exactly as it was.
4.1.0 — July 28, 2026
The master SEO checklist. The Checklist tab is now the complete SEO to-do list for a site, not just the page audit.
- Five sections — SEO Basics, Keyword Research, Technical SEO, On-Site & Content, Off-Site SEO — with live progress counts per section and overall.
- Auto-verified items turn green the moment the site satisfies them: HTTPS, one canonical version, sitemap, robots.txt, schema, canonicals, permalinks, analytics, Search Console / Bing verification, title/description health, a clean 404 log, and more.
- Every fixable item carries a Fix button to the exact tab or screen — and items solved by a plugin setting get a one-click Enable now button.
- External jobs (Search Console setup, sitemap submission, keyword research, backlinks, Google Business Profile…) come with numbered instructions, a link to the right tool, and a Mark done button whose state is remembered. Undo any time.
- The per-page audit remains below as “Pages that need attention” and feeds the title/description items.
Version 4.0.1 — hardening the security modules
Released July 22, 2026. A robustness and security pass over the 4.0.0 modules — everything still ships off by default.
- Fail-proof database-prefix rename. A full database backup must be downloaded before the rename unlocks; it verifies wp-config.php can be rewritten before touching the database, and fully self-restores (tables and role keys) if anything fails — it can never lock you out.
- Firewall false positives fixed. Code and path rules match the URL only, so an ordinary blog comment or contact message that merely mentions code is no longer blocked.
- Login hardening. Two-factor now issues single-use recovery codes (shown once at setup) so a lost authenticator or broken mail server can’t lock you out, plus a wrong-code limit and replay protection, and a fix for a fatal on front-end logins.
- Accurate visitor IPs. The Cloudflare visitor-IP header is trusted only when the request really arrives through Cloudflare, closing a spoofing gap that could bypass the allowlist or login lockout.
- The admin sidebar is now labeled SEO & Security with a lock icon.
Version 4.0.0 — PBJ SEO becomes PBJ SEO & Security
Released July 22, 2026. Our biggest release yet: the plugin is renamed PBJ SEO & Security and gains a whole security half alongside the SEO tools you already have. Every security feature ships OFF — upgrading changes nothing until you turn something on — and nothing contacts an external service unless you enable both a master switch and the feature.
- Web Application Firewall. Inspects anonymous requests before WordPress routes them and blocks SQL injection, XSS, directory traversal, file inclusion, and PHP-injection probes. Manual IP block/allow lists, header-based country blocking, a log-only dry-run mode, and custom virtual-patch rules. Logged-in users and your own API traffic are never inspected.
- Malware scanning & remediation. Core-file integrity against the official WordPress.org checksums, a local signature sweep of plugin, theme, and upload PHP, one-click repair of modified core files, rogue-file quarantine, and Spamhaus domain-blocklist monitoring. Run on demand or schedule daily or weekly.
- Login security. Failed-login rate limiting and IP lockout, two-factor authentication (an authenticator app or an emailed code — no SMS gateway), a secret custom login URL, and password length, complexity, and expiry rules.
- Site hardening. Disable the dashboard file editor, disable XML-RPC, hide the WordPress version, and an opt-in database-prefix rename that is fully backed up and reversible.
- Auditing & monitoring. A new Logs tab with a 30-day security event log — scan findings, firewall blocks, login lockouts, and site activity — filterable by type, plus a lightweight live-traffic view and idle-session timeout.
- Private by default. The only external services any feature can contact are WordPress.org (core checksums) and Spamhaus (DNS reputation and blocklist), each disabled unless you turn on both the master switch and that feature, and each disclosed in plain language.
- Also on the SEO side: the 404 log now keeps not-found URLs for 30 days, and you can select several at once and redirect them all to one page — or dismiss them — in a single action.
Version 3.4.0 — the cache purges itself
Released July 14, 2026.
- Automatic cache purge after a Studio Sync push. When a push commits on the live site, PBJ SEO now purges its LiteSpeed edge cache automatically — no more stale pages hanging around until the cache TTL runs out. It works with every Studio Sync version, and pairs with Studio Sync 3.4.0’s new
pbj_sync_after_commithook. - Automatic cache purge on every dashboard save. Saving any SEO tab — Options, Business, Robots & Sitemaps, Google Tools, 404s & Redirects — purges the edge cache immediately, so changed meta tags, schema, robots rules and redirects take effect right away.
Version 3.3.0 — Google Analytics and Search Console, built in
Connecting a WordPress site to Google’s free tools usually means installing yet another plugin (for the GA tag) and puzzling through a verification flow (for Search Console). 3.3.0 folds both into PBJ SEO — with instructions written for humans:
- New Google Tools tab. Plain-English, step-by-step walkthroughs for connecting your site to Google Analytics and Google Search Console, with direct links into each console. No jargon, no guessing which of Google’s five setup paths applies to you.
- GA4 without another plugin. Paste your Measurement ID (
G-XXXXXXXXXX) and PBJ SEO outputs the Google tag on every page. Logged-in editors and admins are never tracked, so your own work doesn’t skew the numbers. - Search Console verification built in. Paste Google’s HTML-tag token — or lazily paste the whole tag, both work — and the site emits the
google-site-verificationmeta. A Bing Webmaster Tools field is included too. - Sitemap controls on the Robots & Sitemaps tab: choose exactly what the XML sitemap includes — per-post-type toggles, category/tag archives, author archives, and an exclude-by-ID list. Defaults match WordPress core exactly, so nothing changes until you untick something.
- “Refresh sitemap now” button — rebuilds the sitemap routes, clears any cached copy, then fetches the live sitemap and reports how many URLs crawlers currently see.
- The Checklist tab now includes Google Analytics and Search Console rows, each with a Fix link straight to the new tab.
Version 3.2.0 — the Business tab: no more baked-in defaults
Until now, PBJ SEO shipped with our own business data as its defaults — our LocalBusiness name, address, and phone number, our service-page schema map, our service-area city list, even our old-URL redirect map. That was fine when the plugin only ran on pbj.tech; it’s wrong for everyone else. 3.2.0 removes every baked-in default and replaces them with a proper home for your data:
- New Business tab. Everything the plugin can say about your business is now editable at SEO → Business: name, schema.org type, phone, email, price range, image, description, full postal address, map coordinates, your service-area list (one city or county per line), and your service pages (one
slug | Name | Descriptionline each). Fill in a field and the matching output turns on — the front-page LocalBusiness node, Service nodes with your business as provider, areaServed lists, and geo meta tags. Leave a field empty and that output simply doesn’t exist. - Developers keep full control. A new
pbj_seo_business_infofilter runs over the saved Business-tab values, so a site-specific mu-plugin or theme can override or extend any of it in code. The existingpbj_seo_service_schema_mapandpbj_seo_redirect_mapfilters work exactly as before — they just start from empty maps now. - Upgrading? If your site relied on the old defaults (that’s essentially just pbj.tech), fill in the Business tab after updating. Legacy URL redirects are best managed on the 404s & Redirects tab, which has been the redirect home since 3.0.0 anyway.
Version 3.1.0
- Added a License tab where you enter your PBJ license key to unlock automatic updates. The plugin is fully functional without a key; a key covers up to 3 sites.
Version 3.0.1
- The admin dashboard moved from Settings → PBJ SEO to its own top-level SEO item in the sidebar. No settings or behavior changed.
Version 3.0.0 — the admin dashboard
Our biggest PBJ SEO release yet, and a deliberate reversal of the plugin’s original “one small file, no dashboard” design. PBJ SEO now has a full settings screen at Settings → PBJ SEO with four tabs:
- Checklist. A page-by-page SEO audit — every published page and post is scanned for a missing meta description, an over-long or too-short title, no featured image for social sharing, a noindex flag, or thin content — with an Edit button on each. Alongside it, a site-wide checklist covers permalinks, search-engine visibility, tagline, HTTPS, logo/icon, the front-page share image, and the XML sitemap, plus a functional check that PBJ SEO’s own output is actually running and a warning if a second SEO plugin is fighting it.
- Options. Every feature — force-HTTPS, security headers, LiteSpeed edge caching, geo meta, legacy redirects, custom redirects, 404 logging, attachment redirects, feed noindex, head cleanup, and performance tweaks — is now an on/off switch with a plain-English explanation. Everything defaults on, so upgrading changes nothing until you decide it should.
- Robots & Sitemaps. See your sitemap URL and status, preview the effective robots.txt, and add your own robots rules — saved and served automatically.
- 404s & Redirects. A lightweight logger records the URLs visitors hit that don’t exist, with hit counts and referrers, and one click turns any of them into a redirect. A full 301/302 redirect manager replaces the old code-only map.
Version 2.0.0
- Merged PBJ Site Essentials. Security headers, legacy and duplicate-slug 301 redirects, geo meta tags, Service/FAQPage/LocalBusiness schema, meta URL absolutizing, performance tweaks, and the
2026shortcode now live inside PBJ SEO; the standalone Site Essentials plugin is retired (and while it’s still active, the merged module stands down — no double output). Service/FAQ/LocalBusiness schema now ride in the main JSON-LD graph instead of separate script tags, with newpbj_seo_service_schema_mapandpbj_seo_redirect_mapfilters. - New: block editor “PBJ SEO” sidebar. Per-post SEO title, meta description, and noindex fields (previously only settable via WP-CLI), plus a live SEO best-practices checklist with click-to-fix actions, a site-wide checklist, and a pre-publish review panel.
- New output: attachment pages 301 to their parent post, feeds send
X-Robots-Tag: noindex, follow, robots.txt gains aSitemap:line, article published/modified-time Open Graph tags,og:imagewidth/height/alt andtwitter:image:alt, and shortlink/RSD head links are removed. - Changed: force-HTTPS is now on by default (was opt-in). Same safety rails — it only ever redirects when the home URL itself is HTTPS.
- Fixed: LiteSpeed edge-cache headers moved to
template_redirect— previously they fired before the main query, so search/404/preview pages could be cached as public.
Version 1.4.0
- LiteSpeed edge caching. Anonymous front-end pages now send
X-LiteSpeed-Cache-Control: public(6-hour TTL, adjustable via thepbj_seo_cache_ttlfilter) so LiteSpeed servers full-page-cache them with no separate cache plugin. Personalized requests — logged-in users, cart/checkout, comment cookies, search, 404s, previews, feeds — are marked no-cache, with apbj_seo_cacheablefilter for site-specific rules. Content changes send a full-site purge. Both headers are ignored on non-LiteSpeed servers, so there’s zero behavior change elsewhere.
Version 1.3.1
- The posts page (blog index) now honors its own meta description and featured image for the description and
og:image, instead of always falling back to the site tagline and default image.
Version 1.3.0
- Added an opt-in force-HTTPS redirect (
PBJ_SEO_FORCE_HTTPSconstant orpbj_seo_force_httpsfilter). 301s to the canonical HTTPS home host and collapses www in the same hop; no-op when the home URL isn’t HTTPS, under WP-CLI/cron, or behind a TLS-terminating proxy.
Version 1.2.0
- Added an Article JSON-LD node (headline, dates, author, publisher) on posts, with
pbj_seo_is_article,pbj_seo_article_type, andpbj_seo_article_authorfilters. - Added the
pbj_seo_schema_graphfilter so themes can append site-specific schema nodes, andpbj_seo_default_imagefor a site-wide fallback share image. - Added
<lastmod>to core XML sitemap entries and removed the WordPress version meta generator tag.
Version 1.1.0
- Added a per-post custom title override via the
_pbj_seo_titlepost meta (front page or any singular page).
Version 1.0.0
- Initial release. Replaced Rank Math with first-party titles, meta descriptions, canonicals, robots directives, Open Graph/Twitter Cards, Organization/WebSite/WebPage/BreadcrumbList/Product schema, and image alt-text fallbacks.