Title: Bulwarden
Author: jgloeckl.de
Published: <strong>August 23, 2026</strong>
Last modified: September 17, 2026

---

Search plugins

![](https://ps.w.org/bulwarden/assets/banner-772x250.png?rev=3661811)

![](https://ps.w.org/bulwarden/assets/icon-256x256.png?rev=3661811)

# Bulwarden

 By [jgloeckl.de](https://profiles.wordpress.org/bulwarden/)

[Download](https://downloads.wordpress.org/plugin/bulwarden.1.29.1.zip)

 * [Details](https://en-ca.wordpress.org/plugins/bulwarden/#description)
 * [Reviews](https://en-ca.wordpress.org/plugins/bulwarden/#reviews)
 *  [Installation](https://en-ca.wordpress.org/plugins/bulwarden/#installation)
 * [Development](https://en-ca.wordpress.org/plugins/bulwarden/#developers)

 [Support](https://wordpress.org/support/plugin/bulwarden/)

## Description

Bulwarden is the **site agent**, installed on each managed WordPress site. It
 exposes
a signed REST API (`jgm/v1`) that a **Bulwarden hub** calls to read health and security
data, run checks and trigger updates. The hub is a separate application — it is 
not part of this plugin — and **this plugin talks to no hub at all until you pair
it with one. On its own it sends nothing anywhere.

It is built for the person who is responsible for a site they did not build:
 an
agency looking after forty of them, or an owner who wants one place that answers“
is this thing all right?” without logging into it.

#### What it reports

 * **Site health** — WordPress, PHP and plugin versions, pending updates, disk
    
   usage, and WordPress’ own Site Health tests, collected in one snapshot.
 * **A full plugin and theme inventory**, with each item’s version, whether it
    
   is active, whether an update is waiting, and whether it came from WordPress.org
   at all — which is what lets a hub match it against a CVE feed.
 * **Must-use plugins**, which WordPress runs on every single request with
    nothing
   activating them and no way to switch one off from the admin. Nothing else in 
   wp-admin lists them properly, and dropping a file there is the tidiest way to
   install a backdoor.
 * **Configuration-level security checks** with a posture score — file editing,
   
   directory listing, XML-RPC, debug output, and the rest of the settings that quietly
   decide how exposed a site is.
 * **Unfinished updates.** An update that already replaced a site’s files can
    still
   fail to finish, and neither state clears itself: a pending database schema upgrade
   that every administrator is sent to and the public site hides completely, or 
   a leftover `.maintenance` lock from an updater that died mid-copy. Both are reported,
   and both can be finished from the hub by running exactly what WordPress itself
   would have run.
 * **Email delivery.** WordPress keeps no record of the mail a site sends: when
   
   a contact form stops delivering or an order confirmation never arrives, the error
   is raised and thrown away. This counts what was attempted, what failed and why,
   and reports which transport is doing it — PHP’s `mail()`, an SMTP server, or 
   the plugin that configured one. No part of any message is stored: not the recipient,
   not the subject, not the body.

#### What it finds

 * **A malware scan in three layers.** Every core and WordPress.org file is
    verified
   against its _published_ checksums, so a modified file is found whether or not
   anybody has written a signature for what was put in it; then every file is read
   for the code patterns webshells and injected backdoors actually use. Findings
   carry the file, the line and a short excerpt.
 * **Backdoors that arrive dressed as an ordinary plugin** — a forged
    administrator
   login, a privileged action gated on a key baked into the file, an account created
   from hard-coded credentials, users hidden from the users list. A plugin absent
   from WordPress.org has no published files to check it against, so without this
   a hostile one surfaced only as _unverifiable_.
 * **A plugin hiding itself.** Malware that conceals a plugin does it by
    answering
   the question before WordPress can — and the hidden plugin is then also the one
   plugin whose files are never checked. The scan counts what is on disk itself 
   and compares.
 * **Server-configuration files.** `.htaccess`, `php.ini` and `.user.ini` are
    the
   one part of a WordPress install that changes what the _server_ does rather than
   what WordPress does, and nothing in wp-admin shows them. A file set to run before
   every request, a rule that makes uploaded images execute as PHP, a redirect that
   only fires for search engines: all reported, with the line. Nothing is ever changed
   for you — a good share of those directives are written deliberately by a host
   or a security plugin, and the point is that you get to decide which.
 * **Leftover files.** An interrupted backup, a finished restore and a migration
   
   plugin all leave things behind and none of them clean up: a wp-config.php.bak
   the web server hands out as plain text, a `database.sql` in the web root, an 
   installer that overwrites the site with no login. Found, measured, and removable
   from the hub — and the _site_ decides what may be deleted, so a stale list can
   only ever be refused.
 * **Signatures that keep up.** The scan’s pattern list can be updated by your
    
   provider without updating this plugin, so a new detection reaches the site in
   hours rather than waiting for a release to travel a whole fleet. A site that 
   has never been paired scans with the list this plugin ships.
 * **A scan that starts itself.** When a plugin WordPress.org does not publish
    
   appears in the plugins or must-use folder, the site starts a scan within seconds
   and tells your provider what set it off, instead of waiting for the next scheduled
   one. Fake plugins are how most WordPress break-ins arrive, and a scan is a cheap
   thing to be wrong about: nothing is blocked, quarantined or deleted — the site
   looks, and your provider decides.

#### What it does

 * **Remote core, plugin and theme updates**, wrapped in maintenance mode so
    visitors
   never see a half-updated site — and taken off the host’s PHP time limit, so a
   core update on shared hosting finishes instead of being killed part-way.
 * **Zero-knowledge encrypted backups.** The archive is built _and encrypted on
   
   this server
   , under a passphrase that is never transmitted anywhere;
    only the
   ciphertext is uploaded. Nobody — including whoever runs the hub — can read a 
   backup without that passphrase.
 * **Restores that fail safely.** The same thing in reverse, and it can put back
   
   the database alone if that is all that broke. Every table is rebuilt beside the
   live one and swapped in only once it is complete, so a restore stopped by a timeout
   or a server limit leaves the database exactly as it was rather than half-replaced—
   and picks itself back up where it stopped. What could not be done is counted 
   and reported rather than discarded, so a restore that ran into trouble says so
   instead of reporting success.
 * **Page tracing.** Your provider can ask this site to render one of its own
    public
   pages and report which file produced each line of the HTML — core, a plugin, 
   the theme, a must-use plugin. It answers the question nothing in WordPress answers:
   _which_ plugin adds the Google Fonts tag, or the tracking script, or the markup
   nobody can find a setting for. The page is never altered to do it.
 * **WP-Cron on a schedule that actually runs.** A site with no visitors runs no
   
   cron, which is how scheduled posts, order emails and licence checks quietly stop.
 * **A support card on the dashboard** naming whoever maintains the site, how to
   
   reach them, and a _Request help_ button that files a ticket with them directly—
   attached to the right site, without the owner having to explain which one.
 * **An activity log on the site itself**, append-only, recording everything that
   
   changed this site or sent part of it somewhere — and which side asked. Updates
   installed by your provider and anything that failed with them, backups, restores,
   scans, repairs, deleted leftovers, a file read off the server, a traced page,
   and every change to the credentials, the passphrase or the provider details. 
   WordPress records what changed and never who asked, and the answer belongs on
   your own server rather than only in somebody else’s console.

#### Pairing

On activation the plugin provisions an API key + secret. Copy the **connect
 string

from the Bulwarden entry in the WordPress admin menu and paste
 it into the hub’s“
Connect a site” form. Your provider’s support details are then pushed to the site
automatically by the hub and appear on the dashboard.

#### Security

Hub  client requests are authenticated with the per-site API key and an
 HMAC-SHA256
signature over the method, route, timestamp, a single-use nonce and the body. The
nonce is what makes a signature good exactly once: it is recorded when the request
arrives and refused if it comes back, so a captured request cannot be replayed. 
Timestamps outside a 5-minute window are rejected outright, which is also how long
a nonce has to be remembered for. The secret never travels on the wire — it lives
only in this plugin and in the hub webapp.

### Privacy

Bulwarden is a client agent: on its own it sends data nowhere. Pairing this
 site
with a hub (yours, or your maintenance provider’s) is an explicit, opt-in action—
you choose when to copy the connect string from the Bulwarden screen in the WordPress
admin menu and hand it to that hub. Nothing is transmitted before that.

Once paired, data only moves in these cases:

 * **Health, inventory, and security checks.** The paired hub reads these from
    
   this site’s signed REST API when it polls or when a check is requested — WordPress/
   plugin/theme versions and update status, a full plugin & theme inventory, disk
   usage, and the configuration-level security posture score.
 * **Backups**, only when triggered (the Settings screen’s “Back up now”
    button,
   or a request from the paired hub): the full site is archived and encrypted on
   this server with a passphrase that is never transmitted anywhere, then the ciphertext
   is uploaded to the hub. Only someone who has the passphrase can decrypt a backup—
   the hub operator cannot read its contents.
 * **Malware scan results**, only when a scan is triggered (admin- or
    hub-initiated):
   findings (type, severity, file path, line, and a short excerpt) are sent to the
   hub that requested the scan.
 * **Finishing an interrupted update**, when the paired hub asks. Nothing is
    sent
   in that exchange; the site runs WordPress’ own database upgrade, or deletes an
   expired `.maintenance` lock file, and reports the outcome.
 * **Server-configuration files**, when the paired hub asks: the contents of
    this
   site’s `.htaccess`, `php.ini` and `.user.ini` files, so the hub can show what
   is in them and when they last changed. These are configuration, not content —
   no post, page, media file or user data is read.
 * **Email counters**, as part of the health snapshot: how many messages this
    site
   attempted and how many failed over the last seven days, the error text the mail
   server returned, and which transport is sending. **No part of any message is 
   recorded — not the recipient, not the subject, not the body.
 * **A traced page**, only when the hub asks for one: the HTML of a single
    public
   page of this site, exactly as an anonymous visitor would receive it, plus the
   file paths and line numbers that produced it. No file contents are read and no
   logged-in view is ever rendered.

Independently of any hub pairing, this plugin verifies WordPress core and
 wordpress.
org-hosted plugin files by comparing local file hashes against the public checksum
APIs at `api.wordpress.org` and `downloads.wordpress.org`. Those requests carry 
only what identifies the release to look up — the WordPress version and locale, 
and each plugin’s slug and version. No file contents, hashes, URLs or user data 
are sent: the reference checksums come back and the comparison happens here, on 
this server.

Bulwarden does not call home on activation, does not collect analytics, and
 stores
data on no server this site hasn’t been explicitly connected to. The hub itself 
is a separate service outside this plugin’s control; consult your provider for how
it handles the data described above.

### External services

This plugin connects to the services below. Nothing here runs on activation.

#### Your Bulwarden hub

Bulwarden is the site-side agent for a **hub** — the management application an
 
agency or site owner runs to watch and maintain a fleet of WordPress sites. The 
hub is a separate product and is not part of this plugin.

The default hosted hub is operated by Bulwarden at https://bulwarden.net/ —
 terms
of use: https://bulwarden.net/terms/ , privacy policy: https://bulwarden.net/privacy/.
A hub can also be self-hosted, or run by the maintenance provider you buy from, 
in which case this plugin talks to that provider’s server instead and their terms
and privacy policy apply.

**No connection to any hub exists until you make one.** You pair a site by
 copying
the connect string from the Bulwarden screen in the WordPress admin menu and pasting
it into the hub; before that the plugin contacts no hub at all, and it never discovers
or chooses one on its own. Once paired, data moves only in these cases:

 * **When the hub asks for status.** It reads this site’s signed REST API and
    receives
   WordPress/plugin/theme versions and update status, the full plugin and theme 
   inventory, disk usage, the results of WordPress’ own Site Health tests, and the
   configuration-level security posture score.
 * **When a backup runs** (you press “Back up now”, or the hub requests one).
    The
   site is archived and encrypted here, with a passphrase that is never transmitted,
   and only the ciphertext is uploaded. The hub operator cannot read a backup’s 
   contents without that passphrase.
 * **When a malware scan runs** (started by you or by the hub). The findings —
    
   type, severity, file path, line number and a short excerpt — are sent to the 
   hub that asked for the scan.
 * **When the hub pushes your provider’s support details** — their name, logo,
    
   support address and accent colour — on connect and whenever they change them.
   Nothing is sent from the site in that exchange.

Every one of those requests is authenticated with this site’s own API key and
 an
HMAC-SHA256 signature; the shared secret never travels over the wire.

#### api.wordpress.org and downloads.wordpress.org

The malware scan verifies WordPress core and wordpress.org-hosted plugins
 against
their published checksums, using WordPress.org’s own public APIs at https://api.
wordpress.org/core/checksums/1.0/ and https://downloads.wordpress.org/plugin-checksums/.
This happens whenever a scan runs, whether or not the site is paired with a hub.

These requests identify only the release being looked up: the WordPress version

and locale, and each installed plugin’s slug and version. No file contents, hashes,
site URL or user data are sent — the reference checksums are returned and compared
locally.

These are WordPress.org services, covered by the WordPress.org privacy policy:
 
https://wordpress.org/about/privacy/

## Screenshots

[⌊The Bulwarden screen at a glance: whether the site is connected, whether
backups
are encrypted, when the last backup and scan ran, and how current
the scanner's 
signature list is.⌉⌊The Bulwarden screen at a glance: whether the site is connected,
whether
backups are encrypted, when the last backup and scan ran, and how current

the scanner's signature list is.⌉[

The Bulwarden screen at a glance: whether the site is connected, whether backups
are encrypted, when the last backup and scan ran, and how current the scanner’s 
signature list is.

[⌊Connecting the site: the connect string you paste into your hub, and the
API credentials
behind it.⌉⌊Connecting the site: the connect string you paste into your hub, and
the
API credentials behind it.⌉[

Connecting the site: the connect string you paste into your hub, and the API credentials
behind it.

[⌊Backups: a run in progress showing the phase it has reached, its log and a
cancel
button, above the encryption passphrase and the folders left out of
the archive.⌉⌊
Backups: a run in progress showing the phase it has reached, its log and a
cancel
button, above the encryption passphrase and the folders left out of
the archive.⌉[

Backups: a run in progress showing the phase it has reached, its log and a cancel
button, above the encryption passphrase and the folders left out of the archive.

[⌊The malware scan: what the last one found, grouped by kind, and how much of
the
site could be checked against the files WordPress.org published.⌉⌊The malware scan:
what the last one found, grouped by kind, and how much of
the site could be checked
against the files WordPress.org published.⌉[

The malware scan: what the last one found, grouped by kind, and how much of the 
site could be checked against the files WordPress.org published.

[⌊The activity log: an append-only record of credential changes, backups,
restores,
scans and repairs, including the ones the hub triggered.⌉⌊The activity log: an append-
only record of credential changes, backups,
restores, scans and repairs, including
the ones the hub triggered.⌉[

The activity log: an append-only record of credential changes, backups, restores,
scans and repairs, including the ones the hub triggered.

[⌊The dashboard support card — who maintains this site, how to reach
them, and the
Request help button that files a ticket with them directly.⌉⌊The dashboard support
card — who maintains this site, how to reach
them, and the Request help button that
files a ticket with them directly.⌉[

The dashboard support card — who maintains this site, how to reach them, and the
_Request help_ button that files a ticket with them directly.

## Installation

Bulwarden is one half of a pair: this plugin runs on the site, and a **hub** runs
somewhere else and manages it. Installing the plugin on its own does nothing until
you pair it with a hub.

 1. Install and activate the plugin as you would any other — upload it under Plugins
    → Add New → Upload Plugin, or install it from the directory.
 2. Go to **Bulwarden** in the WordPress admin menu. The plugin generated an API key
    and secret for this site on activation; the screen shows a single **connect string**
    that carries the site name, URL and both credentials.
 3. Copy that string and paste it into your hub’s “Connect a site” form. That is the
    whole pairing step — there is nothing to type on this side, and no hub address 
    to configure here.
 4. If you want encrypted backups, set a **backup passphrase** on the same screen. 
    It never leaves this server, and it is required to restore. Keep it somewhere you
    will still have it after the disaster you are backing up against — nobody, including
    your hub operator, can recover a backup without it.

#### Do I need a hub?

Yes, for anything beyond installing it. The hub is separate software: use the hosted
one, run your own, or use the one your maintenance provider runs. Until the site
is paired, this plugin contacts nothing and does nothing.

#### Upgrading

Updates install like any other plugin. Your credentials, passphrase and settings
survive an update; nothing needs re-pairing.

## FAQ

### Does this plugin send my site’s data anywhere on its own?

No. Before you pair it with a hub it contacts nothing at all, and it never
 discovers
or chooses a hub by itself. After pairing, data moves only for the things listed
under Privacy below — health checks, backups, malware scan findings — and only to
the hub you paired with.

### Can the hub operator read my backups?

No, provided you set a passphrase. The archive is encrypted **on this server**
 
with a key derived from your passphrase, and only the ciphertext is uploaded. The
passphrase is never transmitted, so a hub holds bytes it cannot decrypt. The same
property is why nobody can recover a lost passphrase for you.

### What happens if I deactivate or delete the plugin?

The site stops answering its hub, which the hub will notice and report as the
 agent
going unreachable. Deleting the plugin removes its settings, its audit-log table
and its working directories (see `uninstall.php`); backups already stored on a hub
are unaffected.

### Is the plugin available in German?

Yes. Everything the plugin puts on screen — the settings page, the support
 card
on the dashboard, every status message and every scan finding — is translated, and
WordPress picks it up from your site’s own language setting with nothing to configure.
Both German locales are covered: _Deutsch_ and _Deutsch (Sie). The wording is formal
throughout, because these screens are read by the owner of the site rather than 
by the agency that maintains it.

### Does it work on multisite?

The plugin runs per site. A network-wide database upgrade is reported but not
 performed—
core’s own network upgrade screen walks every site in the network, and that is where
it belongs.

### Why does the malware scan flag a file I know is fine?

The file-integrity check compares every core and WordPress.org-hosted file
 against
the checksums WordPress.org published for that exact release. A caching or optimisation
plugin that rewrites a stylesheet produces the same mismatch a backdoor does, which
is why “this file no longer matches what was published” is reported as a fact rather
than a verdict, and why non-executable files are warnings rather than critical findings.
Your hub can show you the actual diff.

### What does it do to my site’s performance?

Nothing on page loads: there is no front-end code path. Backups and malware
 scans
are real work and run in the background across WordPress cron ticks, to a time budget,
so they do not block visitors. Health checks are answered on demand when the hub
asks.

### Which PHP and WordPress versions are supported?

PHP 7.4 or newer and WordPress 6.0 or newer. Backups additionally need the
 sodium
extension (encryption) and either `zlib` or the `zip` extension (archiving); the
Settings screen tells you if either is missing.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Bulwarden” is open source software. The following people have contributed to this
plugin.

Contributors

 *   [ jgloeckl.de ](https://profiles.wordpress.org/bulwarden/)

[Translate “Bulwarden” into your language.](https://translate.wordpress.org/projects/wp-plugins/bulwarden)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/bulwarden/), check 
out the [SVN repository](https://plugins.svn.wordpress.org/bulwarden/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/bulwarden/) by [RSS](https://plugins.trac.wordpress.org/log/bulwarden/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 1.29.1

 * Maintenance: coding-standard clean-up in the one-click sign-in code and
    the 
   settings defaults. No behaviour changes.

#### 1.29.0

 * New: **one-click sign-in from your maintenance provider.** Your provider can
   
   open this site’s admin as an administrator without a password, over the same 
   signed connection it already uses to update and back up the site. Each sign-in
   uses a link that works exactly once, for one minute, for one named administrator,
   and is written to the activity log with the account it used. A switch on the 
   Bulwarden screen, beside the credentials, turns it off — and turning it off closes
   the route, not only the button.

#### 1.28.2

 * Fix: **the progress of a file restore is now reported correctly when it
    finishes.
   
   The step that writes a backup’s files back ended by reporting
    that it had written
   none of them — a completed restore showed “0 of 93,680 files, 0%” beside a count
   of the files it had just written. The two figures were also counted differently:
   the total included the backup’s database dump and its description alongside the
   files, so it could never be reached however many files were restored. It now 
   counts the backup’s files, and every one of them is reported either as written
   or as deliberately left alone — this site’s own wp-config.php, Bulwarden itself,
   and WordPress’ drop-ins are never overwritten, and your hub now says so instead
   of leaving the difference unexplained.

#### 1.28.1

 * Fix: **a restore no longer spends most of its time waiting to be asked to
    carry
   on.
    A restore runs in rounds, and at the end of each one the plugin
    asked WordPress’
   scheduler to come straight back for the next — except that the request was made
   from inside the scheduler, which refuses it. So the work only continued when 
   a visitor happened to arrive, or after a three-minute safety timer, and on a 
   quiet site — which is most sites being restored — a restore took hours of waiting
   for minutes of work. On sites where WordPress’ own scheduler is switched off 
   in favour of a real cron job, which is the usual advice for a site that matters,
   nothing continued the restore at all between those cron runs. Each round now 
   asks for the next one directly.

#### 1.28.0

 * New: **a banner on a site that has the plugin but is not connected to
    anyone.
   
   Until a site is paired with a maintenance hub the plugin does
    nothing at all—
   no backups, no scans, nothing watched — and from the dashboard that is indistinguishable
   from a plugin quietly working. Anyone who can install plugins now sees a notice
   saying so, with the way to connect the site one click away, and it can be put
   off for a month at a time. The support widget on the dashboard no longer says
   Bulwarden maintains a site that nobody has agreed to maintain.
 * New: **a way to ask Bulwarden for help when there is no maintenance provider
   
   at all.
    The Request help button on the dashboard writes to whoever looks
    after
   the site, which is no use to somebody who installed this and has nobody. The 
   plugin’s own settings screen now carries a short form that reaches Bulwarden 
   directly, and says exactly what it sends: the site’s address, its WordPress and
   PHP versions and the reply address typed into it. Nothing else, and no keys.
 * New: **the hub is told whether a backup encryption passphrase has been set** —
   
   the fact that there is one, never the passphrase itself, which stays on this 
   site and is never sent anywhere. Without one every backup stops before it starts,
   and an agency previously found that out from a failed run at three in the morning
   rather than from the setup nobody finished.
 * New: **five more malware detections**, from a study of 400,000 production
    webservers(
   Kasturi et al., _Mistrust Plugins You Must_): code that redefines a built-in 
   PHP function instead of loading the real one, a function name assembled from 
   fragments and then called, a file written to disk, made executable and run, code
   that deletes the file it ran from, and the branding “nulled” plugin marketplaces
   stamp into the paid plugins they give away with something added.

#### 1.27.3

 * Fixed: **a restore of a large backup no longer leaves the site stuck showing
   “
   Briefly unavailable for scheduled maintenance”.
    Downloading and decrypting
    
   a backup take as long as they take, and a round of work that had spent its whole
   time doing that went on to start restoring the database anyway — which begins
   by switching the site into maintenance mode. It then managed a single instruction
   before stopping for the round, so the maintenance mode bought nothing; and because
   WordPress checks for maintenance mode before it loads any plugin, a round that
   did not live long enough to switch it back off took the site’s own timer down
   with it — the timer being the only thing that could start the next round. The
   site sat unavailable, and the restore sat still, until WordPress itself gave 
   up on the maintenance flag ten minutes later. A round that is out of time now
   stops cleanly between phases, and maintenance mode is switched off the moment
   the database work pauses rather than at the very end of the round.

#### 1.27.2

 * Fixed: **restoring a backup onto a different site now rewrites its addresses
   
   to _this_ site’s.
    With “rewrite addresses” ticked, the restore replaced the
   
   backup’s web address with the address it read from the site — but it read it 
   after the backup’s database was already in place, so it was reading the backup’s
   own address back. A backup moved to a new domain either had nothing rewritten
   at all, leaving the restored site pointing at the domain it was copied from, 
   or had every address in the database rewritten from `http://` to https:// on 
   that old domain. The restore reported success in both cases. This site’s addresses
   are now recorded before the restore starts and used as the target; if they could
   not be recorded, the addresses are left alone and the restore says so instead
   of guessing.
 * Fixed: a backup now records the site’s own stored web address rather than the
   
   address of the internal request that happened to start it, so the restore searches
   for the address that is really in the backup.

#### 1.27.1

 * Fixed: **a long restore of a backup that predates this plugin now finishes.**
   
   Restoring the database replaces the list of active plugins with the backup’s,
   and a backup taken before this plugin was installed does not list it. On a site
   big enough to need several rounds of work, the round after the swap loaded WordPress
   without the plugin, so nothing was left to carry on and the restore sat half-
   done until the hub gave up. The plugin now puts itself back the moment the table
   is swapped in, in the same round.
 * Changed: **a restore never writes WordPress’ drop-in files**
    (`object-cache.
   php`, `db.php`, `advanced-cache.php` and the rest of that family in `wp-content`).
   They are loaded before any plugin and are tied to the server they were written
   on — a cache server address, database hosts, absolute paths — so a backup’s copy
   restored onto another server took the whole site down before anything could repair
   it. The copy already on the site is kept, and the restore log says which files
   were left alone.

#### 1.27.0

 * Fixed: **a large restore no longer stops part-way through the database.**
    Restoring
   the database replaces the WordPress options table, and the restore’s own progress
   record and its “carry on” timer were rows in that table — so on a site big enough
   to need several rounds of work, the round that swapped the table in was the last
   one that ever ran. The site sat half-restored, the dashboard showed the last 
   progress it had heard of, and if the backup came from another site, the hub could
   no longer reach this one at all. The restore now puts its own state and this 
   site’s own credentials back the moment the table is swapped in, and carries on.
 * Fixed: each round of a long restore now starts the next one immediately
    instead
   of waiting for a timer, which roughly halves the wall-clock time of a restore
   that spans many rounds — and no longer depends on a visitor arriving to trigger
   it.
 * Improved: **restoring a large database is many times faster.** Backups now
    write
   rows in batches rather than one statement per row, and the restore replays them
   inside transactions, so a database that used to trickle in at about a thousand
   rows a second now goes back at the speed of the disk. Backups made by earlier
   versions restore exactly as before.
 * Improved: a restore interrupted by the hosting provider (a killed process, a
   
   dropped database connection) now resumes exactly where its last saved progress
   left off. Previously the rows written since the last save were replayed on top
   of themselves, which on a bad day was enough errors to abandon the restore.
 * Improved: a restore that resumes after a pause keeps the database session
    settings
   the backup asked for (character set, foreign-key checks, the rule that keeps 
   a row with id 0 from being renumbered).
 * Improved: two rounds of one restore can no longer run at the same time.
 * Improved: the hub’s restore page now says when it could not ask the site for
   
   progress, and why, rather than showing the last answer as if it were live.
 * Developer note: `BULW_RESTORE_TICK_BUDGET` (seconds) shortens a restore’s
    per-
   round budget so that a multi-round restore can be reproduced on a small test 
   site.

#### 1.26.0

 * New: **the scan now reports program files stored on your site.** A malware scan
   
   reads code — PHP, JavaScript, page templates — so an installer or a program sitting
   in your media library was invisible to it. That is the commonest thing a compromised
   WordPress site is actually used for: not defacing the site, but quietly hosting
   a file that is mailed out to other people, until the domain is flagged by Google
   Safe Browsing or the host suspends the account. These are now listed on the security
   tab.
 * New: a program stored under a name that says otherwise — a Windows or Linux
    
   program saved as `.jpg`, `.pdf` or `.mp4` in the uploads folder — is reported
   as a critical finding. There is no innocent reason for one, and it is exactly
   how such a file is kept out of sight.
 * New: a program file found inside `wp-admin` or `wp-includes` is reported as
    
   critical. WordPress itself ships none, in any language.
 * New: every one of these findings carries a SHA-256 checksum of the file, shown
   
   next to it in your provider’s dashboard. That is the part your provider can act
   on: it identifies the file itself, so it can be checked against a malware database
   and recognised if the same file turns up on another site.
 * Note: an ordinary program file elsewhere on the site is reported for
    information
   only. It never marks the site as infected and never triggers an alert — a business
   that distributes its own software has every right to store one, and the scan 
   says so rather than crying wolf.

#### 1.25.1

 * Fix: **a restore no longer hands this site another site’s identity.** WordPress
   
   keeps this plugin’s settings in the database, so restoring a backup made on a
   _different_ site replaced this site’s connection credentials with that site’s—
   and from that moment your provider’s hub could not reach this site at all: no
   health checks, no cron, no scans, no backups. Putting it right meant logging 
   in and pairing the site again by hand. The credentials, the hub address and the
   backup passphrase are now carried across a restore and put back the instant the
   database is in place. Everything else in the backup’s settings is restored as
   before.
 * Fix: the backup passphrase was replaced the same way, so the next backup was
   
   encrypted with a passphrase recorded for a different site — something nobody 
   would discover until the day they needed it.
 * Fix: a backup no longer carries its own “restore in progress” marker, which
    
   could drop one run’s state onto a site part-way through another.

#### 1.25.0

 * New: **a backup can now be restored onto a site whose database tables are named
   
   differently.
    WordPress puts a short prefix in front of every one of its table
   
   names, and it is rarely the same on two installs — so restoring a backup onto
   a staging copy, or onto a site being rebuilt after a break-in, used to be refused
   outright. Your provider can now ask for the tables to be renamed as they are 
   restored, which leaves the site reading its own names and needs no change to 
   wp-config.php at all. Everyone’s role and personal settings are moved across 
   with them, so nobody loses their login.
 * Change: **the message shown when the names do not match no longer recommends
   
   something harmful.
    It used to suggest editing wp-config.php to match the backup.
   
   On a site that is up, that stops WordPress finding its own database, takes the
   site down, and stops this plugin running — which removes the only thing able 
   to perform the restore. The message now explains the two safe ways forward instead.

#### 1.24.1

 * Fix: **a restore that stopped explained itself in punctuation codes.** The messages
   
   this site sends your provider when a restore cannot go ahead — the reason it 
   stopped, and the database errors behind it — were encoded for a web page here
   before being sent, and then encoded a second time by the page that displayed 
   them. Every apostrophe and quotation mark in the one line meant to explain the
   failure arrived as a run of digits and semicolons, including the table names 
   in a database error, which left the explanation unreadable exactly when it was
   needed. The messages are now sent as written.

#### 1.24.0

 * New: **a restore can now be watched while it runs.** Putting a site back can
   
   take an hour on a large one, and until now your provider’s hub learned nothing
   at all until it had finished — a spinner, and then an answer. This site now reports
   every step of a restore as it goes: checking, downloading, decrypting, the database,
   the files, the addresses, and the finish, each with how far through it is and
   how long it has taken, alongside the run log as it is written.
 * Change: a step that will not run says so from the start, and says why — a
    database-
   only restore is not quietly “about to” restore your files.

#### 1.23.0

 * New: **the malware scan knows eighteen more kinds of backdoor.** They were
    written
   against real ones: the plugins compromised on wordpress.org in June 2024 and 
   April 2026, the two backdoored plugins found there in 2026, and the fake plugins
   that plant a hidden administrator, rewrite `wp-cron.php`, take a role from the
   request, or unpack code fetched from a server nobody published. Each check pairs
   the dangerous action with the thing that makes it illegitimate, so an honest 
   plugin doing the same work is not reported.
 * Fix: **eight existing checks reported honest code as malware.** A code comment
   
   mentioning a shell command, a full-width video frame, a compressed setting, an
   embedded font, a temporary-login plugin creating its account from variables, 
   and a plugin’s own test files were each being flagged. Measured against the 250
   most popular plugins and WordPress core, those checks now report nothing there.
   Your provider’s hub refuses to install an update it considers infected, so this
   also unblocks updates it was wrongly refusing.

#### 1.22.2

 * Change: **nothing on your site changes in this release.** It exists because the
   
   plugin’s description here had fallen behind what the plugin actually does, and
   the directory shows that text from the released version rather than from the 
   latest one — so correcting it takes a release.
 * Change: three features that shipped in 1.20.0, 1.21.0 and 1.22.0 were described
   
   only in this changelog and not in the description anybody reads first: the activity
   log on your own site, the scan the site starts for itself when a plugin nobody
   published appears, and what a restore now does when it cannot finish.
 * Change: the security section described how requests between this site and your
   
   provider are signed, and had been describing the form used before 1.11.0. The
   signature has covered a single-use value per request since then, which is what
   makes a captured request useless rather than merely short-lived.
 * Change: the screenshots in the directory listing were taken nine releases ago,
   
   before the support card had a _Request help_ button or a telephone number. They
   are re-taken from this version.

#### 1.22.1

 * Fix: **backups of sites larger than 4 GB could not be completed.** Past that 
   size the
    archive was written in a form no unpacker can read, so the check that
   runs on every finished archive rejected it and the backup was reported as failed
   with “the finished zip could not be re-opened (code 21)”. Smaller sites were 
   never affected. On a site whose hosting is missing PHP’s `zip` extension that
   check cannot run, so an affected backup may have been stored instead of failing—
   those cannot be restored, and should be replaced with a fresh backup after updating.

#### 1.22.0

 * Improve: **a restore that cannot finish no longer leaves your site half-restored.**
   Each
    table is now rebuilt alongside the live one and swapped in only once it
   is complete, so a restore interrupted by a timeout, a server limit or an error
   leaves your database exactly as it was instead of partly replaced. Long restores
   also pick themselves back up where they stopped rather than starting over, and
   your site is put into maintenance mode only while the database is actually being
   written.
 * Improve: **a restore now says when something went wrong.** Failed database statements
   are
    counted and reported instead of being discarded, and a restore that could
   not create or replace your tables stops immediately and explains why rather than
   finishing and reporting success. Your provider’s dashboard shows the difference.
 * Improve: **checks before a restore starts, so it fails before it can do damage.**
   Free
    disk space, the database permissions the restore needs, whether the downloaded
   backup matches what was stored, and whether the backup’s tables match this site’s
   are all verified first. An incomplete download is now reported as an incomplete
   download rather than as a wrong passphrase.
 * Improve: **restores can optionally remove files the backup does not contain**,
   for
    recovering from a hack rather than from a mistake. Off unless your provider
   asks for it, and never applied to your uploads.
 * Improve: **after a restore, the site is put back in working order**: caches are
   cleared
    (so you see the restored site and not the old one), plugins and themes
   the restored database names but that are no longer installed are deactivated 
   rather than breaking the site, and a pending WordPress database upgrade is completed.
 * Improve: backups and restores can no longer run at the same time. A backup started
   while
    a restore was in progress could previously store a copy of a half-restored
   site.
 * Improve: web addresses are rewritten more completely on a restore — the WordPress
   
   address and the site’s folder path as well as the home address — and the rewrite
   can no longer skip rows on large tables or be stopped by unusual stored data.
 * Improve: database triggers and stored routines are now included in backups and
   restored
    with them.
 * Fix: the number of files a restore wrote is shown again on your provider’s dashboard.
 * Fix: files whose names contain two dots in a row (`photo..2019.jpg`) are no longer
   
   skipped by a restore.

#### 1.21.0

 * Improve: **the Activity log now records the things it was missing.** Updates 
   installed on
    this site by your provider are written down — what was updated,
   and anything that failed — and so are a file being read off this server, a change
   to the provider details shown on your dashboard, and a change to the address 
   this site reports to. Between them those were the actions with the biggest effect
   on the site and the least trace of who asked for them. Nothing about how the 
   plugin works changes; the log simply answers more of the questions people bring
   to it.

#### 1.20.0

 * Add: **a plugin that appears out of nowhere is now scanned immediately.** When
   a plugin
    turns up on your site that WordPress.org does not publish — a premium
   plugin you installed by hand, or something nobody installed at all — the site
   starts a malware scan itself, within seconds, and tells your provider what set
   it off. Fake plugins are how most WordPress break-ins arrive, and until now the
   first look at one waited for the next scheduled scan. Nothing is blocked, quarantined
   or deleted: the site looks, and your provider decides. Anything appearing in 
   the _must-use plugins_ folder counts too, since a file put there runs on every
   request with nothing to activate it and no way to switch it off.

#### 1.19.0

 * Add: **German**. Every screen this plugin adds — the settings page, the dashboard
   
   support card, every status message, every scan finding and every error — is translated,
   in both _Deutsch_ and _Deutsch (Sie)_. Nothing to configure: WordPress uses your
   site’s own language setting. The wording is formal throughout, because these 
   screens are read by the person whose site it is rather than by the agency maintaining
   it.
 * Add: a page trace now lists the page’s resource hints — the
    and `<link rel="
   dns-prefetch">` tags — and says which plugin asked for each one. That tag is 
   the reason a page reaches out to a third-party server before it has drawn anything,
   it is what a webfont privacy check flags, and it was the one thing on a traced
   page the trace could not name: nothing enqueues it, and WordPress prints it itself,
   so tracing the bytes only ever answered “WordPress”. Hints added through WordPress’
   own filter now carry the callback that added them; hints printed straight into
   a theme template are found in the page and labelled with the file that printed
   them.
 * Add: **server-configuration checks**. The `.htaccess`, `php.ini` and `.user.ini`
   files
    inside your WordPress folder are now read and reported to your provider,
   with a history of every time one of them changed. These are the one part of a
   WordPress install that changes what the _server_ does rather than what WordPress
   does — nothing in the WordPress admin shows them, and a single line in one can
   outlive every other trace of a break-in. Flagged: a file set to run before every
   request, a rule that makes uploaded images execute as PHP, a redirect that only
   fires for search engines or for visitors arriving from one, and a `php.ini` sitting
   in the media library, which has no legitimate reason to exist.
 * Note: nothing is ever changed on your site. A good share of these directives 
   are written
    deliberately by a host or a security plugin, so the point is that
   you get to see them and decide — not that they are removed for you. A folder 
   correctly set to refuse PHP is reported too, as good news rather than as a problem.
 * Add: the scan’s signature list can now be updated by your provider without updating
   this
    plugin. A new detection reaches your site within hours instead of waiting
   for a plugin release to travel a whole fleet — and a signature that turns out
   to fire on a legitimate plugin can be switched off just as quickly. A site that
   has never been paired, or whose provider is unreachable, keeps scanning with 
   the list this plugin ships.
 * Add: **email delivery is now monitored**. WordPress keeps no record of the mail
   a site
    sends: when a contact form stops delivering, or an order confirmation
   never arrives, the error is raised and thrown away and the site goes on looking
   perfectly healthy. This now counts what the site tries to send and what fails,
   keeps the reason the mail server gave, and reports which transport is doing it—
   PHP’s `mail()`, an SMTP server, or the plugin that configured one. Your provider
   sees it alongside everything else.
 * Note: no part of any message is recorded. Not the recipient, not the subject,
   not the
    body — only how many were attempted, how many failed, and why. And “
   this site has sent no email” is reported as exactly that rather than as a fault:
   a site with no forms and no shop legitimately sends nothing for months.
 * Add: a malware scan can now be **cancelled** from your provider’s hub. A full
   scan reads
    every file on the site across several scheduled runs, so on a large
   or slow one it can sit half-finished for a long time — and until now nothing 
   could stop it, including the next scan, which was refused because a run was already
   going. Cancelling stops the walk on the site and lets a new scan start straight
   away.
 * Add: the backup passphrase field now shows how strong the passphrase is as you
   type.
    Nothing is enforced — a backup passphrase is not a login, and nobody, 
   including your provider, can recover a backup without it, so being locked out
   by a rule you did not expect would be far worse than a weak one. The meter simply
   says what helps: **length beats punctuation**, and four unrelated words reach
   the top of the scale where a short password with symbols in it does not.
 * Fix: a remote update could be killed part-way through by the host’s PHP time 
   limit. A
    core update or a batch of plugin updates is downloaded, unpacked and
   installed inside a single request, and thirty seconds — the default on a great
   many shared hosts — is not long enough. The update is now taken off that clock
   and detached from the connection that asked for it, so it always runs to completion
   even if the hub stops waiting; the hub, for its part, now re-checks the site 
   rather than reporting a slow update as a failed one.

#### 1.18.0

 * Add: the malware scan now recognises the backdoors that arrive dressed as an 
   ordinary plugin, not just the classic obfuscated webshells. A plugin absent from
   WordPress.org has no published files to check it against, so until now one like
   this surfaced only as _unverifiable_ — never as malware — however plainly hostile
   its code. The scan now flags, as critical, a plugin that forges a login as an
   administrator it picked for the caller, that gates a privileged action on a secret
   key baked into the file, that creates an administrator from hard-coded credentials,
   that hides accounts from the users list, or that runs request data through a 
   dynamic function call.
 * Note: each of these is matched by the thing that makes it illegitimate, not by
   an API a healthy plugin also uses — a login plugin that sets a session after 
   checking a password is untouched, and WordPress’ own installer, which creates
   the first administrator on every site, is not mistaken for a planted one. The
   false positives it must not raise are pinned by a test alongside the detections
   themselves.

#### 1.17.1

 * Fix: an untouched WordPress could be reported as having a missing or modified
   core file. A non-English WordPress installs the readme and licence in its own
   language too — `liesmich.html` on a German site, `licenza.html` on an Italian
   one, eight more names across the languages — and only the English `readme.html`
   and `license.txt` were treated as the documentation they are. So deleting them,
   which most hardening guides tell you to do and a good many hosts already have,
   was reported as a missing core file, and a copy edited to hide the version number
   or rewritten in transit was reported as a critical modification. Neither could
   be cleared: the file’s own language package stopped shipping it years ago.
 * Change: the release’s documentation is now recognised by what it is — a readme
   or licence sitting in the site root — rather than by a list of the English names,
   so the same is true of every language and of any name a future one uses. Files
   inside `wp-admin` and `wp-includes` are unaffected: nothing legitimately rewrites
   one of those, whatever its extension, and a modified one is still a critical 
   finding. The skipped files are still pattern-scanned for malware exactly as before.

#### 1.17.0

 * Add: **page tracing**. Your provider can now ask this site, from their hub, to
   render one of its own public pages and report which file produced each line of
   the HTML that came back — core, a plugin, the theme, a must-use plugin, a drop-
   in. The question it exists to answer is the one nothing in WordPress answers:
   _which_ plugin adds the Google Fonts tag, or the tracking script, or the markup
   nobody can find a setting for. Styles and scripts are reported separately with
   the callback that enqueued each one, because those tags are printed by WordPress
   itself and naming WordPress helps nobody.
 * Note: the trace never alters the page. Nothing is injected into the output, no
   marker and no comment; the attribution travels beside the HTML as byte offsets,
   so what is measured is exactly the page a visitor …

## Meta

 *  Version **1.29.1**
 *  Last updated: **9 hours ago**
 *  Active installations: **10+**
 *  WordPress Version: ** 6.0 or higher **
 *  Tested up to: **7.1.1**
 *  PHP Version: ** 7.4 or higher **
 *  Language
 * [English (US)](https://wordpress.org/plugins/bulwarden/)
 * Tags:
 * [backup](https://en-ca.wordpress.org/plugins/tags/backup/)[maintenance](https://en-ca.wordpress.org/plugins/tags/maintenance/)
   [malware](https://en-ca.wordpress.org/plugins/tags/malware/)[monitoring](https://en-ca.wordpress.org/plugins/tags/monitoring/)
   [security](https://en-ca.wordpress.org/plugins/tags/security/)
 *  [Advanced View](https://en-ca.wordpress.org/plugins/bulwarden/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/bulwarden/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/bulwarden/reviews/)

## Contributors

 *   [ jgloeckl.de ](https://profiles.wordpress.org/bulwarden/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/bulwarden/)