# Vaultwarden Job Scheduler Features: Automated Cleanup for Sends, Trash, and Events

> Explore Vaultwarden job scheduler features for automated cleanup. Learn how to purge expired sends, delete trashed ciphers, and clean old events with cron expressions.

- Repository: [Daniel García/vaultwarden](https://github.com/dani-garcia/vaultwarden)
- Tags: deep-dive
- Published: 2026-03-07

---

**Vaultwarden includes a built-in job scheduler powered by the `job_scheduler_ng` crate that automatically purges expired Sends, permanently deletes trashed ciphers, and cleans old events using configurable cron expressions that run in a dedicated background thread.**

Vaultwarden, the open-source Bitwarden-compatible password manager maintained by dani-garcia, provides a lightweight job scheduler that eliminates the need for external cron daemons or task runners. This system executes three distinct cleanup jobs directly within the application process, ensuring data retention policies are enforced without manual intervention. Understanding these Vaultwarden job scheduler features allows administrators to configure precise data lifecycle management through simple environment variables.

## Available Cleanup Jobs

The scheduler manages three primary maintenance operations, each triggered by independent cron schedules defined in the `jobs` configuration section.

### Purge Expired Sends

The **Send purge job** deletes "Send" objects that have exceeded their `deletion_date`, preventing indefinite storage of expired file shares. This job is controlled by the `SEND_PURGE_SCHEDULE` configuration key, which defaults to `0 5 * * * *` (executing hourly at minute 5). In [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs) lines 640–645, the scheduler registers this job by spawning an async task via `runtime.spawn(api::purge_sends(pool.clone()))`. The underlying implementation resides in `src/api/core/sends.rs::purge_sends`, which invokes `Send::purge` from [`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/send.rs) to execute the database deletion.

### Purge Trashed Ciphers

The **trash purge job** permanently removes ciphers that have been in the trash longer than the `trash_auto_delete_days` threshold. Configured via `TRASH_PURGE_SCHEDULE` with a default cron expression of `0 5 0 * * *` (daily at 00:05), this job is registered in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs) lines 647–652. When triggered, the scheduler executes `api::purge_trashed_ciphers(pool.clone())`, implemented in `src/api/core/ciphers.rs::purge_trashed_ciphers`, which calls `Cipher::purge_trash` in [`src/db/models/cipher.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/cipher.rs) to perform the permanent deletion.

### Clean Old Events

The **event cleanup job** purges rows from the `events` table that exceed the `events_days_retain` retention period. This job uses the `EVENT_CLEANUP_SCHEDULE` key, defaulting to `0 10 0 * * *` (daily at 00:10), and is registered in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs) lines 693–699. The execution path flows through `api::event_cleanup_job` in [`src/api/core/events.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/events.rs), which delegates to `Event::clean_events` in [`src/db/models/event.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/event.rs) for the actual database cleanup operation.

## Configuration and Scheduling

All cron expressions are validated at startup in [`src/config.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/config.rs) (lines 543–548) and parsed by the `job_scheduler_ng` crate. An empty string for any schedule disables that specific job entirely.

### Default Configuration

Enable the standard cleanup schedule using the default intervals:

```bash

# .env or config.json

JOB_POLL_INTERVAL_MS=30000
SEND_PURGE_SCHEDULE="0 5 * * * *"
TRASH_PURGE_SCHEDULE="0 5 0 * * *"
EVENT_CLEANUP_SCHEDULE="0 10 0 * * *"

```

### Custom Intervals

Modify the cron expression to adjust frequency. For example, to purge Sends every 15 minutes instead of hourly:

```bash
SEND_PURGE_SCHEDULE="0 */15 * * * *"

```

### Disabling Jobs

Prevent a specific job from running by setting its schedule to an empty string:

```bash

# Disable event cleanup entirely

EVENT_CLEANUP_SCHEDULE=""

```

## Technical Implementation Details

The job scheduler architecture centers around the `schedule_jobs()` function in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs) (spanning approximately lines 630–720), which creates a dedicated thread to host the `JobScheduler` instance.

### Execution Model

The scheduler operates on a **tick-based polling mechanism**:

1. **Initialization**: The thread builds a `JobScheduler` and registers each enabled job with its parsed cron expression
2. **Polling Loop**: The thread enters an infinite loop calling `sched.tick()` to evaluate pending jobs
3. **Interval Control**: Between ticks, the thread sleeps for `JOB_POLL_INTERVAL_MS` milliseconds (default 30,000ms)
4. **Async Execution**: When a job triggers, the scheduler spawns the task onto the Tokio runtime with a cloned database connection pool, ensuring non-blocking operation

### Database Layer Integration

Each cleanup job follows a consistent pattern of API-to-model delegation:

- **Sends**: `src/api/core/sends.rs::purge_sends` → `src/db/models/send.rs::Send::purge`
- **Ciphers**: `src/api/core/ciphers.rs::purge_trashed_ciphers` → `src/db/models/cipher.rs::Cipher::purge_trash`  
- **Events**: `src/api/core/events.rs::event_cleanup_job` → `src/db/models/event.rs::Event::clean_events`

This separation ensures the scheduler thread remains lightweight while database-intensive operations execute on the async runtime with proper connection pooling.

## Summary

- Vaultwarden implements a self-contained job scheduler using the `job_scheduler_ng` crate, requiring no external cron service
- Three automated jobs handle data retention: **Send purge** (hourly default), **trash purge** (daily default), and **event cleanup** (daily default)
- Configuration occurs via environment variables (`SEND_PURGE_SCHEDULE`, `TRASH_PURGE_SCHEDULE`, `EVENT_CLEANUP_SCHEDULE`) using standard cron syntax
- The `schedule_jobs()` function in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs) manages a dedicated polling thread that spawns async tasks onto the Tokio runtime
- Jobs can be individually disabled by setting their respective schedule to an empty string

## Frequently Asked Questions

### How do I disable a specific cleanup job in Vaultwarden?

Set the corresponding configuration key to an empty string. For example, assign `EVENT_CLEANUP_SCHEDULE=""` to prevent the event cleanup job from registering with the scheduler. The validation logic in [`src/config.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/config.rs) treats empty strings as disabled jobs, and the `schedule_jobs()` function in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs) skips registration for any job lacking a valid cron expression.

### What cron format does the Vaultwarden job scheduler use?

Vaultwarden uses the standard Unix cron format with six fields (seconds, minutes, hours, days of month, months, days of week) as implemented by the `job_scheduler_ng` crate. The default schedules follow this pattern: `0 5 * * * *` for hourly execution at minute 5, and `0 5 0 * * *` for daily execution at 00:05.

### How frequently does the job scheduler check for pending tasks?

The scheduler evaluates pending jobs every 30 seconds by default, controlled by the `JOB_POLL_INTERVAL_MS` configuration option. This polling interval determines how often the dedicated scheduler thread calls `sched.tick()` to check if any registered cron jobs should fire, independent of the individual job schedules themselves.

### Where does the actual data deletion logic reside for purge operations?

While the scheduler triggers jobs in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs), the deletion logic resides in the database models layer. For Sends, the logic is in [`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/send.rs); for trashed ciphers in [`src/db/models/cipher.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/cipher.rs); and for events in [`src/db/models/event.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/event.rs). The API layer in `src/api/core/` (sends.rs, ciphers.rs, events.rs) serves as the intermediary that the scheduler invokes, which then calls these model-specific purge methods.