Every few months I look at my backup situation and ask myself the same question: if my laptop disappeared right now, what would actually hurt? The answer is rarely the OS or the apps. It's the working folder โ notes, half-finished scripts, photos I haven't sorted yet, the boring documents that take a year to recreate.
You can solve that with three clicks on Dropbox or OneDrive and a credit card. It's a perfectly fine answer for most people. But if you've been around MojaLab for a while you already know the rule of the house: if a problem can be solved with off-the-shelf SaaS or with a weekend, a terminal and a few open-source tools โ we take the weekend. Not because the SaaS is bad, but because the weekend is where the learning is.
So we built CryptoSync: a small, opinionated stack that backs up a folder to a Wasabi bucket, end-to-end encrypted, on multiple machines, with email alerts when it breaks. The scripts are on GitHub under MIT.
A note on what this post is. The full step-by-step setup โ Wasabi account, IAM policy, rclone configuration, Cryptomator vault creation, Resend API key, cron and Task Scheduler entries, troubleshooting โ lives in the README on the repository. It's the canonical reference and it gets updated when the scripts do. If you want the how-to, go there. This post is the why: the philosophy behind the choices, the non-obvious design decisions, and what I learned running the thing for real.
What "zero-knowledge" actually means here
The boring definition: the storage provider holds bytes, and only bytes. They cannot read your files even if they wanted to, even if they were compelled to, even if their own infrastructure were compromised. The keys never leave your machine.
The interesting question is where in the pipeline encryption happens. Most consumer "encrypted cloud" services encrypt in transit and sometimes at rest on their disks, but the keys live on their side. That's not zero-knowledge โ that's "trust us". Zero-knowledge means the ciphertext is produced before the data crosses the network, and the provider never holds the key material.
That single requirement is what shapes the whole stack:
flowchart TD
subgraph TRUST["๐ก๏ธ Your machine โ trust boundary"]
direction TB
A["๐ Your files<br/><i>plaintext, in the unlocked vault</i>"]
B["๐ Cryptomator vault<br/><i>encrypted folder on disk</i>"]
A -- "you edit via the<br/>mounted virtual drive" --> B
end
subgraph REMOTE["โ๏ธ Wasabi โ sees only ciphertext"]
C["๐๏ธ S3 bucket<br/><i>opaque encrypted blobs</i>"]
end
B <== "rclone bisync<br/>(bidirectional)" ==> C
classDef plain fill:#e8f4ff,stroke:#3b82f6,stroke-width:2px,color:#0f172a
classDef crypt fill:#fff4e0,stroke:#f59e0b,stroke-width:2px,color:#0f172a
classDef cloud fill:#e8f8ee,stroke:#10b981,stroke-width:2px,color:#0f172a
class A plain
class B crypt
class C cloud
style TRUST fill:#fafafa,stroke:#94a3b8,stroke-width:2px,stroke-dasharray:5 5
style REMOTE fill:#f0fdf4,stroke:#10b981,stroke-width:2px
The crucial detail โ and the one most people miss the first time โ is that the sync tool reads from the encrypted folder on disk, not from the unlocked virtual drive Cryptomator exposes. If you point rclone at the virtual drive, you'd be uploading plaintext that happens to come from a vault. By syncing the storage folder instead, the bucket only ever sees Cryptomator's ciphertext.
The three pieces, and why each one
I went through the usual shortlist before settling on this combination. Here is what stuck and why.
Wasabi for the storage
Wasabi is S3-compatible, has no egress fees, costs ~$7/month per TB, and ships with simple IAM policies. The "no egress fees" part matters more than it sounds: backup storage that charges you to read your own data back turns the worst day of your life (the restore) into an unexpected bill. Wasabi has a 90-day minimum object retention, which is fine for an archive and a non-issue for an active sync where files mostly stick around.
Cryptomator for the encryption
Cryptomator is open-source, audited, cross-platform, and uses file-by-file encryption (not a single monolithic blob). The unlocked vault behaves like a normal folder โ drag and drop, Finder/Explorer integration, the lot. Critically, it produces an encrypted folder on disk that is itself just a directory tree of opaque files. That folder is what we sync. No proprietary container format, no special API, no daemon to coordinate with.
rclone bisync for the transport
rclone would already be the obvious choice for "talk to S3 from a script". The reason I went with bisync specifically โ rather than the more common rclone sync (one-way) or rclone copy (additive) โ is that I wanted the bucket and the local folder to be peers, not master-and-mirror. A one-way mirror is great until you accidentally delete something locally and the next run obediently deletes it remotely too. With bisync, deletions still propagate (it is a sync), but the round-trip semantics make multi-device use possible without a server in the middle.
There is a tradeoff, which I'll come back to: bisync does not handle simultaneous edits to the same file on two machines. It detects the conflict and creates a .conflict copy rather than picking a winner. Read the log; nothing is ever silently lost. But this is a backup-and-roaming setup, not Google Docs.
The wrapper script: where the boring resilience lives
If this were just rclone bisync in a cron line, the README would be ten lines long. The reason there's a wrapper at all is that unsupervised sync jobs fail in dull, repeatable ways, and you only find out when you need the backup.
So the script โ Bash for macOS/Linux, PowerShell for Windows, same logic on both โ adds the things you only think about after something has gone wrong once:
- A lockfile with PID and stale-process detection. Cron will happily fire a second instance while the first is still uploading the initial 50 GB. The lockfile prevents that, and after a configurable timeout it assumes the previous run is stuck and force-kills it rather than blocking forever.
- A failure counter with an alert threshold. One transient network hiccup is not an emergency. Three failures in a row is. The script keeps a small state file, increments on failure, and only emails you once the counter hits
MAX_FAILS. - A recovery email. The alert is only useful if you also know when things are healthy again. After an alert, the first successful run sends an "all good" email and clears the marker.
- Log rotation. The log gets useful when something breaks, and useless when it grows to half a gigabyte. Rotation at 1 MB keeps it greppable.
- A pre-flight check. Is the local folder there? Is there an internet connection? If not, log it and bail out cleanly instead of producing a confusing rclone error.
The email transport is Resend, because their free tier is generous, the API is a single HTTP call, and I don't want a backup script to depend on a local SMTP daemon. If the API key is missing or Python isn't installed, email is silently skipped โ the sync itself still runs. Notifications are nice; they are not the critical path.
The unexpected upside: multi-device sync
The original goal was just "encrypted backup of one machine". Then I realised that because bisync is bidirectional and the Cryptomator vault is just a folder, two machines pointing at the same bucket would also end up sharing the same vault. The encrypted blobs in Wasabi become the source of truth, and each machine's local folder is a working copy.
I now run it on a MacBook (cron, every 15 minutes) and a Windows laptop (Task Scheduler, every 15 minutes, offset by a few minutes). It has handled the round-trip cleanly for months, including across simultaneous edits to different files. A few things I learned the hard way:
- Both devices need the same vault password. Different passwords means different ciphertexts for the same plaintext, and your bucket becomes a single shared graveyard of encrypted files neither machine can read in full.
- Stagger the schedules. Even a 2-minute offset between the two cron lines drastically reduces the chance of both machines hitting Wasabi at the exact same moment.
- Wait for the sync to finish before shutting down. The lockfile and log will tell you if it's still running. Pulling the plug mid-upload is recoverable, but it's the most likely way to produce a
.conflictcopy on the next run.
This isn't Dropbox. It's closer in spirit to "a Git repo for my working folder, encrypted, hosted on cheap object storage". And for that role, it has been remarkably stable.
What this is not, and the disclaimer that matters
I'll be direct about the tradeoffs, because anything else would be unfair.
This is not a one-click product. The first run takes 30โ45 minutes of setup, plus the time of the initial upload (which on a 50 GB vault over a home connection is several hours, and the script is not stuck โ tail -f the log). The full setup walkthrough is in the repository README; I'm deliberately not duplicating it here, because the README is the version that stays correct as the scripts evolve. If "configure rclone, write a Wasabi IAM policy, edit a Bash script" doesn't sound fun, this is the wrong tool. Use Backblaze, Arq, iDrive, or Tresorit โ they are good at what they do, and there is zero shame in clicking Sign in with Google when the data on the line is your wedding photos.
Lose the Cryptomator password and the recovery key, and the data is gone. Permanently. Nobody can recover it for you. That's the price of zero-knowledge: the provider being unable to read your data also means the provider being unable to help you when you can't read your data. Treat the recovery key like you'd treat a hardware wallet seed phrase.
The wrapper script is opinionated, not bulletproof. I run it on my own machines and it works for my workload. Read it before you trust it with anything important. The repo's disclaimer is not boilerplate โ it's the actual contract.
Why the journey is worth it
I could have stopped at "Backblaze, $9/month, done". I didn't, because the point was never just the backup. It was understanding โ at the level of what bytes go where, and who can read them โ what backup actually means. Once you've configured an IAM policy by hand, watched bisync resolve a conflict in a log file, and received a recovery email after a flaky DNS week, you stop thinking about cloud storage as magic. It becomes plumbing, and plumbing you can fix.
That's the MojaLab habit: take the long way once, so the short way feels different ever after.
The setup, end to end, is in the README on GitHub. It covers everything: creating the Wasabi bucket and IAM user, configuring rclone, the Cryptomator vault, the Resend API key, editing the script for your platform, the first --resync, scheduling via cron or Task Scheduler, log rotation, and troubleshooting. The repo itself is at github.com/doradame/CryptoSync, MIT-licensed. Clone it, read the script before you run it, and adapt it to your taste.
If you build something on top of it, or break something interesting, I'd love to hear about it.
Made in MojaLab. No AI was mistreated in the making of these scripts
