Once you’re maintaining 50+ WordPress sites from a single tool, the volume of credentials you accumulate climbs fast. Per-site admin passwords, SSH credentials and passphrases, the SMTP password used to send notification mail, server profiles ── all of these need to live somewhere reliable, and you need to be able to move them to a new machine without retyping fifty entries by hand.
We added that “back up everything to a ZIP, restore on the new machine” capability to WP Maintenance Manager early on. Building it taught us a clear lesson: “a backup that works” and “a backup that travels” are two different design problems. This post is an honest write-up of the trade-off we hit and the path we chose for safely moving encrypted credentials across machines.
1. Why a credential backup matters at all
If you only run a handful of sites, a generic password manager (1Password, Bitwarden, etc.) is enough. Once the count goes into double digits, two new requirements show up:
- You want maintenance automated, which means the tool itself has to hold credentials so it can SSH in and run
wpcommands without prompting - You replace your machine, or your team grows, and re-entering fifty entries by hand is not a realistic option
- You want an audit trail: which site was touched, by which account, at which time, exported into client-facing reports
So the tool stores credentials locally. The moment you accept that, you’ve also accepted the responsibility of making those credentials survive a machine replacement, an OS migration, or a hardware failure.
2. How the credentials are stored
Storing credentials in plaintext is obviously off the table. The minimum bar is symmetric encryption with a key that lives outside of the credential file itself.
WP Maintenance Manager uses Python’s cryptography library — specifically Fernet symmetric encryption — and stores its key in a file directly under the user’s home directory (~/.wp_maint_pro_key). Each credential is saved in the form ENC:gAAAAABoT..., an opaque blob that means nothing without the key.
The interesting choice was whether the encryption key should be machine-specific or shared across machines:
| Property | Shared key | Machine-specific key (chosen) | |—|—|—| | Settings file leaks | Whoever has the key reads everything | Without that machine’s key, the leak is inert | | Cloud-synced backups (e.g. iCloud, OneDrive) | Risky — key may sync alongside | Backups alone aren’t enough to compromise | | Cross-machine restore | “Just works” | Doesn’t work as-is |
Security-wise, machine-specific keys are clearly the safer pick. Even if a backup ZIP is leaked through cloud sync, a USB stick, or a stolen laptop, the credentials inside remain unreadable without the corresponding key file on a specific host.
But this choice carries the cross-machine pitfall described next.
3. The pitfall: machine-specific key × portable ZIP
Our first cut of the backup feature did the obvious thing:
1. User clicks “Export backup” 2. Tool packages sites_*.json, server_profiles.json, settings.json, etc. into a ZIP 3. The ZIP contains the encrypted-as-stored ENC:... blobs verbatim
Same machine, no problem — same key, same decryption, everything works.
Take that ZIP from a Mac to a Windows machine and try to restore, however, and you get the worst kind of failure mode:
- The restore succeeds silently (ZIP unpacks, files land in the right place)
- The site list shows up normally in the UI
- But every maintenance run fails to SSH in
- SMTP authentication errors out on every notification mail
- WordPress admin login fails with “wrong password” for every site
Decryption is failing quietly, leaving a list of unusable credentials. The backup file was fine. The restore logic was fine. Yet nothing works ── the kind of bug that’s hardest to catch because each layer in isolation passes its own test.
4. The design trade-off
Three options were on the table.
Option A — switch to a shared key
Simplest fix, worst security. With a shared key, leaking the backup ZIP plus the key file leaks every credential. That’s a bad combination of failure modes for cloud-synced devices, so we ruled this out.
Option B — keep ciphertext as-is and decrypt later
Not actually possible. Ciphertext encrypted with machine A’s Fernet key cannot be decrypted by machine B’s Fernet key, full stop. There is no “decrypt later” path.
Option C — decrypt at export, re-encrypt on import (chosen)
Export:
Mac key decrypts → plaintext credentials → put into ZIP
Import:
Open ZIP → plaintext credentials → encrypt with the new machine's key → save in normal format
Inside the ZIP, the credentials live as plaintext for as long as the user keeps that ZIP around.
The expected reaction here is “isn’t plaintext-in-the-ZIP scary?” We had the same reaction. What got us across that line:
- The ZIP is a user-managed artifact on the user’s own disk, not a server-to-server transfer or a cloud-synced asset
- Users can apply additional protection at their own discretion — encrypted volumes, encrypted ZIP, GPG, or a hardware-encrypted external drive
- The “cloud sync uploads it accidentally” risk is a storage-location policy question, addressed by user discipline rather than by us refusing to ship a working migration path
- The cost of the alternative ── a backup that doesn’t decrypt on the target machine ── is a much worse failure mode for our users, with no recovery option except “do it again”
The framing we settled on: the security boundary for the ZIP belongs to the user who created it. This isn’t a compromise; it’s the standard pattern for any artifact a user explicitly chooses to take off the original device.
Note: minimizing plaintext exposure window
Even with this design, the backup ZIP carries plaintext-equivalent credentials between export and import. We codify the following operational rules in the user manual:– Create the backup immediately before migration, not as a long-term archive
– Delete the ZIP once restore is verified
– For long-term archival, wrap the ZIP in another encryption layer (full-disk encryption, encrypted ZIP, GPG, etc.)
5. The bonus problem: absolute paths
While reworking the encryption flow, we caught a second cross-machine pitfall.
The logo image used in white-label reports was being saved with an absolute path like /Users/<username>/Documents/logo.png in settings.json. Move that to Windows and there’s no /Users/... namespace to begin with. Even moving between two Mac users with different home directories breaks it.
The fix was small: when restore lands on a machine where the referenced file doesn’t exist, leave that field empty rather than carrying a dead path. The user re-selects the logo once after migration; everything else carries over.
Lesson: when a settings file stores file paths, either store them in a form meaningful on the target machine (relative paths, identifiers), or wire in a “fallback to empty if not found” path on import. Independent from credentials, but the same class of cross-machine pitfall.
6. Principles we extracted
Reworking this feature surfaced a few design principles worth writing down.
Principle 1 — Encryption design must cover storage *and* migration
Designing encryption with “where it sits at rest” in mind alone is not enough. Lay out the backup, PC replacement, and OS migration scenarios up front, see which of them cross the encryption boundary, and choose the scheme accordingly.
Principle 2 — Make the security boundary explicit
State plainly what the tool guarantees and where the user takes over. Vague boundaries lead to either over-trust or over-anxiety, neither of which serves the user well.
Principle 3 — “Works” and “travels” are different quality bars
A backup that passes a same-machine round-trip test will silently fail across machines. That class of bug is invisible without deliberately testing the cross-machine path during design.
7. How WP Maintenance Manager ships this
The current backup feature ships with the Option C flow plus the absolute-path fallback:
- Backups generated from the GUI in a few clicks
- Restore automatically re-encrypts under the destination machine’s key
- Logo and other absolute path references degrade to empty fields when the file doesn’t exist on the target
- Machine-specific key design is preserved (a leaked backup ZIP alone is not enough to expose credentials)
Maintenance work routinely involves PC replacements, OS migrations, and team handovers. The maintainability of the maintenance tool itself sets a ceiling on the maintenance quality you can deliver, so this is one of the places we spent the engineering budget.
Summary
- For local credential storage, machine-specific encryption keys are the right call from a security perspective
- That choice does not compose with “ZIP up the encrypted blobs and ship them” — the result won’t decrypt on the new machine
- The fix is decrypt on export, re-encrypt on import, with a clear plaintext-exposure window the user controls
- Pair this with operational guidance so users minimize how long the ZIP exists and how widely it travels
- Hunt down the secondary cross-machine pitfalls (absolute paths, machine-local timezone settings, etc.) at the same time
WP Maintenance Manager is built around the assumption that maintenance quality and tool reliability are the same problem. We treated cross-machine portability of credentials as part of that problem, not as a feature added later. See the User Guide for the operational details, or grab the desktop app from the main site.