| .cargo | ||
| .github/workflows | ||
| sql | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| README.md | ||
| wrangler.toml | ||
Warden: A Bitwarden-compatible server for Cloudflare Workers
This project provides a self-hosted, Bitwarden-compatible server that can be deployed to Cloudflare Workers for free. It's designed to be low-maintenance, allowing you to "deploy and forget" without worrying about server management or recurring costs.
Why another Bitwarden server?
While projects like Vaultwarden provide excellent self-hosted solutions, they still require you to manage a server or VPS. This can be a hassle, and if you forget to pay for your server, you could lose access to your passwords.
Warden aims to solve this problem by leveraging the Cloudflare Workers ecosystem. By deploying Warden to a Cloudflare Worker and using Cloudflare D1 for storage, you can have a completely free, serverless, and low-maintenance Bitwarden server.
Features
- Core Vault Functionality: All your basic vault operations are supported, including creating, reading, updating, and deleting ciphers and folders.
- TOTP Support: Store and generate Time-based One-Time Passwords for your accounts.
- Bitwarden Compatible: Works with the official Bitwarden browser extensions and Android app (iOS is untested).
- Free to Host: Runs on Cloudflare's free tier.
- Low Maintenance: Deploy it once and forget about it.
- Secure: Your data is stored in your own Cloudflare D1 database.
- Easy to Deploy: Get up and running in minutes with the Wrangler CLI.
Current Status
This project is not yet feature-complete. It currently supports the core functionality of a personal vault, including TOTP. However, it does not support the following features:
- Sharing
- Bitwarden Send
- Organizations
- Other Bitwarden advanced features
There are no immediate plans to implement these features. The primary goal of this project is to provide a simple, free, and low-maintenance personal password manager.
Compatibility
- Browser Extensions: Chrome, Firefox, Safari, etc.
- Android App: The official Bitwarden Android app.
- iOS App: Untested. If you have an iOS device, please test and report your findings!
Getting Started
Prerequisites
- A Cloudflare account.
- The Wrangler CLI installed and configured.
Deployment
-
Clone the repository:
git clone https://github.com/your-username/warden-worker.git cd warden-worker -
Create a D1 Database:
wrangler d1 create warden-db -
Configure your Database ID:
When you create a D1 database, Wrangler will output the
database_id. To avoid committing this secret to your repository, this project uses an environment variable to configure the database ID.You have two options:
Option 1: (Recommended) Use a
.envfile:Create a file named
.envin the root of the project and add the following line, replacing the placeholder with your actualdatabase_id:D1_DATABASE_ID="your-database-id-goes-here"Make sure to add the
.envfile to your.gitignorefile to prevent it from being committed to git.Option 2: Set an environment variable in your shell:
You can set the environment variable in your shell before deploying:
export D1_DATABASE_ID="your-database-id-goes-here" wrangler deploy -
Deploy the worker:
wrangler deployThis will deploy the worker and set up the necessary database tables.
-
Set environment variables
ALLOWED_EMAILSyour-email@example.comJWT_SECRETa long random stringJWT_REFRESH_SECRETa long random string
-
Configure your Bitwarden client:
In your Bitwarden client, go to the self-hosted login screen and enter the URL of your deployed worker (e.g.,
https://warden-worker.your-username.workers.dev).
Configuration
This project requires minimal configuration. The main configuration is done in the wrangler.toml file, where you specify your D1 database binding.
Other Environment Variables
You can configure the following environment variables in wrangler.toml under the [vars] section, or set them via Cloudflare Dashboard:
-
TRASH_AUTO_DELETE_DAYS(Optional, Default:30)Number of days to keep soft-deleted items before automatically purging them. When a cipher is deleted, it's marked with a
deleted_attimestamp (soft delete). After the specified number of days, the item will be permanently removed from the database.- Set to
0or a negative value to disable automatic purging - Defaults to
30days if not specified - Example:
TRASH_AUTO_DELETE_DAYS = "7"to keep deleted items for 7 days
- Set to
-
IMPORT_BATCH_SIZE(Optional, Default:30)Number of records to process in each batch when importing data. This helps manage memory usage and processing time for large imports.
- Set to
0to disable batching (all records imported in a single batch) - Defaults to
30records per batch if not specified - Example:
IMPORT_BATCH_SIZE = "50"to process 50 records per batch
- Set to
Scheduled Tasks (Cron)
The worker includes a scheduled task that runs automatically to clean up soft-deleted items. By default, this task runs daily at 03:00 UTC.
- Automatic Cleanup: The scheduled task automatically purges ciphers that have been soft-deleted for longer than the
TRASH_AUTO_DELETE_DAYSperiod - Schedule: Configured in
wrangler.tomlunder[triggers]section with cron expression"0 3 * * *"(daily at 03:00 UTC)
You can modify the cron schedule in wrangler.toml if you want to run the cleanup task at a different time or frequency. See Cloudflare Cron Triggers documentation for cron expression syntax.
Database Backup (GitHub Actions)
This project includes a GitHub Action workflow that automatically backs up your D1 database to S3-compatible storage daily. The backup runs at 04:00 UTC (1 hour after the cleanup task).
⚠️ Important Notes:
- Manual trigger required for first run: You must manually trigger the Action once (GitHub Actions → Backup D1 Database to S3 → Run workflow) before scheduled backups will run automatically.
- Ensure your S3 bucket is set to private access to prevent data leaks and avoid unnecessary public traffic costs.
- ⚠️ CRITICAL: Do NOT use R2 from the same Cloudflare account as your Worker for backups. If your Cloudflare account gets suspended or banned, you will lose access to both your Worker and your backup storage, resulting in complete data loss. Always use a separate Cloudflare account or a different S3-compatible storage provider (AWS S3, Backblaze B2, MinIO, etc.) for backups to ensure redundancy and disaster recovery.
Required Secrets for Backup
Add the following secrets to your GitHub repository (Settings > Secrets and variables > Actions):
| Secret | Required | Description |
|---|---|---|
S3_ACCESS_KEY_ID |
yes | Your S3 access key ID |
S3_SECRET_ACCESS_KEY |
yes | Your S3 secret access key |
S3_BUCKET |
yes | The S3 bucket name for storing backups |
S3_REGION |
yes | The S3 region (e.g., us-east-1). If unsure, use auto |
S3_ENDPOINT |
no | Custom S3 endpoint URL. Defaults to AWS S3 if not set. Required for S3-compatible services (MinIO, Cloudflare R2, Backblaze B2, etc.) |
BACKUP_ENCRYPTION_KEY |
no | Optional encryption passphrase. If set, backups will be encrypted with AES-256. Strongly recommended since the database contains unencrypted user metadata (emails, item counts) |
Backup Features
- Automatic Daily Backups: Production database is backed up daily at 04:00 UTC
- Manual Trigger: You can manually trigger a backup from the GitHub Actions tab
- Environment Selection: When triggering manually, you can choose to backup either
productionordevdatabase - Compression: Backups are compressed using gzip to save storage space
- Optional Encryption: If
BACKUP_ENCRYPTION_KEYis set, backups are encrypted with AES-256-CBC (PBKDF2 key derivation, 100k iterations) - Automatic Cleanup: Old backups older than 30 days are automatically deleted
- S3-Compatible: Works with AWS S3, Cloudflare R2, MinIO, Backblaze B2, and any S3-compatible storage
Backup File Location
Backups are stored in your S3 bucket with the following structure:
# Unencrypted backups
s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz
# Encrypted backups (when BACKUP_ENCRYPTION_KEY is set)
s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc
Decrypting Backups
If you enabled encryption, use the following command to decrypt a backup:
openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \
-in vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc \
-out backup.sql.gz \
-pass pass:"YOUR_ENCRYPTION_KEY"
# Then decompress
gunzip backup.sql.gz
Restoring Database to Cloudflare D1
To restore your D1 database from a backup:
-
Download the backup from S3:
# Using AWS CLI aws s3 cp s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc ./ # Or with custom endpoint (e.g., R2, MinIO) aws s3 cp s3://your-bucket/warden-worker/production/vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc ./ \ --endpoint-url https://your-s3-endpoint.com -
Decrypt the backup (if encrypted):
openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 \ -in vault1_prod_YYYY-MM-DD_HH-MM-SS.sql.gz.enc \ -out backup.sql.gz \ -pass pass:"YOUR_ENCRYPTION_KEY" -
Decompress the backup:
gunzip backup.sql.gz -
Restore to Cloudflare D1:
First, find your database name using wrangler:
wrangler d1 listThis will show a table with your databases. Look for the
namecolumn (e.g.,warden-dbfor production orwarden-devfor dev).Then restore the backup:
# Replace DATABASE_NAME with your actual database name (e.g., warden-db) # First, you may want to clear the existing database (optional, use with caution!) # wrangler d1 execute DATABASE_NAME --remote --command "DELETE FROM ciphers; DELETE FROM folders; DELETE FROM users;" # Import the backup wrangler d1 execute DATABASE_NAME --remote --file=backup.sqlNote: The
--remoteflag is required to execute against your production D1 database. Without it, the command will run against the local development database.
D1 Time Travel (Point-in-Time Recovery)
Cloudflare D1 provides a built-in Time Travel feature that allows you to restore your database to any point within the last 30 days. This is useful for undoing accidental data modifications or deletions without needing a backup.
To use Time Travel:
-
Check current restore bookmark:
# Replace DATABASE_NAME with your actual database name (e.g., warden-db) wrangler d1 time-travel info DATABASE_NAME -
Restore to a specific timestamp:
# Restore to a specific point in time (ISO 8601 format) wrangler d1 time-travel restore DATABASE_NAME --timestamp=2024-01-15T12:00:00Z # Or restore to a specific bookmark wrangler d1 time-travel restore DATABASE_NAME --bookmark=<bookmark_id>
Note: Time Travel retains data for 30 days on the free tier. See Cloudflare D1 Time Travel documentation for more details.
Local Development with D1
You can run this Worker locally with full D1 database support using Wrangler. This is useful for development, testing, or as a temporary fallback when Cloudflare services are unavailable.
To run locally with your production data (useful as emergency fallback):
-
Download and decrypt your backup (follow the steps above)
-
Import the backup to local D1:
# Without --remote flag, this imports to local database wrangler d1 execute vault1 --file=backup.sql -
Start the local server with persistence:
wrangler dev --persist -
Configure your Bitwarden client to use
http://localhost:8787(or your local network IP for mobile devices)
Accessing Local SQLite Database Directly
The local D1 database is stored as a SQLite file. You can access it directly:
# Find the database file
ls .wrangler/state/v3/d1/
# Open with SQLite CLI
sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite
# Example: List all users
sqlite> SELECT email FROM users;
Note: The local development environment requires Node.js and Wrangler installed. The Worker runs in a simulated environment using workerd, Cloudflare's open-source Workers runtime.
Contributing
Contributions are welcome! If you find a bug, have a feature request, or want to improve the code, please open an issue or submit a pull request.
License
This project is licensed under the MIT License. See the LICENSE file for details.