Source: https://wealthfolio.app/blog/database-encryption-keys-backups-and-recovery/

Engineering

# Encrypting Wealthfolio: Keys, Backups, and Recovery

What it takes to encrypt a local SQLite database without making it harder to own your data. The keys, backups, temporary files, and recovery work behind a simple setting.

September 30, 2026

![A hand-drawn database beside a closed padlock, with stippled ink shading and one orange keyhole](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/database-encryption-hero-light.svg)![A hand-drawn database beside a closed padlock, with stippled ink shading and one orange keyhole](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/database-encryption-hero-dark.svg)

Wealthfolio keeps your financial data on your own machine. [Local-first and the SQLite Bet](https://wealthfolio.app/blog/local-first-and-the-sqlite-bet/) explains what that decision buys you: a file you hold, an app that works offline, and no portfolio database on our servers.

But a local file still needs protection. By itself, an ordinary SQLite database is readable by whoever can open it. Account names, transactions, balances. The fact that it never went to our server doesn’t change that.

So Wealthfolio has optional database encryption. There is a setting in the app. You enable it, the database gets encrypted, and you carry on using it.

That small interaction has quite a lot of work underneath it. The file needs to be protected, and it needs to remain yours when you change computers, restore a backup, or turn the setting off again. Those requirements reach into almost every part of the database’s life.

## Keeping SQLite

We use [SQLCipher](https://www.zetetic.net/sqlcipher/design/), which encrypts database pages as they go to disk and decrypts them as they are read. The app still queries SQLite. Holdings, performance calculations, imports, all the repositories that already existed keep doing their jobs.

Wealthfolio’s Rust storage layer uses both Diesel and rusqlite. Both have to use the same SQLCipher-enabled SQLite library, and every connection has to receive its key before it tries to read the database. That includes the read pool, the write actor, migrations, and the less visible connections opened for backups.

Missing one of those paths is enough to have an app that opens normally and then fails when you try to export. Encryption has to follow the data through the whole app.

The same build also opens ordinary, unencrypted SQLite files. Existing users can keep their current setup. Encryption is a choice you make for each profile.

![Reads, writes, and backups all use shared database access. The native credential store supplies a profile key to that access layer, SQLCipher applies it on each connection, and the encrypted database is stored on disk.](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/database-access-light.svg)![Reads, writes, and backups all use shared database access. The native credential store supplies a profile key to that access layer, SQLCipher applies it on each connection, and the encrypted database is stored on disk.](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/database-access-dark.svg)

Native apps keep the key in the device’s credential store. Every database connection goes through the same keyed access layer.

## The key has to outlive the setting

On native apps, the database key is randomly generated and saved through the operating system’s credential store. You don’t type it every time you open Wealthfolio. The app retrieves it when it needs to open the profile.

Before encrypting anything, we save the key and read it back. A credential backend accepting a write is not enough. If the key isn’t there when we look for it, conversion stops while the original database is still untouched.

That ordering matters. Discovering a storage problem before encryption costs someone an error message. Discovering it afterward can cost them access to their portfolio.

Startup follows a different rule: look for an existing key, never quietly make a replacement. A new key cannot open an old encrypted database. If a key or file is missing, the app needs to explain the problem and offer recovery, rather than make a fresh database and act as though nothing happened.

Then there is the less obvious case. You enable encryption, make a few backups, and later disable encryption. What happens to the key?

It stays. The live database may now be unencrypted, but those older backups still need the key they were created with. Deleting it because a toggle changed would leave you with a folder of backups you could no longer read.

A setting describes what you want now. Your backups carry the history of what you wanted before.

Self-hosted installations have a different key source: the database key is derived from the operator’s master secret. That secret needs its own backup, separate from the data. Changing it casually is enough to lose access to things it protected. The [self-hosting guide](https://wealthfolio.app/docs/guide/self-hosting/configuration/#wf_db_require_encryption) covers how to enable encryption with the server stopped.

## Changing the file while the app is alive

By the time you click the encryption setting, the database is already busy. The UI is reading it. Background work may be calculating valuations. The write actor has a connection open. Device sync may be applying changes.

All of that has to stop before the file can be replaced. Cancellation doesn’t mean the work has finished or the file handle has gone away. The runtime stops background work and waits for every database connection to close, while keeping an ownership lock in place through the replacement.

With the database quiet, we build an encrypted replacement and verify it before touching the live file. We make a recovery snapshot, check space for recovery, then install the replacement and check that it opens. If verification fails, the maintenance path attempts to put the previous state back. If recovery cannot be confirmed, it keeps an identifiable snapshot instead of cleaning up the only remaining copy.

When enabling encryption, the recovery snapshot is made from the verified encrypted candidate. It contains the original data, already protected by the stored key. The conversion doesn’t need to leave an extra plaintext recovery copy behind after it succeeds.

![Enabling encryption first saves and reads back the key, stops database users, builds and verifies an encrypted candidate, and saves an encrypted recovery snapshot. It then installs and checks the candidate. Success reopens services; a failed check triggers rollback, with a recovery snapshot retained if recovery cannot be confirmed.](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/safe-conversion-light.svg)![Enabling encryption first saves and reads back the key, stops database users, builds and verifies an encrypted candidate, and saves an encrypted recovery snapshot. It then installs and checks the candidate. Success reopens services; a failed check triggers rollback, with a recovery snapshot retained if recovery cannot be confirmed.](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/safe-conversion-dark.svg)

The live file stays in place while the replacement and recovery snapshot are prepared. Enabling encryption, disabling it, and restoring share the same maintenance path.

Desktop restarts after completion. Mobile rebuilds its services and refreshes the UI. Self-hosted servers do conversion offline. Different ways of getting the database quiet, with the same storage work underneath.

## A backup you can take with you

A saved snapshot and a portable backup have different jobs.

A snapshot is a consistent copy for rolling back inside the current installation. It keeps the encryption it had when it was created. If the database was encrypted, the snapshot needs that installation’s key.

Now imagine your laptop is gone. You have the snapshot on another drive, but the key lived in the laptop’s credential store. Having the file alone isn’t enough.

That is why portable exports have their own password. A protected `.wfbackup` can be opened on another installation with the password you chose at export. It doesn’t depend on the source device’s key.

The export also removes things that belong to the old installation: access tokens, broker associations, device-sync state, and other installation-specific data. Your portfolio comes with you. External services need reconnecting.

Restore first checks a private copy of the selected backup, which keeps the source file’s encryption state. It then prepares an encrypted working database, migrates supported older schemas, and checks integrity, schema, migration history, and foreign keys before offering the replacement for confirmation. The confirmed restore consumes that prepared copy.

The destination keeps its own encryption policy. Restoring an unencrypted backup into an encrypted profile leaves the profile encrypted under its own key. The source file doesn’t get to decide how your current installation stores data.

![A source snapshot uses its original installation key. Portable export creates a sanitized wfbackup protected by a separate backup password. Restore validates it and installs the portfolio using the destination profile's own key and encryption policy.](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/portable-backup-light.svg)![A source snapshot uses its original installation key. Portable export creates a sanitized wfbackup protected by a separate backup password. Restore validates it and installs the portfolio using the destination profile's own key and encryption policy.](https://assets.wealthfolio.app/images/blog/database-encryption-keys-backups-and-recovery/portable-backup-dark.svg)

Three separate secrets: the source installation key, the portable backup password, and the destination installation key. The password opens the transfer file; the destination policy determines storage after restore.

You can still deliberately export an unencrypted `.db` for use with other SQLite tools. Protection is the default in the export dialog, but the readable format remains available. Owning your data includes being able to take it somewhere else.

The password needs to travel with your recovery plan, somewhere you can find it when you need it. Wealthfolio cannot reset it. Without an accessible key or a portable backup whose password you know, we cannot unlock your encrypted data for you.

## The files you don’t see

The database has a life beyond `app.db`. It has journals, a write-ahead log, and temporary working storage. Export and restore also create copies while they prepare the result.

SQLCipher encrypts database page data in the write-ahead log and rollback journals. But its [design documentation](https://www.zetetic.net/sqlcipher/design/#database-encryption-and-temporary-files) also explains that some other temporary files can remain unencrypted. Those paths need attention too.

For encrypted databases, we keep SQLite’s temporary store in memory. Portable export preparation and the prepared restore candidate use encrypted working databases. Otherwise you could protect the main file and still write readable portfolio data to a scratch file during maintenance.

That doesn’t make every file encrypted. Importing an unencrypted `.db` first creates a plaintext staging copy. Choosing an unencrypted export or disabling database encryption also deliberately produces plaintext database files.

There is a cost. Large migrations can use more memory. Plaintext desktop and server databases can use disk for temporary migration work; encrypted ones keep the memory setting. Mobile retains its existing memory behavior. It is one of those choices where the privacy requirement affects how much working room the app needs.

Turning on encryption also cannot reach backward through every copy you’ve ever made. An old plaintext snapshot stays plaintext. So does a CSV you exported last month. So does a copy sitting in a drive backup. The setting changes the live database. Older copies need their own attention.

## What the setting protects

App lock, database encryption, and Connect’s encrypted device sync protect different things. App lock gates access through the interface. Database encryption protects the stored database when its key is unavailable. Device sync protects the data exchanged between devices. Turning on one doesn’t quietly turn on the others.

The running app still needs to read your portfolio. Database encryption doesn’t make a compromised, unlocked device safe, and it doesn’t make a spreadsheet export private. Its scope needs to be clear enough that people can make useful decisions with it.

You should be able to protect your financial records, back them up, move them to a new machine, and understand what you need to keep for recovery. Without having to become a database administrator first.

Getting there means paying attention to the boring moments. A key being saved. A connection closing. A backup made years ago. A file being replaced while the app is waiting to reopen it.

That is where the promise of owning your data has to hold up.

For the actual steps, the [Export & Backup guide](https://wealthfolio.app/docs/guide/data-export/#database-encryption-and-backup-passwords) covers database encryption, portable backups, and restore. The [implementation notes](https://github.com/wealthfolio/wealthfolio/blob/main/docs/architecture/database-encryption-and-backups.md) go deeper into the storage and recovery paths.

### Tags

Privacy

SQLite

Encryption

Backups

Architecture

[← Back to The Blog](https://wealthfolio.app/blog/)

Published September 30, 2026

## Related posts

-   Engineering
    
    ### [Local-first and the SQLite Bet](https://wealthfolio.app/blog/local-first-and-the-sqlite-bet/)
    
    Why Wealthfolio's portfolio lives in a SQLite file on your machine instead of a cloud database, and what that decision cost us to make work.
    
    May 20, 2026
    
-   Engineering
    
    ### [Bring Your Own Price Feed](https://wealthfolio.app/blog/custom-market-data-providers-no-code/)
    
    Wealthfolio lets you point it at any URL that returns a price and describe how to read it, so the mutual funds and regional listings the big providers ignore still get tracked. No code, no waiting for an integration.
    
    Jun 26, 2026
    
-   Product
    
    ### [AI in Wealthfolio](https://wealthfolio.app/blog/ai-in-wealthfolio/)
    
    Ask questions about your portfolio, review suggested spending categories, or connect your own AI agent through MCP. How AI works in Wealthfolio, what it can change, and where your data goes.
    
    Sep 16, 2026
