Skip to content
NeverWipedStart free14 days · no card

Docs · Start

Getting started

Install NeverWiped on a Linux server, run the first backup, check it, and learn how to bring back a file, a folder, a database or a whole server. Every command on this page is copy-paste ready and runs as root.

Markdown versionThe CLI prints English by default (`--lang fr|es` for French or Spanish). Some output blocks come from a French session: they are labelled and translated in the text.

Before you start

  • A Linux server, amd64 (x86_64) or arm64 (aarch64): Debian, Ubuntu, RHEL and derivatives (Rocky, AlmaLinux, Fedora), Alpine. Linux kernel 3.2 or newer; 32-bit systems are not supported. With systemd, or cron with its daemon running (on Alpine: rc-update add crond default && rc-service crond start).
  • On the RHEL family, sudo does not search /usr/local/bin: type sudo /usr/local/bin/neverwiped …, or work in a root shell (sudo -i).
  • root, or sudo.
  • curl or wget, and sha256sum or openssl (almost every distribution has them).
  • Outbound HTTPS (port 443) to https://neverwiped.com (the platform), https://storage.neverwiped.com (backup storage, in France) and https://github.com (restic is downloaded once, at install, and its SHA-256 fingerprint checked).
  • A NeverWiped account with a confirmed email: the free trial lasts 14 days, no card. Then an API key: dashboard › API keys › Create. It starts with nw_acc_ and is shown once.

1. Install in one command

On the server, as root:

curl -fsSL https://neverwiped.com/install.sh | sh

The script asks for your API key, shows what it will back up and waits for your OK. To answer everything up front (scripts, AI agents):

curl -fsSL https://neverwiped.com/install.sh | sh -s -- --api-key "$NEVERWIPED_API_KEY" --yes

Not root? Replace | sh with | sudo sh. No curl? Use wget -qO- https://neverwiped.com/install.sh | sh.

Faster: the dashboard of your client area makes this command with a new API key already in it (… | sh -s -- --api-key KEY). One click, one paste: the script then asks only about the plan and the first backup. The key stays in your shell history; it can only register servers, and you can revoke it in API keys once your servers are installed, without stopping their backups.

What happens, in order:

  1. The script downloads the agent for your architecture, checks its SHA-256 fingerprint and installs it as /usr/local/bin/neverwiped.
  2. neverwiped install downloads restic 0.18.1 (fingerprint checked), detects your sites, databases and Docker volumes, and shows the plan.
  3. After your OK, it registers the server, creates the encrypted repository with a key generated on the server, and shows the recovery key once.
  4. It schedules backups for your plan and runs the first one.

To adjust the plan, add options after --yes: --include /data, --exclude "*.log", --no-databases, --no-docker. See the command reference.

Size and exclusions before the first backup

Before asking you to accept the plan, the install measures it: the size and number of files of each folder and Docker volume once the exclusions are applied, the largest folders, the size of the local database files, and how much each exclusion preset would remove. It reads file sizes only (no content) and sends nothing. It stops after 90 seconds and then says “at least”; --no-estimate skips it.

You can run the same measure before subscribing, with the same options as the install. Nothing is registered and no network call is made:

neverwiped estimate
neverwiped estimate --preset logs --exclude /var/www/site/tmp
neverwiped estimate --json

The figure is the size before compression and deduplication: the first backup sends at most this much. Text and databases usually compress well, photos, videos and archives hardly at all. Database exports are not counted in it: the estimate shows the size of the database files on disk instead, and an export is usually smaller. For databases in Docker the export size is not measured. Compare the total with the storage of each plan on the pricing page.

After the server is registered, the install compares the estimate with the space left on your subscription. If the files and volumes alone are larger, it asks before the first backup, with “no” as the default answer. With --yes it does not start the first backup, so that it doesn’t stop half way at the quota. Scheduled backups stay on.

Presets. None is applied by default. Add one with --preset NAME (repeatable, or --preset logs,build, or --preset all), at estimate or at install. The estimate proposes those that would save at least 100 MiB on your server.

PresetWhat it leaves outPatterns
logsApplication log files in the backed-up folders*.log, *.log.[0-9]*, *.log.gz, **/storage/logs, **/var/log
buildBuild output and package caches that a build recreates**/.next/cache, **/.nuxt, **/__pycache__, **/.gradle, **/.terraform, **/.yarn/cache, **/.pnpm-store, Maven, Cargo, Composer and npm caches in /root and /home/*, and a few more
trashTrash folders**/.Trash-*, **/.local/share/Trash
disk-imagesDisk and virtual machine images, ISOs*.iso, *.qcow2, *.vmdk, *.vdi, *.vhd, *.vhdx
archivesArchives and old local backups*.tar, *.tar.gz, *.tgz, *.tar.bz2, *.tar.xz, *.tar.zst, *.sql.gz, *.sql.zst, *.sql.xz, *.bak

Pick archives only if those files are copies of data the plan already backs up: an archive that is your only copy of something would no longer be backed up. The same goes for logs if you need old logs (an audit, for example). Patterns follow restic’s syntax: a pattern without a leading / matches at any depth. Like every option, presets are not remembered: repeat them when you run neverwiped install again.

2. Save your recovery key

At the end of the install the agent prints this box. The example below is from a French session; in English it says “RECOVERY KEY: write it down now, away from this server. It will never be shown again and NeverWiped doesn’t know it. Without it, if this server is lost, your backups can’t be read.”

========================================================================
CLÉ DE RÉCUPÉRATION — notez-la maintenant, hors de ce serveur.

    Ab3dEf-GhJkMn-PqRsTu-VwXyZa-b2c3d4-e5f6g7

Serveur : web-01 (identifiant 0b6a3c1e-2f4d-4e8a-9c7b-5d1e2f3a4b5c). Code de contrôle de cette clé : 4342-B317.
Notez le nom du serveur et le code de contrôle à côté de la clé : chaque serveur a sa propre clé de récupération.
Elle ne sera plus jamais affichée et NeverWiped ne la connaît pas.
Sans elle, si ce serveur est perdu, vos sauvegardes sont illisibles.
========================================================================

Put it in your password manager now, not on the server. Why it matters: encryption and the recovery key.

Then test the copy you wrote: sudo neverwiped recovery-key check (the key isn’t sent anywhere). Lost it later? While the server still runs, sudo neverwiped recovery-key new makes a new one: lost recovery key.

3. The first backup

The install runs it for you (unless you passed --no-backup). Run one whenever you like:

neverwiped backup now
Sauvegarde démarrée (manual)
Export postgresql (toutes les bases)…
Sauvegarde réussie en 2s : 2,0 Mo ajoutés, 5,3 Mo stockés au total, instantané a31a3fb5

“Backup succeeded in 2 s: 2.0 MB added, 5.3 MB stored in total, snapshot a31a3fb5.” Only changes are sent. After that, backups run on their own: every night on Pro and Business, once a week on Essentiel, at a fixed time between about 1 and 6 a.m. server time. Backups run at low priority; to cap their upload rate or CPU as well, see Bandwidth, CPU and priority.

4. Check status

neverwiped status
neverwiped list

status shows the server, what is backed up, your plan, whether backups are active, storage used and the last backup:

Serveur      : vps-prod (identifiant be1e7955-1dd6-4211-8002-7746d6ede9e2)
Plateforme   : https://neverwiped.com
Agent        : neverwiped 0.2.0, restic 0.18.1
Dossiers     : /etc, /var/spool/cron, /var/www
Base         : postgresql (locale)
Abonnement   : Essentiel (trialing)
Sauvegardes  : hebdomadaire, actives
Espace       : 5,3 Mo utilisés sur 5,0 Go
Dernière     : 2026-11-04 02:16
Planification: minuteur systemd (…)

What to look for: Sauvegardes (backups) says actives, Dernière (last) is a recent date, and Planification (schedule) found a timer or a cron entry. You also get an email if a backup fails or none has run for a while: see status and alerts.

5. Restore

Start with the list of backups; dates are in server time.

neverwiped list

Every restore below goes into a separate folder: nothing on the server is overwritten. Original paths are recreated under the folder: /var/www/site/index.html comes back as /root/restore/var/www/site/index.html.

One file

neverwiped restore latest --include /var/www/site/wp-config.php --target /root/restore
ls -l /root/restore/var/www/site/wp-config.php
cp -a /root/restore/var/www/site/wp-config.php /var/www/site/wp-config.php
rm -rf /root/restore

--include takes the full path as it was on the server; case matters. If ls says No such file, that path is not in this backup: check the spelling, or pick an older backup from neverwiped list. cp -a keeps the owner and permissions the file had. Delete the folder afterwards, otherwise the copy is saved again by the next backup if /root is in your plan.

A folder

Pick the night before the accident (here 3 November), compare, then copy back what you need:

neverwiped restore 2026-11-03 --include /var/www/site/uploads --target /root/restore
diff -rq /root/restore/var/www/site/uploads /var/www/site/uploads
cp -a /root/restore/var/www/site/uploads/. /var/www/site/uploads/

A date means the last backup of that day. You can also give latest, a minute ("2026-11-03 22:59") or an id from neverwiped list. Add --yes if the folder isn’t empty.

A database

Each backup holds a dump of the databases found, as .sql files, made while they run. For PostgreSQL it is pg_dumpall: every database, each consistent on its own, plus the roles. For MySQL and MariaDB it is mysqldump (or mariadb-dump) --all-databases --single-transaction: every database read from one snapshot without locking your site, plus the users and their rights, routines, triggers and events. An export cut off before its end makes the backup fail; it is never kept as a good one. Nothing is re-imported automatically, on purpose.

Which point in time. You can go back to the time of a backup, not to any moment in between: every night on Pro and Business, once a week on Essentiel. Changes made after the last backup are in none of them. Before a risky change (a migration, a big import), run neverwiped backup now.

Pick the backup in neverwiped list (server time), then bring its dumps back into a separate folder. Give the minute shown by list, a day (the last backup of that day) or an id:

neverwiped list
neverwiped restore "2026-11-03 02:16" --include /var/lib/neverwiped/dumps --target /root/restore
ls -l /root/restore/var/lib/neverwiped/dumps

Stop the application that writes to the database first. The files are readable by root only, so your root shell reads them (<); psql -f run as postgres can't open them.

One PostgreSQL database damaged, the others fine (a wrong DELETE, a failed migration). Don't re-import the whole file over databases that still exist: it adds rows to their tables again, or stops halfway with errors. Take out the part of the database you need (here shop), rename the damaged one aside, and import that part alone:

sed -n '/^-- Database "shop" dump$/,/^-- PostgreSQL database dump complete$/p' /root/restore/var/lib/neverwiped/dumps/postgresql-local.sql > /root/restore/shop.sql
grep -c '^CREATE DATABASE' /root/restore/shop.sql
runuser -u postgres -- psql -d postgres -c 'ALTER DATABASE shop RENAME TO shop_damaged'
runuser -u postgres -- psql -v ON_ERROR_STOP=1 -d postgres < /root/restore/shop.sql

grep -c must print 1. 0 means this backup has no database of that name: list them with grep '^-- Database' /root/restore/var/lib/neverwiped/dumps/postgresql-local.sql. If the rename says the database “is being accessed by other users”, the application is still connected. ON_ERROR_STOP=1 stops at the first error instead of carrying on. Check your application, copy from shop_damaged anything recent you still need, then drop it with runuser -u postgres -- psql -d postgres -c 'DROP DATABASE shop_damaged'. This works for any database except postgres and template1, which a dump never recreates: for those, use the whole file on a fresh server.

One MySQL or MariaDB database damaged, the others fine. The file holds every database one after the other, and each table in it starts with DROP TABLE IF EXISTS. Take the settings at the top of the file (character set, time zone, foreign key checks) and the part of your database (here shop), save the damaged database aside, then import that part:

D=/root/restore/var/lib/neverwiped/dumps
grep '^-- Current Database' $D/mysql-local.sql
{ sed -n '1,/^-- Current Database: /p' $D/mysql-local.sql; sed -n '/^-- Current Database: `shop`$/,/^-- Current Database: /p' $D/mysql-local.sql; } > /root/restore/shop.sql
grep -c '^USE `shop`;' /root/restore/shop.sql
mysqldump --single-transaction --routines --events --triggers --databases shop > /root/restore/shop-damaged.sql
mysql < /root/restore/shop.sql

grep -c must print 1. 0 means this backup has no database of that name: the first grep lists the ones it has. mysql stops at the first error. Each table of the backup is dropped and recreated as it was; a table created since the backup stays as it is; users and passwords are not touched. Check your application: shop-damaged.sql holds the database as it was just before, if you need something recent from it. On MariaDB 11 without the mysql and mysqldump commands, type mariadb and mariadb-dump. When root needs a password, these commands read it from /root/.my.cnf; on an older Debian that logs in with /etc/mysql/debian.cnf, add --defaults-extra-file=/etc/mysql/debian.cnf right after mysql and mysqldump.

All the databases are gone, or this is a new server: re-import the whole file. It recreates the roles too; where a role already exists you see role "…" already exists, which is harmless.

# PostgreSQL on the server (all databases, roles included)
runuser -u postgres -- psql postgres < /root/restore/var/lib/neverwiped/dumps/postgresql-local.sql
# MySQL or MariaDB on the server
mysql < /root/restore/var/lib/neverwiped/dumps/mysql-local.sql

On Alpine, which has no runuser, use su postgres -c 'psql postgres' with the same < file, and the same for the other psql lines.

For MySQL and MariaDB, the whole file also brings back the users, their passwords and rights as they were at that backup (a password changed since goes back to the old one), and makes them active (FLUSH PRIVILEGES). Import it into the same product (MySQL into MySQL, MariaDB into MariaDB), at the same or a newer version; on MariaDB, run mariadb-upgrade afterwards if the version is newer. From MySQL to MariaDB or back, import the databases one by one as above and create the users again. Two messages you may meet: Unknown command '\-' at line 1 means the file comes from a recent MariaDB and your client is older, or is MySQL's: remove that first line with sed -i '1{/999999/d}' <file> and import again. Got a packet bigger than 'max_allowed_packet': add --max-allowed-packet=1G after mysql; if the server itself refuses, raise max_allowed_packet in its configuration.

MySQL or MariaDB tables not in InnoDB. The one snapshot covers InnoDB tables, the default engine. If some tables use MyISAM or Aria, the backup log (/var/log/neverwiped.log) says so: those tables are copied as they are when their turn comes, not at the same instant as the rest. List them all, then convert them at a quiet time (the conversion rewrites the table):

mysql -e "SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND ENGINE <> 'InnoDB' AND TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')"
mysql -e 'ALTER TABLE shop.hits ENGINE=InnoDB'

Databases in Docker come back as postgresql-docker-<container>.sql or mysql-docker-<container>.sql; the commands for containers are in the reference. The one-database steps above work the same with docker exec -i <container> sh -c 'exec psql -U "${POSTGRES_USER:-postgres}" -v ON_ERROR_STOP=1 -d postgres' < /root/restore/shop.sql. For MySQL or MariaDB in a container, build shop.sql the same way from mysql-docker-<container>.sql and import it with the container command of the reference in place of mysql. When you are done, rm -rf /root/restore: the dumps would otherwise be saved again by the next backup if /root is in your plan.

A Docker volume

How a volume is backed up. neverwiped status lists your volumes and how each one is taken:

  • live (the default for uploads, files and configuration): read while its containers keep running. In the backup it sits at /var/lib/docker/volumes/<name>/_data.
  • copied with its containers paused (proposed at install for Redis, Valkey, MongoDB, Elasticsearch, OpenSearch, InfluxDB, RabbitMQ, Vaultwarden and similar): the running containers that use the volume are paused (docker pause: frozen, not stopped or restarted), the volume is copied on the same disk, they resume, then the copy is uploaded. The pause lasts as long as the local copy, a few seconds for a few GB. In the backup it sits at /var/lib/neverwiped/volumes/<name>. The copy is what a power cut at that instant would leave on disk: an application with a journal (Redis, MongoDB, SQLite…) starts from it as after a crash.

The paused copy needs free space for the volume plus 10 % and 1 GB on the disk of /var/lib/neverwiped, and is stopped after 5 minutes. When it can't be made, the volume is backed up live that time and the backup is marked failed with the reason, so you see it. Change the way a volume is taken with neverwiped install --yes --pause-volume <name> or --live-volume <name> (repeat the flags whenever you run install again). Volumes of PostgreSQL, MySQL and MariaDB containers are not copied as files at all: their export is (A database). Folders mounted from the host (./data:/data) are backed up with that folder, live.

Bring a volume back. Restore it into a separate folder; the CLI prints where it came back:

V=app_uploads
neverwiped restore latest --include /var/lib/docker/volumes/$V/_data --target /root/restore   # a live volume
neverwiped restore latest --include /var/lib/neverwiped/volumes/$V --target /root/restore      # a paused copy
SRC=/root/restore/var/lib/docker/volumes/$V/_data      # or /root/restore/var/lib/neverwiped/volumes/$V

Then stop what uses it, keep its current content aside, and copy the backup in:

docker ps -a --filter volume=$V                 # the containers that use it
docker compose stop                             # in the application's folder (or: docker stop <container>…)
MP=$(docker volume inspect -f '{{.Mountpoint}}' $V)
mkdir -p /root/volume-before && cp -a "$MP/." /root/volume-before/
test -d "$MP/" && find "$MP" -mindepth 1 -delete
cp -a "$SRC/." "$MP/"
docker compose start                            # (or: docker start <container>…)

Owners, permissions and links come back as they were. The volume no longer exists (new server, docker compose down -v): run docker compose up --no-start in the application's folder first, which creates the volume and the containers without starting them, then the three last lines (or docker volume create $V without Compose). A volume with a 64-character name is anonymous: it belongs to one container and gets a new name when the container is re-created, so copy into the new container's volume (docker inspect -f '{{range .Mounts}}{{.Name}} {{end}}' <container>). Don't restore a live volume in place (without --target) while its containers run: the files would change under the application, and files created since the backup would stay. When everything works, rm -rf /root/restore /root/volume-before.

A whole server

The server still runs (bad update, broken config): restore in place. This overwrites the saved paths with their version from that night. Stop your applications first.

neverwiped restore 2026-11-03

The agent shows what will be overwritten and asks you to type RESTAURER (French for “restore”). Without a terminal it refuses, unless you add --yes, which counts as an explicit OK to overwrite.

The server is lost (deleted, broken disk, no access any more). You need two things: an account API key (dashboard › API keys › Create) and your recovery key. Everything happens on the new server, without support. Install your base software there first (web server, database server) with the package manager, as on the old one.

  1. Install the agent only, without registering a new server:
curl -fsSL https://neverwiped.com/install.sh | NEVERWIPED_NO_SETUP=1 sh
  1. Give this server access to the lost server’s backups:
neverwiped recover

It asks for the API key, shows the servers of your account (choose the lost one), asks for the recovery key (hidden while you paste it) and asks you to confirm. It then revokes the lost server’s storage login and agent key: if the old machine still runs, or comes back, it can no longer read or add backups. Your recovery key becomes this server’s key (/etc/neverwiped/repo.key) and is never sent to us; keep your copy, no new one is created. You get an email about it.

If the key is refused (the recovery key does not open the backups of …), nothing is kept on the server: check the key (6 groups of 6 characters) and run neverwiped recover again. The dashboard then shows that server under the new server’s name; to name it with --server, use its ID.

With several servers, recover first compares your key with the check code of the server you chose (the code shown with the key at install and on the server’s page). A key of another server is refused before anything is revoked, and the message names the server it belongs to. See several servers.

In a script (no terminal), pass everything as options. Put the recovery key in a root-only file first (cat, paste, Enter, Ctrl-D), so it stays out of your shell history:

umask 077
cat > /root/recovery.key
NEVERWIPED_API_KEY=nw_acc_YOUR_KEY neverwiped recover --server OLD-SERVER-NAME --recovery-key-file /root/recovery.key --yes
rm -f /root/recovery.key
  1. Restore everything into a folder. Nothing on the new server is overwritten:
neverwiped list
neverwiped restore latest --target /root/restore
  1. Put back what you need. This is a new machine: copy your data and application folders, and the configuration files of your software one by one, not the whole system. For example:
cp -a /root/restore/var/www/. /var/www/
cp -a /root/restore/etc/nginx/sites-available/example.com /etc/nginx/sites-available/

Do not copy /root/restore/etc over /etc, and do not run neverwiped restore in place on a new server. It would bring back the old machine’s network, disks (fstab) and SSH settings, and /etc/neverwiped, the lost server’s agent configuration whose keys recover has just revoked: the agent would stop working. Then re-import the databases with the “new server” lines of a database (the files are in /root/restore/var/lib/neverwiped/dumps), and start your applications.

  1. Clean up, then resume backups on this server, into the same repository (no new server, no new recovery key):
rm -rf /root/restore
neverwiped install --yes

Remove the folder first: otherwise the restored copy goes into the next backup if /root is in your plan. neverwiped list then shows the old server’s backups next to the new ones; you can still restore from the old ones.

Last resort, restic alone, without the agent (no access to your account any more, for example): write to support from your account’s email with the name of the lost server. Support issues a new storage login for that repository, through a safe channel, and invalidates the old one. Support can’t read your backups: it doesn’t have the key. Then, on the new server, install restic 0.18.1 and check its fingerprint (arm64: restic_0.18.1_linux_arm64.bz2, fingerprint 87f53fddde38764095e9c058a3b31834052c37e5826d2acf34e18923c006bd45), and restore with the recovery key (replace the capitals with your key and the login support gave you):

curl -fsSLO https://github.com/restic/restic/releases/download/v0.18.1/restic_0.18.1_linux_amd64.bz2
echo "680838f19d67151adba227e1570cdd8af12c19cf1735783ed1ba928bc41f363d  restic_0.18.1_linux_amd64.bz2" | sha256sum -c
bunzip2 restic_0.18.1_linux_amd64.bz2 && install -m 0755 restic_0.18.1_linux_amd64 /usr/local/bin/restic
umask 077
printf '%s' 'YOUR-RECOVERY-KEY' > /root/recovery.key
export RESTIC_REPOSITORY='rest:https://LOGIN:PASSWORD@storage.neverwiped.com/LOGIN/'
export RESTIC_PASSWORD_FILE=/root/recovery.key
restic snapshots
restic restore latest --target /root/restore
rm -f /root/recovery.key

Then continue from step 4.

Without the recovery key and without the old server, the backups can’t be read by anyone, us included. That is the price of encryption we can’t open.

Through an AI agent: plan, then approve

When an AI agent (Claude Code, Codex, Cursor) restores for you, have it ask with a plan first. --plan writes nothing, takes no lock and doesn’t contact NeverWiped:

neverwiped restore latest --include /var/www/site/uploads --target /root/restore --plan

It prints which backup (date and ID), which paths, where it would write (a separate folder, empty or not, or in place), an approval code and the exact command that runs this restore. The agent shows you that plan and waits. After your OK it runs the printed command unchanged, for example (the ID and the code are examples):

neverwiped restore 8b28999a --include /var/www/site/uploads --target /root/restore --approve K7QD-2M9F

Before writing anything, the NeverWiped agent recomputes the code from the backup, the folder and the paths. If one of them differs from the plan you approved (latest now points to a newer backup, a folder or a path was changed), it refuses and nothing is written: ask for a new plan. The printed command names the backup by its ID, so a backup that runs between your OK and the restore doesn’t change what comes back.

The code is not a password and doesn’t stop anyone with root from restoring without it. It ties your OK to one plan. Copying files back from /root/restore over live files is a separate step, and a separate OK. The AI agents page has a prompt for this.

6. Test a restore today

A backup you have never restored is a hope. Do this on day one, then once a month:

neverwiped restore latest --target /root/test-restore --yes
ls -R /root/test-restore | head
rm -rf /root/test-restore

Then check that you can find the recovery key in your password manager.

Next