A Bitwarden-compatible server for Cloudflare Workers
See the codeThis 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.
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.
Warden supports file attachments using either Cloudflare KV or Cloudflare R2 as the storage backend:
| Feature | KV | R2 |
|---|---|---|
| Max file size | 25 MB (hard limit) | 100 MB (By request body size limit of Workers) |
| Credit card required | No | Yes |
| Streaming I/O | Yes | Yes |
Backend selection: R2 takes priority — if R2 is configured, it will be used. Otherwise, KV is used.
See the deployment guide for setup details. R2 may incur additional costs; see Cloudflare R2 pricing.
[!NOTE] Due to the D1 single-row size limit of 2 MB, the maximum text Send size is approximately 1.8 MiB. Additionally, the
/api/syncendpoint serializes all of the current user's Sends into the response. A large number of Sends or very large text Sends will significantly increase CPU time and response size.
This project is not yet feature-complete, and it may never be. It currently supports the core functionality of a personal vault, including TOTP. However, it does not support the following 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.
A demo instance is available at warden.qqnt.de.
You can register a new account using an email ending with @warden-worker.demo (The email does not need verification).
If you decide to stop using the demo instance, please delete your account to make space for others.
It's highly recommended to deploy your own instance since the demo can hit the rate limit and be disabled by Cloudflare.
The frontend is bundled with the Worker using Cloudflare Workers Static Assets. The GitHub Actions workflows download a pinned bw_web_builds (Vaultwarden web vault) release (default: v2026.6.4) and deploy it together with the backend. You can override it via GitHub Actions Variables (BW_WEB_VERSION for prod, BW_WEB_VERSION_DEV for dev), or set it to latest to follow upstream.
How it works:
/api/*, /identity/*) are routed to the Rust Worker.UI overrides (optional):
public/css/.bw_web_builds:
mkdir -p public/web-vault/css/ && cp public/css/vaultwarden.css public/web-vault/css/[!NOTE] Migrating from separate frontend deployment? If you previously deployed the frontend separately to Cloudflare Pages, you can delete the
warden-frontendPages project and re-setup the router for the worker. The frontend is now bundled with the Worker and no longer requires a separate deployment.
[!WARNING] The web vault frontend comes from Vaultwarden and therefore exposes many advanced UI features, but most of them are non-functional. See Current Status.
The default *.workers.dev domain is disabled by default, since it may throw 1101 error. You can enable it by setting workers_dev = true in wrangler.toml.
If you want to use a custom domain instead of the default *.workers.dev domain, follow these steps:
example.com)A (or AAAA for IPv6)vault for vault.example.com)192.0.2.1 (this is a placeholder, the actual routing is handled by Worker)[!IMPORTANT] The Proxy status must be "Proxied" (orange cloud). If it shows "DNS only" (gray cloud), Worker routes will not work.
warden-workervault.example.com/* (replace with your domain)warden-workerThis project includes rate limiting powered by Cloudflare's Rate Limiting API. Sensitive endpoints are protected:
| Endpoint | Rate Limit | Key Type | Purpose |
|---|---|---|---|
/identity/connect/token (password grant) | 5 req/min per email + 5 req/min per IP | Email + IP address | Prevent password brute force and credential stuffing |
/identity/connect/token (send access) | 15 req/min | IP address | Prevent Send password brute force |
/api/sends/access/* (password protected) | 10 req/min | IP address | Prevent Send password brute force |
/api/accounts/register | 5 req/min | IP address | Prevent mass registration & email enumeration |
/api/accounts/prelogin | 5 req/min | IP address | Prevent email enumeration |
You can adjust the rate limit settings in wrangler.toml:
[[ratelimits]]
name = "LOGIN_RATE_LIMITER"
namespace_id = "1001"
# Adjust limit (requests) and period (10 or 60 seconds)
simple = { limit = 5, period = 60 }
[[ratelimits]]
name = "SEND_ACCESS_RATE_LIMITER"
namespace_id = "1003"
# Public Send password checks use a slightly wider IP limit.
simple = { limit = 10, period = 60 }
[!NOTE] The
periodmust be either10or60seconds. See Cloudflare documentation for details.
If the binding is missing, requests proceed without rate limiting (graceful degradation).
Cloudflare Workers Free plan has a very small per-request CPU budget. Two kinds of endpoints are particularly CPU-heavy:
To keep the main Worker fast while still supporting these operations, Warden can offload selected endpoints to Durable Objects (DO):
HEAVY_DO): implemented in Rust as HeavyDo (reuses the existing axum router) so CPU-heavy endpoints can run with a higher CPU budget.How to enable/disable
Whether CPU-heavy endpoints are offloaded is determined by whether the HEAVY_DO Durable Object binding is configured in wrangler.toml.
[!NOTE] Durable Objects have much higher CPU budget of 30 seconds per request in free plan(see Cloudflare Durable Objects limits), so we can use it to offload the CPU-heavy endpoints.
Durable Objects can incur two types of billing: compute and storage. Storage is not used in this project, and the free plan allows 100,000 requests and 13,000 GB-s duration per day, which should be more than enough for most users. See Cloudflare Durable Objects pricing for details.
If you choose to disable Durable Objects, you may need subscribe to a paid plan to avoid being throttled by Cloudflare.
Warden supports live sync for vault data via two mechanisms: WebSocket push (for desktop apps and browser extensions) and Mobile push notifications (for official mobile apps).
WebSocket Push (Desktop & Extensions)
This feature is powered by Durable Objects and enabled by default when the NOTIFY_DO Durable Object binding is configured in wrangler.toml. Removing this binding (and migration) will gracefully disable WebSocket notifications.
Mobile Push Notifications
Warden supports push notifications to official Bitwarden mobile apps via the Bitwarden push relay service.
Setup:
PUSH_INSTALLATION_ID & PUSH_INSTALLATION_KEY) via the Cloudflare dashboard or wrangler cli.PUSH_ENABLED to true in wrangler.toml [vars] or via the Cloudflare dashboard.Optionally, you can override the default relay endpoints by setting PUSH_RELAY_URI and PUSH_IDENTITY_URI (defaults to https://push.bitwarden.com and https://identity.bitwarden.com).
For detailed configuration and troubleshooting, see the Vaultwarden wiki on push notifications.
Configure environment variables in wrangler.toml under [vars], or set them via Cloudflare Dashboard:
BASE_URL (Optional):
https://vault.example.com or https://vault.example.com:8443PASSWORD_ITERATIONS (Optional, Default: 600000):
TRASH_AUTO_DELETE_DAYS (Optional, Default: 30):
0 or negative to disable.IMPORT_BATCH_SIZE (Optional, Default: 30):
0 disables batching.DISABLE_USER_REGISTRATION (Optional, Default: true):
AUTHENTICATOR_DISABLE_TIME_DRIFT (Optional, Default: false):
true to disable ±1 time step drift for TOTP validation.ATTACHMENT_MAX_BYTES (Optional):
104857600 for 100MB.ATTACHMENT_TOTAL_LIMIT_KB (Optional):
1048576 for 1GB.ATTACHMENT_TTL_SECS (Optional, Default: 300, Minimum: 60):
SEND_TEXT_MAX_BYTES (Optional, Default: 1887436 ≈ 1.8 MiB):
SEND_MAX_BYTES (Optional, Default: 104857600 = 100 MiB):
USER_SEND_LIMIT_KB (Optional):
SEND_TTL_SECS (Optional, Default: 300):
The worker runs a scheduled task to clean up soft-deleted items. By default, it runs daily at 03:00 UTC (wrangler.toml [triggers] cron "0 3 * * *"). Adjust as needed; see Cloudflare Cron Triggers documentation for cron expression syntax.
wrangler dev --persistwrangler dev --persistwrangler d1 execute vault1 --file=backup.sql.wrangler/state/v3/d1/Run the Worker locally with D1 support using Wrangler.
Quick start (API-only):
wrangler dev --persist
Full stack (with Web Vault):
Download the frontend assets (see deployment doc).
Start locally:
wrangler dev --persist
Access the vault at http://localhost:8787.
Using production data temporarily:
Download and decrypt a backup (see backup doc).
Import locally without --remote:
wrangler d1 execute vault1 --file=backup.sql
Start wrangler dev --persist and point clients to http://localhost:8787.
Inspect local SQLite:
ls .wrangler/state/v3/d1/
sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite
[!NOTE] Local dev requires Node.js and Wrangler. The Worker runs in a simulated environment via workerd.
If you deployed via a GitHub fork, keeping up to date is straightforward:
[!TIP] It is recommended to sync your fork when a new release is published in the upstream, so you always have the latest features and security fixes.
Issues and PRs are welcome. Please run cargo fmt and cargo clippy --target wasm32-unknown-unknown --no-deps before submitting.
This project is licensed under the MIT License. See the LICENSE file for details.
116 followers · starred Feb 2026
290 followers · starred Jan 2026
56 followers · starred Feb 2026
193 followers · starred Jul 2026
A Bitwarden-compatible server for Cloudflare Workers
See the codeThis 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.
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.
Warden supports file attachments using either Cloudflare KV or Cloudflare R2 as the storage backend:
| Feature | KV | R2 |
|---|---|---|
| Max file size | 25 MB (hard limit) | 100 MB (By request body size limit of Workers) |
| Credit card required | No | Yes |
| Streaming I/O | Yes | Yes |
Backend selection: R2 takes priority — if R2 is configured, it will be used. Otherwise, KV is used.
See the deployment guide for setup details. R2 may incur additional costs; see Cloudflare R2 pricing.
[!NOTE] Due to the D1 single-row size limit of 2 MB, the maximum text Send size is approximately 1.8 MiB. Additionally, the
/api/syncendpoint serializes all of the current user's Sends into the response. A large number of Sends or very large text Sends will significantly increase CPU time and response size.
This project is not yet feature-complete, and it may never be. It currently supports the core functionality of a personal vault, including TOTP. However, it does not support the following 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.
A demo instance is available at warden.qqnt.de.
You can register a new account using an email ending with @warden-worker.demo (The email does not need verification).
If you decide to stop using the demo instance, please delete your account to make space for others.
It's highly recommended to deploy your own instance since the demo can hit the rate limit and be disabled by Cloudflare.
The frontend is bundled with the Worker using Cloudflare Workers Static Assets. The GitHub Actions workflows download a pinned bw_web_builds (Vaultwarden web vault) release (default: v2026.6.4) and deploy it together with the backend. You can override it via GitHub Actions Variables (BW_WEB_VERSION for prod, BW_WEB_VERSION_DEV for dev), or set it to latest to follow upstream.
How it works:
/api/*, /identity/*) are routed to the Rust Worker.UI overrides (optional):
public/css/.bw_web_builds:
mkdir -p public/web-vault/css/ && cp public/css/vaultwarden.css public/web-vault/css/[!NOTE] Migrating from separate frontend deployment? If you previously deployed the frontend separately to Cloudflare Pages, you can delete the
warden-frontendPages project and re-setup the router for the worker. The frontend is now bundled with the Worker and no longer requires a separate deployment.
[!WARNING] The web vault frontend comes from Vaultwarden and therefore exposes many advanced UI features, but most of them are non-functional. See Current Status.
The default *.workers.dev domain is disabled by default, since it may throw 1101 error. You can enable it by setting workers_dev = true in wrangler.toml.
If you want to use a custom domain instead of the default *.workers.dev domain, follow these steps:
example.com)A (or AAAA for IPv6)vault for vault.example.com)192.0.2.1 (this is a placeholder, the actual routing is handled by Worker)[!IMPORTANT] The Proxy status must be "Proxied" (orange cloud). If it shows "DNS only" (gray cloud), Worker routes will not work.
warden-workervault.example.com/* (replace with your domain)warden-workerThis project includes rate limiting powered by Cloudflare's Rate Limiting API. Sensitive endpoints are protected:
| Endpoint | Rate Limit | Key Type | Purpose |
|---|---|---|---|
/identity/connect/token (password grant) | 5 req/min per email + 5 req/min per IP | Email + IP address | Prevent password brute force and credential stuffing |
/identity/connect/token (send access) | 15 req/min | IP address | Prevent Send password brute force |
/api/sends/access/* (password protected) | 10 req/min | IP address | Prevent Send password brute force |
/api/accounts/register | 5 req/min | IP address | Prevent mass registration & email enumeration |
/api/accounts/prelogin | 5 req/min | IP address | Prevent email enumeration |
You can adjust the rate limit settings in wrangler.toml:
[[ratelimits]]
name = "LOGIN_RATE_LIMITER"
namespace_id = "1001"
# Adjust limit (requests) and period (10 or 60 seconds)
simple = { limit = 5, period = 60 }
[[ratelimits]]
name = "SEND_ACCESS_RATE_LIMITER"
namespace_id = "1003"
# Public Send password checks use a slightly wider IP limit.
simple = { limit = 10, period = 60 }
[!NOTE] The
periodmust be either10or60seconds. See Cloudflare documentation for details.
If the binding is missing, requests proceed without rate limiting (graceful degradation).
Cloudflare Workers Free plan has a very small per-request CPU budget. Two kinds of endpoints are particularly CPU-heavy:
To keep the main Worker fast while still supporting these operations, Warden can offload selected endpoints to Durable Objects (DO):
HEAVY_DO): implemented in Rust as HeavyDo (reuses the existing axum router) so CPU-heavy endpoints can run with a higher CPU budget.How to enable/disable
Whether CPU-heavy endpoints are offloaded is determined by whether the HEAVY_DO Durable Object binding is configured in wrangler.toml.
[!NOTE] Durable Objects have much higher CPU budget of 30 seconds per request in free plan(see Cloudflare Durable Objects limits), so we can use it to offload the CPU-heavy endpoints.
Durable Objects can incur two types of billing: compute and storage. Storage is not used in this project, and the free plan allows 100,000 requests and 13,000 GB-s duration per day, which should be more than enough for most users. See Cloudflare Durable Objects pricing for details.
If you choose to disable Durable Objects, you may need subscribe to a paid plan to avoid being throttled by Cloudflare.
Warden supports live sync for vault data via two mechanisms: WebSocket push (for desktop apps and browser extensions) and Mobile push notifications (for official mobile apps).
WebSocket Push (Desktop & Extensions)
This feature is powered by Durable Objects and enabled by default when the NOTIFY_DO Durable Object binding is configured in wrangler.toml. Removing this binding (and migration) will gracefully disable WebSocket notifications.
Mobile Push Notifications
Warden supports push notifications to official Bitwarden mobile apps via the Bitwarden push relay service.
Setup:
PUSH_INSTALLATION_ID & PUSH_INSTALLATION_KEY) via the Cloudflare dashboard or wrangler cli.PUSH_ENABLED to true in wrangler.toml [vars] or via the Cloudflare dashboard.Optionally, you can override the default relay endpoints by setting PUSH_RELAY_URI and PUSH_IDENTITY_URI (defaults to https://push.bitwarden.com and https://identity.bitwarden.com).
For detailed configuration and troubleshooting, see the Vaultwarden wiki on push notifications.
Configure environment variables in wrangler.toml under [vars], or set them via Cloudflare Dashboard:
BASE_URL (Optional):
https://vault.example.com or https://vault.example.com:8443PASSWORD_ITERATIONS (Optional, Default: 600000):
TRASH_AUTO_DELETE_DAYS (Optional, Default: 30):
0 or negative to disable.IMPORT_BATCH_SIZE (Optional, Default: 30):
0 disables batching.DISABLE_USER_REGISTRATION (Optional, Default: true):
AUTHENTICATOR_DISABLE_TIME_DRIFT (Optional, Default: false):
true to disable ±1 time step drift for TOTP validation.ATTACHMENT_MAX_BYTES (Optional):
104857600 for 100MB.ATTACHMENT_TOTAL_LIMIT_KB (Optional):
1048576 for 1GB.ATTACHMENT_TTL_SECS (Optional, Default: 300, Minimum: 60):
SEND_TEXT_MAX_BYTES (Optional, Default: 1887436 ≈ 1.8 MiB):
SEND_MAX_BYTES (Optional, Default: 104857600 = 100 MiB):
USER_SEND_LIMIT_KB (Optional):
SEND_TTL_SECS (Optional, Default: 300):
The worker runs a scheduled task to clean up soft-deleted items. By default, it runs daily at 03:00 UTC (wrangler.toml [triggers] cron "0 3 * * *"). Adjust as needed; see Cloudflare Cron Triggers documentation for cron expression syntax.
wrangler dev --persistwrangler dev --persistwrangler d1 execute vault1 --file=backup.sql.wrangler/state/v3/d1/Run the Worker locally with D1 support using Wrangler.
Quick start (API-only):
wrangler dev --persist
Full stack (with Web Vault):
Download the frontend assets (see deployment doc).
Start locally:
wrangler dev --persist
Access the vault at http://localhost:8787.
Using production data temporarily:
Download and decrypt a backup (see backup doc).
Import locally without --remote:
wrangler d1 execute vault1 --file=backup.sql
Start wrangler dev --persist and point clients to http://localhost:8787.
Inspect local SQLite:
ls .wrangler/state/v3/d1/
sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite
[!NOTE] Local dev requires Node.js and Wrangler. The Worker runs in a simulated environment via workerd.
If you deployed via a GitHub fork, keeping up to date is straightforward:
[!TIP] It is recommended to sync your fork when a new release is published in the upstream, so you always have the latest features and security fixes.
Issues and PRs are welcome. Please run cargo fmt and cargo clippy --target wasm32-unknown-unknown --no-deps before submitting.
This project is licensed under the MIT License. See the LICENSE file for details.
116 followers · starred Feb 2026
290 followers · starred Jan 2026
56 followers · starred Feb 2026
193 followers · starred Jul 2026