Skip to content

Environment variables

Copy .env.example to .env and fill in what you need. See Get started → Install for the handful that are actually required. The release stack has its own, shorter env.example (attached to every GitHub release and in deploy/release); every variable in it is described here, grouped as in the file on Release stack reference, and the repository’s .env.example is the full list. An empty value in .env falls back to the default named here; NODE_ENV is deliberately absent from the file, since the release image always runs with NODE_ENV=production and any entry here, even an empty one, would override that.

Variable Default Purpose
PORT 3000 HTTP port of the api process.
RESTOW_PUBLIC_URL none Public origin the browser uses (origin only), e.g. https://app.example.com or http://localhost:5173.
RESTOW_APP_DOMAIN none Public app domain for the Caddy edge (automatic Let’s Encrypt TLS), e.g. app.example.com.
RESTOW_API_URL none Internal API base URL. A normal installation does not need it; leave it empty (the public demo’s seed process uses it).
RESTOW_MODE none Operating mode: local | public. Set by the setup wizard; may be pinned here.
RESTOW_EDITION none Public demo only: the edition the demo shows without a license key, community | business | service_provider. It has an effect only together with RESTOW_DEMO=true; anywhere else it is ignored. Leave it empty: a production installation gets its edition from a license key under Installation → License (full images). See License and editions.
RESTOW_LICENSE_PUBLIC_KEY built into the release images Optional override of the Ed25519 license verification key (raw, base64url), for development and tests of the full build. Release images carry the vendor’s key.
RESTOW_UPDATE_CHECK_URL empty: the Updates tab decides Environment override for the update check: a releases endpoint over https (a GitHub .../releases or .../releases/latest URL, or a Forgejo or Gitea releases API). When set it wins over the tab under Settings, Updates: the check is on, the source is this URL and read-only there, and no stored token is sent to it. Empty means the tab decides, and the check is off until an administrator turns it on. Only the release list is read; no installation data is sent. See Updates.
RESTOW_RUN_AS_COMMUNITY empty (off) Full build only, since 0.4.5. 1 starts a release that the installed Business or Service Provider key does not cover (it was released after the key’s update period ended) as Community instead of refusing to start: the modules stay off and no data is changed or deleted. Installing a key that covers the release turns them on again at once. It has no effect while the key covers the running release, and the check never applies to beta licenses, development builds, installations without a key or the public demo. See License and editions.
LOG_LEVEL info Log level of the worker: debug | info | warn | error.
RESTOW_DOCS_TROUBLESHOOTING_URL https://docs.restowbackup.com/administrators/troubleshooting/ The page the failure explanations in the web interface link to for more help. Point it at your own runbook if you keep one.
RESTOW_PRODUCT_NAME Restow The product name that the web app, mails, reports, the API documentation and the names of passkeys and authenticator entries show. A display name only: the RESTOW_* variables, restow-agent, restow-restore, the X-Restow-* headers and the image names keep their names. The static maintenance page is written when the web image is built and carries the name given as the build argument RESTOW_PRODUCT_NAME, not this runtime value.

RESTOW_VERSION (the running release) and, since 0.4.5, RESTOW_RELEASED_AT (its release date, the commit date of the release tag) are written into the image at build time; do not set them in .env, since an empty value there would hide them. GET /api/v1/status reports the date as releasedAt, and the update check compares it with the update period of a license key (License and editions).

Variable Default Purpose
RESTOW_SETUP_TOKEN empty: a new token at every start The one-time setup token the setup wizard asks for in its first step. Empty: the api prints a fresh token to its log at every start until the setup is complete (docker compose logs api | grep 'SETUP TOKEN'). Set it for an unattended installation or when container logs are shipped elsewhere: at least 16 characters, spaces and dashes not counted (for example openssl rand -hex 16); it is then never written to the log, and the api refuses to start with a shorter value. No effect once the setup is complete.

Read by the Compose files only.

Variable Default Purpose
RESTOW_HTTP_PORT, RESTOW_HTTPS_PORT 80, 443 Release stack: host ports of the Caddy edge. Let’s Encrypt needs both reachable on the standard ports, so change them only behind another reverse proxy or for a local evaluation. From 0.2.0 they also take an address: 192.168.1.50:443 publishes on one interface only, and 127.0.0.1: publishes on the loopback at a port Docker picks (the install script uses that behind a proxy for the port it does not need).
RESTOW_API_PORT 3000 Host port on which the api is published on 127.0.0.1 only. The edge reaches the api over the Compose network and does not use it.
POSTGRES_PORT 5432 Source stack only: port on which the postgres service is published on 127.0.0.1. The release stack publishes no PostgreSQL port.
Variable Default Purpose
RESTOW_IMAGE none (required in the release stack) The application image with its version, used by api, worker and scheduler: ghcr.io/restow-backup/restow:0.4.7 (full build) or ghcr.io/restow-backup/restow-community:0.4.7 (Community build). Use both images of one build at the same version. In the source stack leave it empty: Compose then runs the restow:local image it builds. The opt-in updater writes this line.
RESTOW_WEB_IMAGE none (required in the release stack) The web image of the same build, with the Caddy edge: ghcr.io/restow-backup/restow-web:0.4.7 or ghcr.io/restow-backup/restow-web-community:0.4.7. Empty in the source stack: restow-web:local. The updater writes this line too.
RESTOW_UPDATER_IMAGE empty: the updater starts from RESTOW_IMAGE (from 0.2.1); restow:local in the source stack The updater’s own image. Optional since 0.2.1: on its first start the updater writes it itself, pinned by digest to the image it runs, so the next rewrite of RESTOW_IMAGE does not reach it. After a signed image-mode update it moves itself to the verified release (see RESTOW_UPDATER_SELF_UPDATE); it never moves to an image built from source. If you set it yourself, use an image of the same build as RESTOW_IMAGE (restow-community for the Community build). See How the updater and the mounter pin their image.
RESTOW_PROJECT_DIR empty: the project directory is mounted at /project (from 0.2.1) Absolute path, on the host, of the directory that holds docker-compose.yml and .env, for example /opt/restow. Optional since 0.2.1; set it only when the Compose file needs another path (empty, the directory is mounted at /project and the updater reads its host path from the mount; set, it is mounted at the same path). The install script sets it. Used by the updater and, from 0.3.1, the mounter; from 0.3.6 the api also reads it to show commands that start with cd <directory>.
RESTOW_UPDATER_URL http://updater:8090 Where the api looks for the optional updater. Nothing answers there unless the updater profile runs. Change it only for an updater that runs elsewhere.
RESTOW_UPDATER_SOURCE_HOSTS empty: source mode off Repositories (git.example.com/acme/restow, github.com/acme/restow) or whole hosts (git.example.com), comma-separated, that the updater may build an update from (source mode, a custom update source under Settings, Updates). Empty: source mode is off, and only the signed release images of the default source can be installed from the web interface. Do not list github.com alone. Whatever is built from these repositories runs with access to all data and keys: list only repositories you trust as much as the release itself. The api reads it too, to let the update check reach a listed host on a private network. Recreate the updater after a change: docker compose --profile updater up -d updater.
RESTOW_UPDATER_IMAGE_REPOSITORY, RESTOW_UPDATER_WEB_IMAGE_REPOSITORY empty: the release images of the updater’s own build Where the updater pulls the application and the web image from. Set both only for a registry mirror of the release images, and copy the signatures with them (cosign copy).

The updater (docker compose --profile updater up -d, opt-in) mounts the Docker socket, which is root on the host; read Updates first. The updater service gets no .env as its environment: from .env the Compose file passes it only RESTOW_PROJECT_DIR (as RESTOW_UPDATER_PROJECT_DIR), RESTOW_UPDATER_SOURCE_HOSTS and the two image repositories. The variables below are read by the updater container only (ROLE=updater); the defaults apply unless you set one in a Compose override of the updater service.

Variable Default Purpose
RESTOW_UPDATER_PROJECT_DIR set from RESTOW_PROJECT_DIR Absolute host path of the Compose project, mounted at the same path.
RESTOW_UPDATER_HEALTH_TIMEOUT_SECONDS 600 How long to wait for the new api (migrations can take a while).
RESTOW_UPDATER_MIN_FREE_MB 1024 Margin on top of what an update needs (since 0.4.0). The updater adds up the database dump, the images and the room for the migrations and refuses an update that does not fit, with the blocker disk_space; this margin comes on top. See Updates → Disk space.
RESTOW_UPDATER_CLI_IMAGE docker:27-cli, pinned by digest Image used to run docker and docker compose. Must carry a digest (name:tag@sha256:...).
RESTOW_UPDATER_COSIGN_IMAGE ghcr.io/sigstore/cosign/cosign:v3.1.3, pinned by digest The cosign image that verifies the release signatures before an image is pulled. Must carry a digest.
RESTOW_UPDATER_VERIFY_SIGNATURES true false skips the signature check, for test installations or unsigned mirrors only; the digest stays required, and every run records that the signature was not checked.
RESTOW_UPDATER_SELF_UPDATE true From 0.2.1. After an image-mode update whose images passed the signature check, the updater pins RESTOW_UPDATER_IMAGE to the verified application image by digest and recreates itself. false keeps the updater’s image unchanged until you move it by hand. Set it in .env, then recreate the updater.

The mounter adds NFS shares as repository storage from the web interface and, since 0.4.0, runs the containers that back up and restore file shares. New installations start it by default (install.sh --no-mounter leaves it off); on an existing installation start it with Enable network shares or docker compose --profile mounts up -d mounter.

Variable Default Purpose
RESTOW_MOUNTER_IMAGE empty: starts from RESTOW_IMAGE The mounter’s image, the application image started with ROLE=mounter. On its first start the mounter writes this line itself, pinned by digest to the image it runs (a local build has no digest and is not pinned). The opt-in updater moves it to the verified image of every signed update it installs. Without the updater, to move it, set it (or empty it) and run docker compose --profile mounts up -d mounter.
RESTOW_MOUNTER_URL http://mounter:8091 Where the api looks for the mounter. Change it only for a mounter that runs elsewhere.
RESTOW_MOUNTER_SECRET_FILE /mounter-shared/secret The shared secret the api reads from the volume restow-mounter-shared.
RESTOW_MOUNTER_HEALTH_TIMEOUT_SECONDS 300 How long the mounter waits for api and worker after a change.
RESTOW_MOUNTER_PROBE_TIMEOUT_SECONDS 60 How long the share test may take.
RESTOW_MOUNTER_MAX_RUNNERS 8 (since 0.4.0) Hard cap of file share runners (containers) at the same time. The setting File share runners → Runs at the same time in the web interface is the normal limit and cannot exceed it. The release stack passes the variable to the mounter and the worker.
RESTOW_MOUNTER_RUNNER_MAX_MEMORY_MIB 16384 (since 0.4.0) Upper bound of the memory per file share runner in MiB; Memory per runner (MiB) in the web interface cannot exceed it. Passed to the mounter and the worker like the line above.
RESTOW_MOUNTER_SELINUX_CONTEXT false (since 0.4.0) true adds the context= mount option for file share mounts, for a Docker host with SELinux enforcing. Needed if a test mounts but reads “Permission denied”. The release docker-compose.yml does not pass it from .env (see the note below the table).
RESTOW_MOUNTER_RUNNER_API_URL http://api:3000 (since 0.4.0) The address a file share runner calls, on the internal runners network. The default is right for the release stack.
RESTOW_MOUNTER_RUNNER_NETWORK runners (since 0.4.0) The Compose network key of the runners’ network (the Docker name is <project>_runners).
RESTOW_MOUNTER_RUNNER_EXEC_TIMEOUT_SECONDS 60 (since 0.4.0) How long a share test or folder listing in a runner may take.

The mounter has no env_file: the release docker-compose.yml passes only RESTOW_MOUNTER_PROJECT_DIR, RESTOW_MOUNTER_MAX_RUNNERS and RESTOW_MOUNTER_RUNNER_MAX_MEMORY_MIB to it (and the last two to the worker). RESTOW_MOUNTER_SELINUX_CONTEXT, RESTOW_MOUNTER_RUNNER_API_URL, RESTOW_MOUNTER_RUNNER_NETWORK and RESTOW_MOUNTER_RUNNER_EXEC_TIMEOUT_SECONDS therefore take effect only when you put them into the environment of the mounter service in docker-compose.override.yml and recreate it, for example services: mounter: environment: RESTOW_MOUNTER_SELINUX_CONTEXT: "true". See File shares: the Docker host.

The release notes of 0.4.0 name RESTOW_SHARE_REUSE_MAX_ENTRIES as an override of the limit above which a share run reads all permissions again instead of reusing the previous ones. The runner (restow-share) reads it, but the mounter passes no .env variable on to the runner container, so setting it in .env has no effect in 0.4.7.

Variable Default Purpose
RESTOW_EDGE_TRUSTED_PROXIES loopback only Peers whose X-Forwarded-For the edge keeps (and appends to) instead of replacing with the peer’s own address: space-separated CIDR ranges, or private_ranges. Needed only when another reverse proxy sits in front of Caddy. Name only the proxy’s own address (for example 192.168.1.20/32): every listed peer may claim any client address. The api reads the same value: it records the right-most X-Forwarded-For address that is not one of these proxies as the client address (audit log, the operator notice acceptance, the sign-in rate limit and the agent lockout), never the left-most one, which the client itself can write. Set it in .env; the shipped Compose files pass it to the api and to Caddy.
RESTOW_EDGE_TLS acme From 0.2.0. How the edge gets its certificate. acme (or empty): automatic HTTPS, Let’s Encrypt for a public RESTOW_APP_DOMAIN. internal: a certificate for RESTOW_APP_DOMAIN from Caddy’s own certificate authority, for the encrypted hop behind another reverse proxy; the edge then also answers connections without SNI with that certificate. internal needs no public DNS record and no inbound port 80, whatever the name. Any other value stops the edge at start. The install script’s --behind-proxy sets internal; see Behind a reverse proxy. The root certificate to verify the edge with is /data/caddy/pki/authorities/local/root.crt in the caddy-data volume (for example docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt .).
RESTOW_EDGE_HSTS false true sends Strict-Transport-Security (with includeSubDomains). Set it only once RESTOW_APP_DOMAIN is a real, publicly resolvable domain for which the edge terminates its own valid TLS certificate (public mode). The value is compared literally, so it must be exactly lowercase true; anything else leaves HSTS off. Never set it for a local or IP installation or a bare HTTP domain: browsers would then insist on HTTPS for a host that may never serve it, with no way back until the max-age expires. It applies to every subdomain of RESTOW_APP_DOMAIN too, not only to that host.
Variable Default Purpose
POSTGRES_PASSWORD none (required) Password for the Postgres superuser Docker Compose creates. Compose does not start postgres without it.
POSTGRES_USER, POSTGRES_DB restow, restow Owner role and database name of the postgres service. Change them only before the first start, and use the same names in DATABASE_MIGRATION_URL.
DATABASE_MIGRATION_URL none (required) The database owner (POSTGRES_USER); runs migrations only, for example postgres://restow:<POSTGRES_PASSWORD>@postgres:5432/restow.
DATABASE_URL none (required) The application role, subject to Row Level Security, for example postgres://restow_app:<password>@postgres:5432/restow. The migration step creates it with the name/password given here.
DATABASE_PROVIDER_URL none (required) The installation role (BYPASSRLS), for example postgres://restow_provider:<password>@postgres:5432/restow, created the same way.
DATABASE_AUTH_URL none (optional, since 0.3.5): the login is derived from the other two The auth role that sign-in runs on. Leave it empty and the migration derives its login from DATABASE_URL and DATABASE_PROVIDER_URL. Set it only for a login of your own; see below.
Variable Default Purpose
RESTOW_POSTGRES_DATA_TYPE empty: volume volume or bind: whether the postgres service’s data directory is a Docker volume or a directory on the host. The updater writes bind when it moves the database.
RESTOW_POSTGRES_DATA empty: the volume pgdata The volume name, or with bind the absolute host directory the data lives in, for example /mnt/volume-1/restow-db/restow-postgres-<time>. The updater writes it when it moves the database from Installation → Database.

Both lines are optional and read only by the docker-compose.yml of 0.4.0 or later. Once they are set, never start the stack from a compose file that does not read them (the file of 0.3.6 does not): PostgreSQL would not find its data. install.sh refuses such a file with exit code 8. See Database.

Three roles, three connection strings, three different passwords (generate each with openssl rand -hex 16). In Compose the database host is the service name postgres. See Get started → The three database roles. The processes refuse to start if DATABASE_URL turns out to be a superuser or able to bypass RLS.

The auth role (since 0.3.5). Sign-in (sessions, passwords, passkeys, two-factor secrets) runs on a database role of its own, created and kept up to date by the migration. Its login is derived from the other two logins (<application role>_auth, for example restow_app_auth), so you set nothing for it and need no change in .env when you update. Set DATABASE_AUTH_URL, for example postgres://restow_auth:<password>@postgres:5432/restow, only to give it a login of your own, or when you manage the database roles yourself; the migration step creates it like the others. The application role keeps to tenant data under Row Level Security and may only add jobs to the queue. Together with the installation role, the auth role is the only one that reaches the sign-in tables.

Generate both with openssl rand -base64 32.

Variable Purpose
RESTOW_MASTER_KEY Required. 32-byte key-encryption key (KEK), base64. Wraps every tenant key; keep a copy offline, because without it no backup can ever be read again. See Backing up Restow itself.
BETTER_AUTH_SECRET Required. Secret for the auth system’s own tokens and sessions.

Microsoft Entra (multi-tenant app for backup, client credentials)

Section titled “Microsoft Entra (multi-tenant app for backup, client credentials)”
Variable Purpose
ENTRA_CLIENT_ID The backup app’s Application (client) ID.
ENTRA_CLIENT_SECRET Client secret credential (fill exactly one of this or the certificate path).
ENTRA_CLIENT_SECRET_EXPIRES_AT Optional, since 0.4.7: when ENTRA_CLIENT_SECRET expires, as Entra shows it (YYYY-MM-DD, for example 2027-03-01). Restow reminds you 30 days before, again 7 days before and once the secret has expired, but only with this date, because it does not read it from Microsoft. Set it in .env and recreate the api. A certificate needs none (its own end counts), and an unreadable value counts as unknown: no reminder. The release stack’s env.example does not list it; env_file hands it to the api all the same.
ENTRA_CLIENT_CERT_PATH Optional: certificate path (PEM, private key + certificate) instead of a secret. Used in preference to the secret if both are set.
ENTRA_AUTHORITY_HOST Optional: authority host for sovereign clouds. Default https://login.microsoftonline.com.
DIRECTORY_FULL_SYNC_HOURS Default 24. Hours between full directory enumerations; incremental runs happen in between.

All of these are optional: the app registration can instead be entered in the web interface (stored encrypted, effective without a restart). When ENTRA_CLIENT_ID plus ENTRA_CLIENT_SECRET or ENTRA_CLIENT_CERT_PATH are set here, they take precedence and the web interface shows them read-only.

See First steps → Connect Microsoft 365 for how to obtain these, and Expiry of the secret or certificate for the reminders.

Variable Purpose
ENTRA_SSO_CLIENT_ID Reserved for sign-in with Microsoft (Entra ID), which is planned and not part of 0.4.7. It has no effect: leave it empty.
ENTRA_SSO_CLIENT_SECRET Reserved for the same feature. It has no effect in 0.4.7: leave it empty.
RESTOW_EXPERIMENTAL_MICROSOFT_SIGN_IN For developers only: true switches the Microsoft sign-in code on. It is not supported, and nobody could sign in with it, because no account is linked to a Microsoft identity. Leave it empty; the sign-in endpoints then answer 404.

Used when nothing is saved under Installation → Notification mail; the web interface wins. Microsoft 365 with an app registration of its own for notifications, and Google Workspace (a service account with domain-wide delegation), have no variables here: set them up in the web interface under Installation → Notification mail, which seals their keys in the database.

Variable Purpose
MAIL_TRANSPORT smtp | graph.
SMTP_HOST, SMTP_PORT SMTP server and port.
SMTP_SECURE true for implicit TLS (also assumed on port 465); otherwise STARTTLS.
SMTP_USER, SMTP_PASSWORD Optional SMTP credentials.
SMTP_FROM Sender address.
GRAPH_MAIL_SENDER Sender mailbox for the Microsoft Graph sendMail transport.
GRAPH_MAIL_TENANT_ID Entra tenant of the sender mailbox, if the setup wizard names none.
Variable Default Purpose
IMAP_ALLOW_INSECURE false Permits IMAP sources without TLS. Development only; never in production.
IMAP_ALLOW_PRIVATE_NETWORKS provider-saved only true lets every IMAP source reach loopback and private networks; by default only servers a provider admin saved can, and tenant admins are limited to public servers.

How IMAP archiving watches an account (IDLE or at intervals) is set with the ARCHIVE_IMAP_* variables under Archive capture from the mailbox and, per source, with its setting Check at intervals only.

The installation’s default storage. The provider owner can also set it in the web interface (Installation → Default repository & network shares); a setting saved there takes precedence over these values, which stay the fallback.

Variable Default Purpose
STORAGE_TARGET local Target type: local | s3.
STORAGE_LOCAL_PATH /data/chunks Path inside the container for the default local target.
STORAGE_COPY_LOCAL_PATH none Optional second target every default write is copied to (a mounted NFS share).
S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_PREFIX none S3-compatible target (Hetzner Object Storage, Garage, Wasabi, B2, AWS, …).
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY none S3 credentials.
S3_FORCE_PATH_STYLE true false for virtual-hosted bucket addressing.
S3_ALLOW_INSECURE false (since 0.4.0) true accepts a plain http:// S3 endpoint to a host other than localhost, 127.0.0.1 or [::1]. For test and development setups only, for example an object store on the Compose network; set it for the api and the worker. Without it, an S3 endpoint that is saved or changed must use https:// (endpoint_insecure), for provider administrators and the installation default too. An S3_ENDPOINT saved before 0.4.0 keeps working, and its card and every test say in red that it should move to HTTPS.

All optional. See Mail file import and export.

Variable Default Purpose
RESTOW_IMPORT_DIR ./import next to docker-compose.yml Host directory that is mounted read-only at /var/lib/restow/import in the api and the worker. Each tenant uses its own subfolder <directory>/<tenant slug>/, which you create yourself: drop EML, MSG, MBOX or ZIP files (or a MailStore export folder) there and pick them under Tenant settings → Connections → Add source → Import mail files.
IMPORT_DIR /var/lib/restow/import The path inside the containers. Change it only together with the mount above.
IMPORT_MAX_FILE_BYTES 10 GiB Largest file the browser upload accepts, in bytes.
IMPORT_UPLOAD_TTL_HOURS 48 Hours an unfinished or unused upload is kept before it is deleted.
IMPORT_SEGMENT_BYTES 8 MiB Size of one upload chunk in bytes (allowed: 64 KiB to 32 MiB).
IMPORT_MAX_MESSAGE_BYTES 256 MiB Largest single message that is read into memory.
IMPORT_MAX_STAGING_BYTES 100 GiB Bytes the uploads of one tenant may hold in staging together, counted by declared file size until their import has ended. A new upload that does not fit is refused before its first byte (import-staging-full). The server import folder does not use staging.
IMPORT_PARSE_WORKERS 2 (1 to 8) Parser processes the worker runs at the same time for MSG files and message metadata. Each may use up to 512 MB; a message above 8 MiB gets a process of its own, up to 3 GB. Lower it on a small host.
EXPORT_TTL_HOURS 24 Hours a finished export can be downloaded before its file is deleted.
EXPORT_MAX_TENANT_BYTES 50 GiB Bytes all unexpired export files of one tenant may take together. A new export request is refused (export-quota-exceeded), and an export that grows beyond it fails (“The export is larger than the space for exports”, failure code export.quota_exceeded), until older exports have expired.
PREVIEW_PARSE_WORKERS 2 (1 to 8) Parser processes the api runs at the same time for the mail preview, attachment downloads and journal reports. Same memory limits as above.
PREVIEW_TIMEOUT_MS 10000 How long one message may take for its formatted preview. A message that needs more is shown as text (“Simplified view”) or as unreadable.

All optional. The defaults suit the release stack; only a custom image needs changes. See Endpoint backup.

Variable Default Purpose
RESTIC_BINARY restic from the PATH The restic binary that the api and the worker run for retention, checks, restore tests, browsing and downloads. The image ships it in /usr/local/bin/restic.
RESTOW_RESTIC_CACHE_DIR the temp folder Where restic keeps its per-endpoint caches. A folder on a volume, for example /data/restic-cache, keeps them across restarts and makes browsing faster.
RESTOW_AGENT_DIR /srv/agent Where the image keeps the agent binaries, in /srv/agent/<version>/<os>-<arch>/. A development checkout reads agent/install from the repository instead.
RESTOW_AGENT_INSTALL_DIR /srv/agent/install Where the image keeps the install scripts. Point RESTIC_BINARY, RESTOW_AGENT_DIR or RESTOW_AGENT_INSTALL_DIR elsewhere only for a custom image.
RESTOW_ENDPOINT_QUOTA_GIB 2048 (2 TiB) Storage budget of one endpoint repository in GiB (whole or decimal numbers; 0 switches it off). An administrator can set another value per machine (“Storage budget” in its settings). Read by api and worker.
RESTOW_ENDPOINT_TENANT_QUOTA_GIB 20480 (20 TiB) Storage budget of all endpoint repositories of one tenant together, in GiB (0 switches it off). An upload beyond a budget is refused (endpoint-quota-exceeded), restores keep working, and the alerts warn from 90 percent. Read by api and worker.
Variable Default Purpose
WORKER_CONCURRENCY 2 Parallel jobs per queue in one worker process.
WORKER_TENANT_CONCURRENCY 2 Parallel jobs per tenant in one worker process.
WORKER_POLL_SECONDS 2 pg-boss polling interval.
WORKER_SHUTDOWN_TIMEOUT_MS 50000 (since 0.3.5) How long shutdown waits for running jobs to checkpoint. Keep it below the stop_grace_period of 60 seconds of the worker service in the release stack; Docker’s default of 10 seconds would cut the drain off.
WORKER_CANCEL_POLL_MS 15000 How often a running job checks whether it was cancelled.
WORKER_PROGRESS_FLUSH_ITEMS 50 Progress is written every N items…
WORKER_PROGRESS_FLUSH_MS 2000 …or at least every M milliseconds.
IMMUTABILITY_RENEWAL_CONCURRENCY 32 (since 0.4.0; 8 before, at most 256) How many Object Lock requests the daily renewal of an immutable repository sends at the same time. An interrupted renewal continues where it stopped.

Since 0.4.4, all optional, read by the worker (Business and Service Provider; the Community build keeps the capture service idle). The release stack’s env.example does not list them: add the ones you need to .env, which api and worker read. What they control: Archiving, Connecting sources.

Variable Default Purpose
ARCHIVE_CAPTURE_CONCURRENCY 16 Mailboxes one worker process works on at the same time (a stream per archived mailbox and way).
ARCHIVE_CAPTURE_TENANT_CONCURRENCY 4 Of those, at most this many for one tenant, so that one tenant’s mailboxes do not use up all places.
ARCHIVE_BACKFILL_CONCURRENCY 4 One-time takeovers of existing mail that run at once in one worker process.
ARCHIVE_GRAPH_CATCHUP_MINUTES 360 Microsoft 365: how often the delta catch-up over every folder runs per mailbox while change notifications arrive. It runs at once after a missed notification.
ARCHIVE_GRAPH_POLL_MINUTES 15 Microsoft 365 without notifications (no public https:// URL on port 443, see Network and ports): how often each mailbox is checked. It is the capture then.
ARCHIVE_IMAP_IDLE_MAX 300 IMAP archiving: accounts one worker process watches with IMAP IDLE, one connection each. 0 switches IDLE off; further accounts are checked by a login at intervals. Mind the connection limit per account of your providers.
ARCHIVE_IMAP_POLL_MINUTES 5 How often a watched connection checks the other folders of the account by STATUS.
ARCHIVE_IMAP_LOGIN_POLL_MINUTES 10 How often an account that nobody watches with IDLE is checked by a login.

The public URL that Graph notifications need is RESTOW_PUBLIC_URL, unless Installation → Server & operation → Server sets one.

Variable Default Purpose
PVE_BLOCK_MAP_CACHE_MB 256 (since 0.4.0) Memory in MiB that the decoded block maps of the VM disks being browsed or restored may take in the api, cached by size instead of by count. Raise it for very large VM disks. See Files of a guest.
RESTOW_HOST_SMBIOS_UUID none (since 0.4.2) The SMBIOS UUID of the machine Restow runs on, for finding the Restow server’s own VM among the guests. Overrides /sys/class/dmi/id/product_uuid, which the api reads otherwise. Set it only for a host that hides /sys.
Variable Default Purpose
WEBHOOK_POLL_MS 5000 Poll interval for due deliveries.
WEBHOOK_CONCURRENCY 4 Parallel deliveries.
WEBHOOK_TIMEOUT_MS 10000 Per-attempt timeout.
RESTOW_WEBHOOK_ALLOW_PRIVATE refused true permits webhook targets in loopback and private networks.
Variable Default Purpose
RESTOW_TIME_ZONE none (since 0.4.2) The time zone of the installation, for an installation in which no owner has chosen one in the web interface: an IANA name such as Europe/Vienna. Planned updates are read in it, and a new tenant starts with it. The first of these applies: the zone chosen in the setup wizard or under Installation → Server & operation → Server, then RESTOW_TIME_ZONE, then the process zone TZ of the server containers, then Europe/Berlin. A value that is not an IANA zone is skipped, so a typo stops nothing. Not in .env.example: add the line to .env and recreate the containers (docker compose up -d) if you want it. See The installation’s time zone.
SCHEDULER_DEFAULT_TIMEZONE Europe/Berlin IANA time zone in which the times of the recommended default schedules are interpreted, for example Europe/Vienna. An unknown zone stops the scheduler.
SCHEDULER_TICK_MS 30000 Tick interval.
SCHEDULER_LEADER_RETRY_MS 15000 Leader retry.
SCHEDULER_BATCH_SIZE 200 Due schedules per tick.
SCHEDULER_DEFER_MS 3600000 Delay before a schedule that can’t be planned (invalid cron) is looked at again.
SCHEDULER_LOCK_KEY none Postgres advisory lock key for leader election; change only when two installations share a database.

All optional; the receiver is off until JOURNAL_SMTP_PORT is set. Setup, DNS, port 25 and the certificate: Exchange journaling. Since 0.4.4 the same receiver takes the copies that a mail provider forwards (Forwarding copies from the mail provider).

Variable Default Purpose
JOURNAL_SMTP_PORT none (receiver off) Port the archive’s SMTP journal receiver listens on. The receiver starts only when this is set. Exchange Online delivers to port 25. The repository’s .env.example sets 25; the release env.example leaves it empty. Empty keeps it off: nothing listens, and the release Compose file maps a closed port (2525) on the loopback interface only, so a default installation opens no mail port to the network.
JOURNAL_SMTP_BIND 127.0.0.1 Release stack only: the host address the port is published on. Set it to 0.0.0.0 together with JOURNAL_SMTP_PORT=25 to receive Exchange Online journal reports, which must reach the receiver directly, not through Caddy (the receiver must be reachable from the internet).
JOURNAL_HOSTNAME none The host name Exchange Online delivers to, for example archive.example.com: host name only, without scheme, path, port or mail address. It forms the tenants’ journal addresses, journal+<token>@<host>; publish it in DNS. Without a valid value Restow shows no complete address.
JOURNAL_TLS_CERT_PATH none Path to the certificate chain (PEM, leaf first) of a publicly trusted authority, matching JOURNAL_HOSTNAME. Exchange Online requires TLS, so the receiver does not start without a certificate (the archive page then says “no TLS certificate”) and never falls back to a built-in one. With a certificate, mail is refused until STARTTLS. In the release stack: /etc/restow/journal-tls/fullchain.pem.
JOURNAL_TLS_KEY_PATH none Path to the private key (PEM, not encrypted with a passphrase) that belongs to the certificate. Both paths must be set. In the release stack: /etc/restow/journal-tls/privkey.pem.
JOURNAL_TLS_DIR ./journal-tls Both stacks’ Compose file: the host directory that is mounted read-only into the api at /etc/restow/journal-tls. Put fullchain.pem and privkey.pem there. Renewed files are picked up within five minutes, without a restart. The api reads them at start and expects them to be valid: restart it after you first put them there, or after a start that failed because they were missing or expired. Where the certificate comes from, with the docker compose cp commands: The journal receiver’s certificate.
JOURNAL_ALLOW_INSECURE empty (off) true starts the receiver without a certificate and without offering STARTTLS. Local development only, never in production: Exchange Online does not deliver to such a receiver and mail would cross the network in plain text. Ignored while a certificate is configured: a configured certificate that is broken never degrades to plain text.
JOURNAL_MAX_SIZE_MB 150 Largest journal report accepted, in MB. Larger reports are refused.
JOURNAL_RATE_PER_IP 600 (since 0.4.4; a fixed 60 before) Connections per sending IP address per minute, per api process. Beyond it the receiver answers 421. Raise it for a hoster that forwards the mail of many accounts.
JOURNAL_RATE_PER_ADDRESS 300 (since 0.4.4) Deliveries per archive address (journal address or the archive address of an IMAP account) per minute, per api process. Beyond it the receiver answers 451 at RCPT TO.

Read by the repository’s docker-compose.yml only.

Variable Default Purpose
RESTOW_WEB_PORT 5173 Host port of the Vite dev server of the dev profile.
MAILPIT_SMTP_PORT, MAILPIT_UI_PORT 1025, 8025 Host ports of Mailpit, the development SMTP sink of the mail profile, and of its web interface.

For the public demo in deploy/demo/ only; leave all of them unset in a normal installation. deploy/demo/README.md and deploy/demo/.env.example in the repository describe the demo’s other variables.

Variable Purpose
RESTOW_DEMO true switches demo mode on: the demo account signs in without an authenticator code, a single guard refuses every change that could alter the installation or reach outside, and webhooks and notifications do nothing. There is no setup token in demo mode.
RESTOW_DEMO_EMAIL, RESTOW_DEMO_PASSWORD Credentials of the demo account, shown in plain text on the login page.
RESTOW_DEMO_SEED_TOKEN Shared secret between the demo’s seed process and the api.
RESTOW_EDITION The edition the demo shows without a license key (see Core).
RESTOW_DEMO_COMPOSE_FILES, RESTOW_DEMO_PUBLISH_PUBLIC Since 0.4.5 deploy/demo/reset.sh reads the colon-separated list of compose files from deploy/demo/.env too (before, only from its environment) and logs which files a run uses. A list from .env that would publish web on every interface (the override for a demo on its own VM, with ports 80 and 443) is used only together with RESTOW_DEMO_PUBLISH_PUBLIC=true; entries that are not compose files in deploy/demo/ are refused before anything is stopped.
RESTOW_DEMO_TEMPLATES, RESTOW_DEMO_TEMPLATE_DIR, RESTOW_DEMO_TEMPLATE_MAX_AGE_DAYS, RESTOW_DEMO_TEMPLATE_MIN_FREE_MB, RESTOW_DEMO_TEMPLATE_QUIET_SECONDS The fast nightly reset (since 0.4.5). After a full seed the reset saves a template (a database dump and the seeded volumes) and restores it on the following nights instead of seeding again, so the demo is offline for about 15 to 25 seconds instead of many minutes. RESTOW_DEMO_TEMPLATES=false switches it off. A template older than RESTOW_DEMO_TEMPLATE_MAX_AGE_DAYS (default 7; 0 always seeds), a new release, new images, a changed .env or a failed restore lead to a full seed. The other settings: where templates are kept (default templates, the newest two are kept), the free space in MB that must remain after saving one (default 1024) and how long the save waits for running jobs (default 300 seconds).