# Vaultwarden Emergency Access: How Timeout and Reminder Configurations Work

> Learn how Vaultwarden emergency access works with timeout and reminder configurations. Understand waiting periods and auto-approval settings for secure vault access.

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

---

**Vaultwarden emergency access allows designated contacts to request vault access after a configurable waiting period, enforced by background jobs that send reminders and auto-approve requests based on cron schedules defined in [`src/config.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/config.rs).**

Vaultwarden, the open-source Bitwarden-compatible password manager, implements an emergency access system that lets users designate trusted contacts to recover or view their vault after a safety waiting period. Understanding how the **emergency access timeout and reminder configurations** work requires examining the state machine in [`src/db/models/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/emergency_access.rs), the API logic in [`src/api/core/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/emergency_access.rs), and the background job schedules in [`src/config.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/config.rs). This guide explains the complete lifecycle from invitation to automatic approval.

## How Emergency Access Works

Vaultwarden emergency access establishes a trust relationship between a **grantor** (the vault owner) and a **grantee** (the trusted contact). The system supports two distinct access modes that determine what the grantee can do once approved.

### Access Modes: View vs. Takeover

- **View**: The grantee can read the grantor’s vault items but cannot modify the account or master password. This is suitable for read-only recovery scenarios.
- **Takeover**: The grantee gains full control of the grantor’s account, including the ability to reset the master password and delete organizations. This provides complete account recovery.

Both modes enforce a mandatory **wait time** (specified in days) between when the grantee initiates the request and when access is granted. This waiting period is configurable per invitation via the `wait_time_days` field.

### Feature Toggle

Emergency access is globally gated by the configuration flag `emergency_access_allowed`. Every endpoint in [`src/api/core/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/emergency_access.rs) starts with the helper `check_emergency_access_enabled()`, which aborts with *“Emergency access is not enabled.”* if the feature is disabled.

```rust
fn check_emergency_access_enabled() -> EmptyResult {
    if !CONFIG.emergency_access_allowed() {
        err!("Emergency access is not enabled.")
    }
    Ok(())
}

```

*Source: `src/api/core/emergency_access.rs:711-715`*

## The Emergency Access State Machine

The `EmergencyAccess` model tracks status through a strict enum-driven lifecycle. Each transition is handled by specific API endpoints that validate permissions and update the database record.

### Status Definitions

| Status | Value | Description |
|--------|-------|-------------|
| `Invited` | 0 | Initial state after the grantor sends an invitation. |
| `Accepted` | 1 | Grantee has accepted the invitation. |
| `Confirmed` | 2 | Grantor confirmed the invitation and supplied the encrypted key. |
| `RecoveryInitiated` | 3 | Grantee initiated recovery; the timeout clock starts. |
| `RecoveryApproved` | 4 | Request approved (manually or via timeout); access granted. |

### Invitation and Acceptance Workflow

1. **Invite**: `POST /emergency-access/invite` creates a record with `status = Invited` and stores the `wait_time_days`. The server emails the grantee a JWT invitation token.
2. **Accept**: `POST /emergency-access/<id>/accept` validates the JWT and calls `EmergencyAccess::accept_invite()`, transitioning to `Accepted` and linking the grantee’s UUID.
3. **Confirm**: `POST /emergency-access/<id>/confirm` stores the `key_encrypted` (the grantor’s vault key encrypted for the grantee) and sets `status = Confirmed`.

*Source: Invitation logic at `src/api/core/emergency_access.rs:998-1014`; acceptance at lines 332-371; confirmation at lines 388-426.*

### Initiating Recovery and Manual Approval

Once the relationship is `Confirmed`, the grantee can trigger the timeout period:

- **Initiate**: `POST /emergency-access/<id>/initiate` sets `status = RecoveryInitiated` and records the current timestamp in `recovery_initiated_at` and `last_notification_at`.
- **Approve**: `POST /emergency-access/<id>/approve` allows the grantor to manually bypass the wait time, immediately setting `status = RecoveryApproved`.
- **Reject**: `POST /emergency-access/<id>/reject` resets the status back to `Confirmed`, canceling the request.

The helper `is_valid_request` enforces that subsequent `view` or `takeover` calls require `status = RecoveryApproved` and that the grantee UUID matches the stored `grantee_uuid`.

*Source: Initiation at `src/api/core/emergency_access.rs:441-467`; validation at lines 700-709.*

## Timeout Configuration and Auto-Approval

The automatic approval mechanism relies on a background job that polls for expired wait times and transitions requests to `RecoveryApproved` without manual intervention.

### The Timeout Job

The `emergency_request_timeout_job` runs on the schedule defined by `emergency_request_timeout_schedule` (default: `"0 7 * * * *"`, meaning every hour at minute 7). It queries all records with `status = RecoveryInitiated`, calculates the expiration time by adding `wait_time_days` to `recovery_initiated_at`, and approves any expired requests.

```rust
pub async fn emergency_request_timeout_job(pool: DbPool) {
    for mut emer in emergency_access_list {
        let recovery_allowed_at = emer.recovery_initiated_at.unwrap()
            + TimeDelta::try_days(i64::from(emer.wait_time_days)).unwrap();
        if recovery_allowed_at.le(&now) {
            emer.update_access_status_and_save(
                EmergencyAccessStatus::RecoveryApproved as i32,
                &now,
                &conn,
            ).await?;
            // Sends "approved" and "timed out" emails
        }
    }
}

```

*Source: Job implementation in [`src/api/core/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/emergency_access.rs); scheduled in `src/main.rs:667`.*

### Cron Schedule Configuration

You can customize the check frequency via the `emergency_request_timeout_schedule` configuration key. The default cron expression `"0 7 * * * *"` runs hourly at minute 7. To run every 6 hours, you would override this in your environment:

```bash
ROCKET_EMERGENCY_REQUEST_TIMEOUT_SCHEDULE="0 0 */6 * * *"

```

*Source: Default values in `src/config.rs:552-557`.*

## Reminder Notification Configuration

To ensure grantors do not miss pending recovery requests, Vaultwarden runs a separate reminder job that sends email notifications starting one day before the timeout expires.

### The Reminder Job

The `emergency_notification_reminder_job` executes on the `emergency_notification_reminder_schedule` (default: `"0 3 * * * *"`, hourly at minute 3). It identifies requests where the current time has passed `recovery_initiated_at + (wait_time_days - 1)`, then sends a reminder email if at least one day has elapsed since the last notification.

```rust
let final_recovery_reminder_at = emer.recovery_initiated_at.unwrap()
    + TimeDelta::try_days(i64::from(emer.wait_time_days - 1)).unwrap();

let next_recovery_reminder_at = if let Some(last) = emer.last_notification_at {
    last + TimeDelta::try_days(1).unwrap()
} else {
    now
};

if final_recovery_reminder_at.le(&now) && next_recovery_reminder_at.le(&now) {
    emer.update_last_notification_date_and_save(&now, &conn).await?;
    // send recovery reminder email
}

```

*Source: [`src/api/core/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/emergency_access.rs) (job definition) and [`src/mail.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/mail.rs) (email templates).*

### Notification Timing Logic

- **First reminder**: Sent when the current time exceeds `wait_time_days - 1` days after initiation.
- **Subsequent reminders**: Sent every 24 hours thereafter until the request is approved or rejected.
- **Tracking**: The `last_notification_at` field prevents spam by enforcing a minimum 24-hour gap between emails.

## Configuration Options and Defaults

The behavior of both background jobs is controlled through the `CONFIG` struct, which maps to environment variables using the `ROCKET_` prefix.

| Config Key | Default | Description |
|------------|---------|-------------|
| `emergency_access_allowed` | `true` | Global toggle to enable or disable the entire feature. |
| `emergency_request_timeout_schedule` | `"0 7 * * * *"` | Cron for the auto-approval job (hourly at :07). |
| `emergency_notification_reminder_schedule` | `"0 3 * * * *"` | Cron for the reminder job (hourly at :03). |

*Source: `src/config.rs:552-557`.*

## Practical API Workflow Examples

### Inviting a Grantee (Grantor Action)

```http
POST /emergency-access/invite HTTP/1.1
Content-Type: application/json
Authorization: Bearer <grantor-jwt>

{
  "email": "trustee@example.com",
  "type": "Takeover",
  "waitTimeDays": 7
}

```

This creates the `EmergencyAccess` record with `status = Invited` and triggers `mail::send_emergency_access_invite` with a JWT link.

### Initiating Recovery (Grantee Action)

```http
POST /emergency-access/<uuid>/initiate HTTP/1.1
Authorization: Bearer <grantee-jwt>

```

This transitions the status to `RecoveryInitiated`, sets `recovery_initiated_at = now()`, and starts the timeout countdown.

### Automatic Approval (System Action)

No manual API call is required. Once `now >= recovery_initiated_at + wait_time_days`, the `emergency_request_timeout_job` automatically updates the status to `RecoveryApproved` and emails both parties.

### Accessing the Vault (Grantee Action)

After approval, the grantee can retrieve the encrypted vault key:

```http
POST /emergency-access/<uuid>/takeover HTTP/1.1
Authorization: Bearer <grantee-jwt>

```

The server validates the request via `is_valid_request`, ensuring the status is `RecoveryApproved` and the type matches `Takeover`.

## Summary

- **Vaultwarden emergency access** uses a state machine (`Invited` → `Accepted` → `Confirmed` → `RecoveryInitiated` → `RecoveryApproved`) to control trust relationships.
- **Timeout configuration** is controlled by the `emergency_request_timeout_schedule` cron job (default hourly at minute 7), which auto-approves requests after `wait_time_days` have elapsed.
- **Reminder configuration** uses `emergency_notification_reminder_schedule` (default hourly at minute 3) to send daily alerts starting one day before the timeout expires.
- **Global toggle**: Set `emergency_access_allowed` to `false` to disable the feature entirely.
- **File locations**: State logic resides in [`src/db/models/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/emergency_access.rs), API endpoints in [`src/api/core/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/emergency_access.rs), and job scheduling in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs).

## Frequently Asked Questions

### How do I change the default waiting period for emergency access requests?

The waiting period is set per invitation via the `waitTimeDays` field in the `POST /emergency-access/invite` request. It is not a global configuration; each grantor specifies how many days (minimum typically 1) they want the grantee to wait before access is granted. The background jobs then respect this value when calculating the approval time.

### Can I disable the automatic reminder emails?

While there is no explicit boolean flag to disable reminders, you can effectively stop the reminder job by setting the `emergency_notification_reminder_schedule` to a cron expression that never runs (e.g., `"0 0 31 2 * *"` for February 31st). However, note that this also disables the safety notifications that alert grantors to pending takeovers.

### What happens if the grantor rejects a recovery request?

If the grantor calls `POST /emergency-access/<id>/reject`, the system resets the `EmergencyAccess` record from `RecoveryInitiated` back to `Confirmed` (as implemented in `src/api/core/emergency_access.rs:514-545`). The grantee must initiate a new request if they wish to attempt access again, restarting the `wait_time_days` countdown from zero.

### How does Vaultwarden ensure the grantee cannot access the vault before the timeout expires?

The `is_valid_request` helper function (lines 700-709 in [`src/api/core/emergency_access.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/emergency_access.rs)) strictly checks that `status == RecoveryApproved` before allowing `view` or `takeover` endpoints to proceed. Until the `emergency_request_timeout_job` updates the status or the grantor manually approves, the grantee receives a validation error when attempting to access the vault.