---
title: "Backups"
manual: "T3Vault"
version: "main"
permalink: "https://docs.typo3.org/permalink/codemacher/t3vault:backup@main"
source: "Usage/Backup.rst"
rendered: "2026-10-01T12:10:44+00:00"
---

# Backups {#backup}

Prerequisites: a **backup encryption password** is set in Settings.

> [!IMPORTANT]
> ZIP AES-256 encrypts file *contents*, not archive metadata. Entry names,
> sizes and the directory tree remain readable without the password. Keep
> backup directories **outside the web root** (default
> `var/t3vault/backups`). Never publish `.t3vault.tar` or ZIP
> parts under `public/`.
>
> `config.local.php` (standalone password, signing secret, backup password)
> is **not** included. TYPO3 `settings.php` /
> `LocalConfiguration.php` are stored with DB credentials and
> `encryptionKey` removed — reconfigure them on restore.

Manual backups from **New Backup** open a dialog to include or exclude
components (database, configuration, packages, Composer files, FAL/media,
uploads). The job label reflects the selection (e.g. database-only is shown as
a database backup, not a full site backup). Only a complete set (database +
configuration + Composer / `typo3conf`) enables a full site restore; the
restore installer warns and skips inapplicable steps for partial packages.
Scheduled tasks of type `full` still back up all components.

![New Backup dialog with checkboxes for database, configuration, packages, Composer files and media](../Images/screenshot-new-backup.png)

## Backup types {#backup-types}

-   **`full`**

    Default. Everything needed to reinstall elsewhere (or a selected subset
    when started from the New Backup dialog).

    **Composer layout** (`config/` \+ `public/`):

    -   `database.zip` (AES-256; entry `database.sql.gz`)
    -   `config/` (settings, additional, sites)
    -   `packages/` and other local Composer path repositories (e.g. `extensions/` from `composer.json`)
    -   `composer.json` / `composer.lock`
    -   `public/fileadmin/` and other local FAL storages (without `_processed_/` / `_temp_/`)
    -   optional `uploads/`, `index.php`, `.htaccess`

    **Classic layout** (`typo3conf/` in the document root):

    -   `database.zip` (AES-256; entry `database.sql.gz`)
    -   `typo3conf/` (without temp/autoload caches)
    -   `fileadmin/`, optional `uploads/` (without `_processed_/` / `_temp_/`)
    -   `index.php`, `.htaccess`
    -   Core (`typo3/`, `typo3_src/`) and `typo3temp/` are **not**
        included

-   **`fileadmin`**

    Media only (ZIP parts of fileadmin / FAL; without `_processed_/` / `_temp_/`).

-   **`database`**

    Encrypted database dump only (`database.zip` \+ `MANIFEST.json`).
    Useful for frequent scheduled tasks when a full site backup is not required
    every night.

Each completed backup also contains `MANIFEST.json` with layout-specific
restore hints and environment metadata (PHP / DB / TYPO3 versions).

## Scheduled backup tasks {#backup-tasks}

Backup tasks define **what** to back up, **when**, and **how long** to keep
archives. Configure them in the UI (**Backup tasks**) or via the API
(`GET`/`POST` `/tasks`). Definitions are stored in
`tasks.json` inside the T3Vault data directory (0600).

![Backup tasks dialog listing scheduled full-site and database jobs](../Images/screenshot-tasks.png)

Each task has:

-   `type` — `database`, `fileadmin` or `full`
-   `schedule` — `hourly` (minute of hour) / `daily` / `weekly` /
    `monthly` (with `at` time) or a five-field `cron` expression
-   `retention` — one or more tiers `{ every: { value, unit }, keep }` with
    `unit` in `hour|day|week|month|year`

Retention uses **independent historical slots** per tier, measured from the
newest non-pinned backup of that task:

-   The **finest** tier (smallest `every`) covers ages `[0, keep·E)` in
    `keep` slots of length `E` (includes the newest backup).
-   **Coarser** tiers start one full period back: slots
    `[E, 2·E)` … `[keep·E, (keep+1)·E)`. So “1× month / keep 1” retains a
    backup about 30–60 days old, and “1× year / keep 1” about 365–730 days old
    (fixed durations: month = 30 days, year = 365 days).

Each slot keeps the newest backup in that age interval; the union of all tiers
survives. Manual backups (no `taskId`) and pinned backups are never deleted
by retention.

Run due tasks from cron (synchronous, no HTTP self-invocation loop):

```bash
php t3vault/cli/t3vault-run.php due
php t3vault/cli/t3vault-run.php task <id>
php t3vault/cli/t3vault-run.php retention
php t3vault/cli/t3vault-run.php list
```

Or via the TYPO3 console (recommended on Composer installs):

```bash
vendor/bin/typo3 t3vault:run due
vendor/bin/typo3 t3vault:run task <id>
vendor/bin/typo3 t3vault:run retention
vendor/bin/typo3 t3vault:run list
```

**TYPO3 Scheduler** (ext:scheduler suggested):

-   Dedicated task **“T3Vault: run due backup tasks”**, or
-   Task type **Execute console commands** → command `t3vault:run`
    (optional argument `due`, which is the default).

On the local DDEV project, `scheduler:run` is invoked every minute via
Supercronic (`.ddev/web-build/typo3.cron`). After creating a Scheduler task
in the backend, due T3Vault backups run automatically while `ddev start` is
active — no host crontab needed.

Manual “Run now” in the UI starts the job via the normal web process chain.
Backups created by a task carry `taskId` / `taskName` / `mode` in the
plan and manifest so retention only touches that task’s archives.

## What is never packed {#what-is-never-packed}

-   **Symlinks leaving the backup scope.** Because archiving follows symlinks, a
    link inside `fileadmin/` that points to `~/.ssh/id_rsa`,
    `/etc/passwd` or an `.env` outside the project would otherwise end
    up in the archive. Such links are skipped and listed as `skippedSymlinks` in
    the backup plan. Links that resolve back into the project are kept, so
    Composer path repositories in `vendor/`, `public/_assets/` and the
    classic `typo3/` links keep working.
-   **System directories configured as FAL storage.** A `sys_file_storage` row
    with a base path such as `/etc`, `/root`, `/usr` or a bare
    home directory is ignored. Regular storages outside the project root (for
    example `/srv/shared-media`) are still backed up as external parts.

## How processing works {#backup-process}

1.  Start a backup from the UI (type `full`, `fileadmin` or `database`),
    or let a scheduled task / the CLI runner start it.
1.  A JSON plan is written under the backup directory.
1.  **Web path:** the server continues batches via self-invocation (CURL), so
    closing the browser does not stop the job — useful on shared hosting
    without cron.
    **CLI path** (`t3vault/cli/t3vault-run.php`): one process runs the
    plan to completion without HTTP continuation.
1.  When finished, integrity sealing runs (hashes + OpenTimestamps). Job status
    `completed` is independent of whether the stamp already reached Bitcoin
    blockchain confirmation (see [Integrity & trust](https://docs.typo3.org/permalink/codemacher/t3vault:integrity@main)).

Optional batch options (API / advanced):

| Option | Default meaning |
| --- | --- |
| `maxZipSizeMb` | Target size per ZIP part (default 500) |
| `filesPerBatch` | Files per batch iteration (default 50) |
| `maxTimePerBatch` | Seconds per batch (default 20) |

## Directory layout {#backup-layout}

```text
var/t3vault/backups/
└── backup_YYYYMMDD_HHMMSS_<id>/
    ├── backup-plan.json
    ├── backup-plan.json.state.json   # while running
    ├── backup.log
    ├── database.zip                 # AES-encrypted SQL dump (full backups)
    ├── MANIFEST.json
    ├── *_part_*.zip                 # AES-encrypted
    ├── integrity.json
    ├── integrity.json.hmac          # optional
    ├── integrity.json.ots
    └── integrity-meta.json
```

`backup-plan.json` may also hold optional user fields `name` (label,
max. 120 characters) and `note` (free text, max. 2000 characters). Edit them
from the backup list (pencil icon). They are not part of the integrity seal, so
changing them does not invalidate the archive signature. An empty value removes
the field. Without a custom `name`, the UI shows the backup type label.

## Error messages and references {#backup-errors}

API responses keep infrastructure detail out: instead of an exception message
with absolute paths, a failing request answers with a generic message and a
reference such as `ref: 9f2ac41b77e0`. The full detail — exception class,
message, file and line — is written to

```text
var/t3vault/state/error.log
```

Search that file for the reference to see what actually happened. The file is
created with `0600` in the private directory, outside the web root.

## Download {#backup-download}

From the backup list you can download:

-   `.t3vault.tar` — archive of the backup directory
-   Installer helpers for restore on a target server
-   **DDEV project** script (`t3vault-ddev.sh`) for local Composer-based
    reproduction

A browser download cannot send the `Authorization` header, so each download is
authorised by a separate short-lived token in the URL (60 s; 120 s for the
installer). The token is bound to one purpose, to one backup, and — on first use
— to the requesting client. Repeat requests from the same client still work
(redirects, resumed transfers), while the same URL from anywhere else is refused.
The generated DDEV script therefore carries its own archive token (one hour),
because it runs later and from another client than the browser.

## Importing backups {#backup-import}

An upload (`.t3vault.tar` or loose artifacts) is content from outside, so
it is checked before it appears in the backup list.

**Request limits.** At most `uploadMaxFiles` files, `uploadMaxFileBytes` per
file and `uploadMaxTotalBytes` in total (see [Settings overview](https://docs.typo3.org/permalink/codemacher/t3vault:configuration-keys@main)). The
import is also refused when the target volume has less than roughly twice the
payload free, because a TAR import stages the archive and then writes the
extracted artifacts. Only one import runs at a time.

**Structural validation.** Every ZIP part is inspected with the same rules the
restore installer applies: absolute entry paths, `..` segments, backslashes and
symlink entries are rejected, as is an implausible compression ratio. A rejected
upload is deleted immediately and answered with a log reference — no half-imported
directory stays behind.

**Trust marking.** A backup created here is sealed: `integrity.json` lists
the artifact hashes and `integrity.json.hmac` binds that list to this
installation's signing secret. An import is therefore rated:

| Rating | Meaning |
| --- | --- |
| *verified* | Hashes match `integrity.json` **and** the HMAC verifies with the local secret — sealed here, unchanged |
| *unverified* | Anything else: no `integrity.json`, altered artifacts, a seal from another installation, or no local signing secret |

A foreign archive cannot verify here — that is expected, not an attack, and it
is reported as such. The list and the details dialog show the rating with its
reason, and the generated `RESTORE.md` of an unverified import carries a
warning: restoring an archive of unknown origin introduces its code and data into
the target system.

`integrity-meta.json` from an upload is discarded. It records the result of
a verification run on the *source* system and must not be shown as this
installation's verification state.
