Back to Overview

Data-at-Rest Encryption

Integration credentials are encrypted at rest with AES-256-GCM. A dedicated DATA_ENCRYPTION_KEY decouples that protection from SECRET_KEY_BASE, so you can rotate your session secret without making every stored credential unreadable.

Luka Breitig — Software Engineer & AI Developer
Luka Breitig

Software Engineer & AI Developer

🔐 Why a Dedicated Key

Tymeslot encrypts sensitive integration credentials — CalDAV passwords, calendar OAuth tokens, video API keys, Slack and Telegram tokens, webhook secrets — before they are written to the database. By default the encryption key is derived from SECRET_KEY_BASE.

That works, but it welds your data-at-rest protection to your cookie-signing secret: rotating SECRET_KEY_BASE would make every stored credential undecryptable , forcing every user to reconnect every integration.

Setting a dedicated DATA_ENCRYPTION_KEY separates the two secrets. Your session secret becomes freely rotatable, and your at-rest credentials keep their own independent key.

🧩 How It Works

Every encrypted value is self-describing: it carries a one-byte version prefix identifying the key that produced it, so several keys can coexist and data can move from one key to another with no downtime.

Legacy values

Written before you set a dedicated key, encrypted under the SECRET_KEY_BASE-derived key. They keep opening — the legacy key stays available for decryption.

Current values

Written once DATA_ENCRYPTION_KEY is configured, encrypted under the dedicated key. This is what all new writes use.

Upgrading changes nothing until you opt in

When DATA_ENCRYPTION_KEY is absent, Tymeslot keeps reading and writing the legacy format exactly as before — so deploying this version with no configuration change is completely safe. Setting the key flips new writes to the dedicated key; a one-time sweep then migrates your existing values.

1 Enable the Dedicated Key

How you provide the key depends on how you deploy. Once set, it must stay stable forever — losing it makes stored credentials unrecoverable.

Cloudron — automatic

Nothing to do. On the first boot after upgrading, the container generates a key, persists it to /app/data/data_encryption_key (included in Cloudron backups), and starts writing new credentials under it. Then run the migration in step 2.

Docker — set it in your .env

Generate a key and add it to your environment file, then restart:

echo "DATA_ENCRYPTION_KEY=$(openssl rand -base64 48 | tr -d '\n')" >> .env

New values are written under the new key immediately. Existing values keep decrypting under the old key automatically — nothing breaks.

Source / other platforms

Provide DATA_ENCRYPTION_KEY as an environment variable through whatever mechanism your platform uses (Railway variables, a systemd unit, a secrets manager). The value must be Base64 that decodes to at least 32 bytes — openssl rand -base64 48 is a good source.

2 Migrate Existing Credentials

After the dedicated key is configured, existing credentials are still stored under the old key. A one-time sweep rewrites every encrypted value with the new key, so the two secrets become fully independent. The sweep is idempotent and resumable — safe to run repeatedly and to re-run after an interruption.

Cloudron

cloudron exec --app tymeslot.yourdomain.com -- sh -c \
  'export DATA_ENCRYPTION_KEY=$(cat /app/data/data_encryption_key); \
   /app/bin/tymeslot eval "Ecto.Migrator.with_repo(Tymeslot.Repo, fn _ -> IO.inspect(Tymeslot.Security.CredentialReencryption.run(), label: :reencryption) end)"'

Docker

docker exec tymeslot /app/bin/tymeslot eval \\\n  'Ecto.Migrator.with_repo(Tymeslot.Repo, fn _ -> IO.inspect(Tymeslot.Security.CredentialReencryption.run(), label: :reencryption) end)'

Source install

mix tymeslot.reencrypt_credentials

Each run reports per-table counts. Re-run until the reported migrated is 0 — that confirms every credential is on the dedicated key.

Values that can't be decrypted are left untouched

Any value that no available key can open (genuinely lost key, corruption) is reported as unrecoverable and left as-is. Those integrations follow the normal reconnection flow — the user is prompted to reconnect. The sweep never deletes or overwrites data it cannot read.

3 Store and Protect the Key

The key is now the single thing standing between an attacker and your stored credentials. Treat losing it as equivalent to losing every integration credential at once.

  • Keep one authoritative copy that never changes, plus at least one offline backup of the key itself in a password manager or secrets vault — separate from your server backups.
  • Never commit it to git, paste it into a ticket or chat, or log it.
  • Understand the backup trade-off. If the key lives in the same backup as your database (e.g. Cloudron backs up /app/data, which holds both), then at-rest encryption protects you against a leaked database — a stray SQL dump — but not against a compromised full backup. To defend against backup compromise, inject the key at runtime from a secrets manager that is not part of the database backup.

Two rules you must not break

  • Never delete the key (the /app/data/data_encryption_key file on Cloudron, or the env var elsewhere). Losing it makes stored credentials permanently unrecoverable.
  • Do not rotate SECRET_KEY_BASE until the migration in step 2 reports 0. Until then, some credentials still depend on the SECRET_KEY_BASE-derived key.

🔄 Rotating the Key Later

The versioned format is built for rotation, not just this one migration. A future key gets its own version id and coexists with the current one: new writes use the new key, old values keep opening under the previous key, and the same re-encryption sweep walks the data onto the new key. Only once the sweep reports 0 is the previous key retired. Nothing is ever wiped.

Do not rotate by simply changing the value

Swapping DATA_ENCRYPTION_KEY to a brand-new value in place would strand every credential already written under the old one — the old key would no longer be in the keyring to read them. True rotation keeps the old key available alongside the new one until the sweep finishes. Multi-key rotation is introduced through a release that registers the additional key; follow the upgrade notes for that version rather than editing the value by hand.

Frequently Asked Questions

Do I have to set DATA_ENCRYPTION_KEY?

No. It is optional and recommended. Without it, Tymeslot works exactly as it always has — credentials are encrypted with a key derived from SECRET_KEY_BASE. You only need it if you want to rotate SECRET_KEY_BASE without breaking stored credentials, or want an independent key for defence in depth.

I upgraded and see a warning that DATA_ENCRYPTION_KEY is not set. Is anything broken?

Nothing is broken. It is an advisory reminder that your at-rest credentials are still keyed off SECRET_KEY_BASE, so rotating that secret would make them undecryptable. Set DATA_ENCRYPTION_KEY and run the migration to clear it.

What happens if I lose the key?

Every credential written under it becomes permanently unreadable, and each affected integration will prompt the user to reconnect. There is no recovery — this is why an offline backup of the key, stored separately from your database backups, is essential.

How do I know the migration is complete?

Re-run the sweep until the reported migrated count is 0. A run that migrates nothing means every credential is already on the dedicated key. Only then is it safe to rotate SECRET_KEY_BASE.

Is it safe to run the migration while the app is serving traffic?

Yes. The sweep migrates each value in place using a compare-and-swap, so a credential that another process refreshes mid-sweep is never clobbered, and it is fully idempotent — re-running after any interruption simply continues where it left off.

Verify Your Setup

Confirm each of the following before rotating SECRET_KEY_BASE:
  • The key is configured. The boot warning about DATA_ENCRYPTION_KEY no longer appears in the logs.
  • An offline backup of the key exists in a password manager or secrets vault, separate from your database backups.
  • The migration reports 0. The re-encryption sweep migrates nothing on its most recent run.
  • You have not deleted the key and do not plan to change its value by hand.

🔗 Related Articles

Read reCAPTCHA Bot Protection

reCAPTCHA Bot Protection

Protect Tymeslot booking forms with Google reCAPTCHA v3 — invisible bot detection with score-based thresholds and no CAPTCHA friction for legitimate users.

Read Docker Self-Hosting

Docker Self-Hosting

Deploy Tymeslot with Docker Compose on any VPS or home server. Includes reverse proxy setup, file upload volumes, and WebSocket configuration for LiveView.

Read Cloudron Deployment

Cloudron Deployment

Install Tymeslot on Cloudron in minutes. PostgreSQL, SSL certificates, email relay, and automatic updates are all provisioned and managed for you.