# How TREK Handles Encryption Key Rotation for Production Deployments

> Learn how TREK enables zero-downtime encryption key rotation for production with its Node.js migration script. It safely decrypts and re-encrypts secrets, ensuring secure data without service interruption.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: how-to-guide
- Published: 2026-07-04

---

**TREK handles encryption key rotation through a Node.js migration script ([`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts)) that decrypts all AES-256-GCM protected secrets with the old key and re-encrypts them with a new key while creating a timestamped database backup, allowing production operators to rotate keys without downtime.**

TREK, an open-source travel management application maintained in the `mauriceboe/TREK` repository, protects sensitive configuration values—such as API keys, OAuth secrets, and MFA TOTP seeds—using AES-256-GCM encryption. Understanding how to perform encryption key rotation in production deployments is critical for maintaining security compliance, as the system relies on a 32-byte hex key derived from the `ENCRYPTION_KEY` environment variable or persistent file storage. The encryption architecture is designed to support zero-downtime rotation workflows that re-encrypt data in place without affecting unencrypted user records.

## Understanding TREK's Encryption Key Resolution

Before rotating keys, you must understand how TREK resolves the active encryption key at startup. According to the source code in `mauriceboe/TREK`, the system checks four locations in strict precedence order:

### Production Key Precedence Order

1. **`ENCRYPTION_KEY` environment variable** – The highest priority source. When set, TREK also persists this value to `./data/.encryption_key` to ensure container restarts survive the loss of the environment variable.
2. **`./data/.encryption_key` file** – Used when the environment variable is absent. Created automatically on first start if no other key source exists.
3. **`./data/.jwt_secret` file** – A legacy fallback for pre-v2 installations. On startup, this value is copied to `.encryption_key` and future JWT rotations no longer impact encryption.
4. **Auto-generated key** – On fresh installations with no key source, TREK generates a random 32-byte hex key and writes it to `./data/.encryption_key`.

## How Encryption Key Rotation Works in TREK

TREK provides a dedicated migration utility rather than manual database edits. The script at [`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts) performs an atomic re-encryption of every stored secret by decrypting with the old key and encrypting with the new key.

### The Migration Script Process

When executed, the script performs the following operations:

- Prompts for the **old** `ENCRYPTION_KEY` value (hidden input)
- Prompts for the **new** `ENCRYPTION_KEY` value (hidden input)
- Requires manual confirmation before proceeding
- Creates a timestamped backup of the SQLite database named `travel.db.backup-<epoch>`
- Iterates through all encrypted tables and re-encrypts values in place
- Reports counts of migrated, skipped, already-migrated, and errored entries
- Aborts safely if any value cannot be decrypted, leaving the original database backup untouched

## Performing a Zero-Downtime Key Rotation

Use the following workflow to rotate keys in production without service interruption:

### 1. Generate a New Encryption Key

Create a cryptographically secure 32-byte hex key:

```bash
export NEW_ENCRYPTION_KEY=$(openssl rand -hex 32)
echo "New key generated: $NEW_ENCRYPTION_KEY"

```

### 2. Execute the Migration Script

Run the migration from the host or inside a Docker container:

**Host execution:**

```bash
cd server
node --import tsx scripts/migrate-encryption.ts

```

**Docker execution:**

```bash
docker exec -it trek node --import tsx scripts/migrate-encryption.ts

```

The script will interactively prompt for the old key, new key, and confirmation. You can also pipe inputs for automation:

```bash
node --import tsx scripts/migrate-encryption.ts <<EOF
$OLD_ENCRYPTION_KEY
$NEW_ENCRYPTION_KEY
yes
EOF

```

### 3. Update the Runtime Environment

Persist the new key for subsequent restarts:

```bash

# Option A: Environment variable (recommended for containers)

export ENCRYPTION_KEY=$NEW_ENCRYPTION_KEY

# Option B: Persistent file

echo "$NEW_ENCRYPTION_KEY" > ./data/.encryption_key

```

### 4. Restart TREK

Restart the service to load the new key:

```bash
docker restart trek

```

## Backup and Recovery Considerations

The regular backup ZIP created by TREK **does not** contain the encryption key. Production operators must store the key separately in a password manager or secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager) and synchronize it with backup restoration procedures.

If the encryption key is lost entirely, all encrypted settings become unreadable. TREK cannot decrypt them, and you must manually re-enter those secrets via the admin UI. Unencrypted data—including trips, users, and places—remains unaffected.

## Summary

- **Key resolution** follows a strict four-tier order: environment variable → `./data/.encryption_key` → legacy `./data/.jwt_secret` → auto-generation.
- **Zero-downtime rotation** is achieved via [`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts), which creates a database backup before re-encrypting all secrets.
- **Backup isolation** requires separate storage of the encryption key outside the TREK backup ZIP.
- **Legacy migration** automatically upgrades old installations using `.jwt_secret` to the modern `.encryption_key` system on startup.

## Frequently Asked Questions

### Where does TREK store the production encryption key?

TREK stores the active encryption key in the `ENCRYPTION_KEY` environment variable, which takes precedence over all other sources. If the environment variable is not set, TREK falls back to the `./data/.encryption_key` file. When the environment variable is present, TREK writes its value to this file to ensure persistence across container restarts.

### What happens if I lose the encryption key?

If the encryption key is lost, TREK cannot decrypt any AES-256-GCM protected values, including API keys and OAuth secrets. You must re-enter these sensitive configuration values manually through the admin UI. Unencrypted database records—such as user accounts, trip data, and location information—remain fully accessible and unaffected by key loss.

### Does TREK support zero-downtime encryption key rotation?

Yes. TREK supports zero-downtime rotation through the [`server/scripts/migrate-encryption.ts`](https://github.com/mauriceboe/TREK/blob/main/server/scripts/migrate-encryption.ts) migration script. The script re-encrypts all stored secrets in place while the application is running, creates a timestamped backup of the SQLite database, and only requires a standard service restart to activate the new key after migration completes.

### How do I rotate the encryption key when running TREK in Docker?

Execute the migration script inside the running container using `docker exec -it trek node --import tsx scripts/migrate-encryption.ts`, then update the `ENCRYPTION_KEY` environment variable in your [`docker-compose.yml`](https://github.com/mauriceboe/TREK/blob/main/docker-compose.yml) or orchestration platform, and finally restart the container with `docker restart trek`. The script handles the re-encryption atomically before the new key is activated.