Dans cet article

Un serveur d’automatisation ne vaut que si ses identifiants, son historique d’exécution et sa configuration survivent à une mise à jour ratée ou à la perte d’un Droplet. Limitez les accès, créez une sauvegarde chiffrée, puis restaurez-la dans un environnement sûr.

Consultez les éléments d’une sauvegarde complète n8n et les droits Spaces. Le nettoyage révisé dispose uniquement de tests hors ligne avec pannes simulées. Il supprime les archives temporaires lors des erreurs gérées ; cette suppression ne garantit pas un effacement sécurisé, et une coupure ou SIGKILL peut laisser des fichiers. Une restauration réelle complète reste un exercice distinct obligatoire.

Les commandes ci-dessous ciblent le Server CLI n8n 2.x. Vérifiez --version et unpublish:workflow --help dans l’image enregistrée avant de poursuivre. La référence Server CLI décrit l’export/import d’entités et unpublish:workflow --all ; update:workflow est déprécié depuis la version 2.0. Si l’image est plus ancienne ou ne propose pas une commande, arrêtez-vous et adaptez la procédure à son aide, sans mise à niveau pendant la récupération. Aucun environnement n8n installé n’a été exécuté pendant cette revue.

Définir la reprise avant le premier workflow

n8n n’est pas seulement un outil visuel d’automatisation. Il stocke les définitions de workflow, les identifiants chiffrés avec la clé de l’instance, les données d’exécution et la configuration qui indique où les retours d’appel doivent arriver. Une capture de l’éditeur n’est pas une sauvegarde. Une reprise réussie signifie qu’un nouveau serveur démarre avec la même clé de chiffrement, charge un workflow choisi et exécute un test inoffensif sans reconnecter chaque service.

Ce guide utilise l’application n8n en un clic de DigitalOcean sur un Droplet et un compartiment Spaces privé pour les sauvegardes chiffrées. Cette installation n’a pas de basculement automatique. N’ajoutez pas de dossiers clients, de clés API de production ou d’actions financières à un workflow avant d’avoir testé sa gestion des erreurs habituelles et cette procédure de reprise.

  • Un domaine géré par l’équipe, comme n8n.example.com, et l’accès à ses enregistrements DNS.
  • Un compte DigitalOcean qui peut créer un Droplet, un pare-feu, un compartiment Spaces et une clé Spaces limitée.
  • Un administrateur Linux qui sait utiliser SSH et peut conserver une clé de récupération hors ligne.
  • Un Droplet de test neuf et séparé pour la restauration.

Déployer l’image n8n, puis terminer la limite d’accès

L’application Marketplace déploie un Droplet avec la version stable actuelle de n8n et demande de faire pointer un enregistrement DNS A vers ce serveur avant de terminer la configuration. Utilisez une clé SSH pour le compte d’administration. N’activez pas un mot de passe SSH simplement pour rendre la première connexion plus facile.

Créez un pare-feu cloud avant d’inviter une autre personne. Autorisez TCP 80 et 443 pour le service web. Autorisez TCP 22 seulement depuis la sortie de votre VPN ou une adresse IP d’administration fixe. Activez la supervision et les sauvegardes du Droplet, mais considérez-les comme une couche distincte. La sauvegarde de ce guide protège les données et la configuration n8n sous une forme transportable.

Commande shell
ssh ADMIN_USER@YOUR_DROPLET_IP
cd /opt/n8n-docker-caddy
sudo docker compose ps
sudo docker compose config --services
N8N_CONTAINER=$(sudo docker compose ps -q n8n)
N8N_IMAGE_ID=$(sudo docker inspect --format '{{.Image}}' "$N8N_CONTAINER")
sudo docker image inspect --format '{{json .RepoDigests}}' "$N8N_IMAGE_ID"
sudo docker volume ls
sudo ls -la

Garder stables l’adresse de retour et la clé de chiffrement

Terminez la configuration initiale sur votre domaine HTTPS, pas en gardant l’adresse IP du serveur dans des favoris. Dans le dossier de déploiement, examinez les fichiers Compose et le fichier d’environnement générés avant de les modifier. Définissez l’hôte public, le protocole et l’URL de webhook sur l’adresse HTTPS définitive avec les noms de variables documentés par la version n8n installée. Redémarrez la pile Compose seulement après avoir enregistré une copie protégée de la configuration actuelle.

Dans la disposition Docker et Caddy standard, le volume de données n8n contient la base SQLite et son matériel de chiffrement. L’archive de ce volume est le registre de reprise des identifiants. Si vous définissez N8N_ENCRYPTION_KEY hors de ce volume, conservez aussi l’archive de configuration protégée. Renouveler une clé est une tâche de maintenance volontaire, pas une étape de dépannage. Une sauvegarde sans la clé correspondante peut afficher des identifiants qu’elle ne peut pas utiliser.

  • Conservez les secrets dans l’environnement du serveur ou un gestionnaire de secrets protégé, jamais dans une note de workflow ni un dépôt Git.
  • Désignez un responsable pour chaque identifiant de production et retirez les identifiants qu’aucun workflow n’utilise.
  • Configurez la purge des exécutions avant que l’archive dépasse la fenêtre de sauvegarde ou le budget de stockage approuvé.

Créer un compartiment privé et une clé réservée à la sauvegarde

Créez un compartiment Spaces privé, par exemple n8n-recovery-your-team, dans une région choisie délibérément. N’activez ni listing public ni CDN. Activez le versionnage, puis créez une règle de cycle de vie qui conserve les archives principales pendant une durée approuvée par l’équipe. Quatre-vingt-dix jours sont un bon premier choix sans objectif de reprise défini. Créez ensuite une clé Spaces séparée et limitée à ce compartiment. Les clés limitées DigitalOcean proposent Read ou Read/Write/Delete. La tâche d’envoi exige sa propre clé Read/Write/Delete, car il n’existe pas de clé plus limitée en écriture seule. Conservez cette clé dans un fichier lisible par root sur le Droplet, avec le mode 600. Gardez aussi la clé publique du destinataire age ici. Conservez la clé privée correspondante hors du Droplet, par exemple dans un gestionnaire de mots de passe d’équipe avec un accès de reprise.

La sauvegarde reste illisible dans Spaces sans l’identité age hors ligne. Cela limite les dégâts si une clé Spaces fuit, mais cela signifie aussi qu’il faut vérifier que la personne de reprise peut utiliser cette identité avant de considérer la tâche comme fiable.

Sur votre poste d'administration, installez age puis créez l'identité avec age-keygen -o n8n-recovery-key.txt. Protégez ce fichier dans votre gestionnaire de mots de passe. La commande age-keygen -y n8n-recovery-key.txt affiche le destinataire public age1... à copier sur le Droplet. La clé privée reste sur votre poste jusqu'au test de restauration.

Commande shell
sudo apt-get update
sudo apt-get install -y awscli age
sudo install -d -m 700 /etc/n8n-recovery
sudo nano /etc/n8n-recovery/spaces.env
# Ajoutez AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY, puis enregistrez.
sudo chmod 600 /etc/n8n-recovery/spaces.env
sudo nano /etc/n8n-recovery/age-recipient.txt
# Ajoutez un destinataire public age1..., puis enregistrez.
sudo chmod 600 /etc/n8n-recovery/age-recipient.txt

Installer age

Sauvegarder le vrai volume persistant, pas seulement le fichier Compose

Cette procédure couvre SQLite avec la clé de chiffrement dans le fichier config du volume .n8n et les fichiers locaux sous APP_DIR/local_files. Le Caddyfile peut être à la racine ou dans caddy_config. Si N8N_ENCRYPTION_KEY vient de l’environnement, ou si un montage persistant sort de APP_DIR, adaptez et testez la sauvegarde avant de continuer. Notez le digest exact de l’image avec docker inspect et gardez-le avec le lot.

La disposition Compose de l’application en un clic peut changer avec le temps. Ne copiez pas le nom d’un volume depuis cet article. Utilisez docker volume ls et la configuration Compose relevée plus haut pour identifier le volume qui contient les données n8n. Ce guide couvre la disposition Marketplace Docker et Caddy avec des données n8n persistantes dans un volume. Si vous la remplacez volontairement par PostgreSQL, utilisez la procédure documentée d’export et de restauration du fournisseur de base, et testez-la comme un plan de reprise séparé.

Le script ci-dessous arrête brièvement la pile Compose, archive le volume n8n nommé et la configuration de déploiement, chiffre les deux avec votre destinataire public age, puis envoie les fichiers chiffrés. Définissez VOLUME_NAME seulement après avoir inspecté votre serveur. Le script refuse volontairement de le deviner. Il construit un manifeste de configuration à partir des fichiers réellement présents, inclut la configuration Caddy lorsqu’elle existe et échoue s’il ne trouve pas de fichier Compose. Exécutez-le une fois à la main avant de le planifier.

Enregistrez le script ci-dessous sous /usr/local/sbin/backup-n8n-to-spaces avec l'éditeur serveur. Exécutez ensuite les commandes d'installation et le premier test avant de créer la planification.

/usr/local/sbin/backup-n8n-to-spaces
#!/usr/bin/env bash
set -euo pipefail
umask 077

APP_DIR=/opt/n8n-docker-caddy
VOLUME_NAME=REPLACE_WITH_THE_N8N_DATA_VOLUME
BUCKET=YOUR_PRIVATE_BUCKET
REGION=YOUR_SPACES_REGION
PREFIX=n8n
BACKUP_DIR=/var/backups/n8n

case "$VOLUME_NAME" in
  REPLACE_*) echo "Set VOLUME_NAME after docker volume ls" >&2; exit 1 ;;
esac

set -a
. /etc/n8n-recovery/spaces.env
set +a
export AWS_DEFAULT_REGION=us-east-1
install -d -m 700 "$BACKUP_DIR"
WORK_DIR=$(mktemp -d "$BACKUP_DIR/run.XXXXXXXX")
RESTART_REQUIRED=0
cleanup() {
  status=$?
  trap - EXIT HUP INT TERM
  set +e
  if (( RESTART_REQUIRED )); then
    sudo docker compose start
    restart_status=$?
    if (( restart_status != 0 )); then
      echo "ERROR: n8n restart failed; investigate immediately" >&2
      status=1
    fi
  fi
  if ! rm -rf -- "$WORK_DIR"; then
    echo "ERROR: temporary archive cleanup failed; inspect $WORK_DIR" >&2
    status=1
  fi
  if (( status != 0 )); then
    echo "ERROR: n8n backup failed; no new success marker was written" >&2
  fi
  exit "$status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

STAMP=$(date -u +%Y-%m-%dT%H-%M-%SZ)
VOLUME_ARCHIVE="$WORK_DIR/n8n-volume-$STAMP.tar.gz"
CONFIG_ARCHIVE="$WORK_DIR/n8n-config-$STAMP.tar.gz"
RECIPIENT=$(cat /etc/n8n-recovery/age-recipient.txt)

cd "$APP_DIR"
CONFIG_INPUTS=()
for path in .env compose.yml docker-compose.yml Caddyfile caddy_config local_files; do
  [[ -e "$path" ]] && CONFIG_INPUTS+=("$path")
done
if [[ ! -f compose.yml && ! -f docker-compose.yml ]]; then
  echo "No Compose file found in $APP_DIR" >&2
  exit 1
fi
[[ -f .env ]] || { echo "Missing required .env in $APP_DIR" >&2; exit 1; }

# Set first so cleanup also retries start if stop only partly succeeds.
RESTART_REQUIRED=1
sudo docker compose stop
sudo docker run --rm -v "$VOLUME_NAME":/data:ro -v "$WORK_DIR":/backup alpine \
  sh -c "tar -C /data -czf /backup/$(basename "$VOLUME_ARCHIVE") ."
sudo tar -C "$APP_DIR" -czf "$CONFIG_ARCHIVE" "${CONFIG_INPUTS[@]}"
sudo docker compose start
RESTART_REQUIRED=0

age -r "$RECIPIENT" -o "$VOLUME_ARCHIVE.age" "$VOLUME_ARCHIVE"
age -r "$RECIPIENT" -o "$CONFIG_ARCHIVE.age" "$CONFIG_ARCHIVE"
aws s3 cp "$VOLUME_ARCHIVE.age" "s3://$BUCKET/$PREFIX/" --endpoint-url "https://$REGION.digitaloceanspaces.com" --only-show-errors
aws s3 cp "$CONFIG_ARCHIVE.age" "s3://$BUCKET/$PREFIX/" --endpoint-url "https://$REGION.digitaloceanspaces.com" --only-show-errors
# Written only after both uploads finish. A successful upload is not a restore test.
date -u +%s > "$WORK_DIR/last-success"
mv "$WORK_DIR/last-success" "$BACKUP_DIR/last-success"
Commande shell
sudo chown root:root /usr/local/sbin/backup-n8n-to-spaces
sudo chmod 700 /usr/local/sbin/backup-n8n-to-spaces
sudo /usr/local/sbin/backup-n8n-to-spaces

Planifier la tâche et rendre un échec visible

Enregistrez ce bloc dans /etc/cron.d/n8n-backup, pas dans crontab -e. Définissez root:root et le mode 644 sur ce fichier, avec un saut de ligne final. timedatectl affiche le fuseau du serveur. La ligne utilise cette heure locale, pas nécessairement UTC.

Une sauvegarde qui échoue sans bruit est pire que l’absence de sauvegarde parce qu’elle donne une fausse confiance. Commencez avec une exécution quotidienne et un journal, puis reliez les échecs au système d’alerte que votre équipe regarde déjà. Choisissez la fréquence selon l’état d’automatisation que vous pouvez perdre. Pour un workflow qui crée des dossiers clients toutes les quelques minutes, une exécution quotidienne n’est probablement pas suffisante.

Toutes les quelques semaines, listez le préfixe du compartiment et comparez les deux archives les plus récentes avec le planning. Les archives du volume et de la configuration doivent avoir le même horodatage. Examinez une paire manquante avant la prochaine modification habituelle du serveur.

Configurez une vraie alerte avant d’activer cron. MAILTO nécessite un transport de courrier opérationnel et une boîte surveillée. Testez la réception avec un échec volontaire et un marqueur périmé. Un journal seul ne suffit pas. Le seuil de 30 heures ci-dessous est un exemple pour cette planification quotidienne ; adaptez-le à votre objectif de récupération. Surveillez aussi la panne complète du serveur depuis un autre système.

/usr/local/sbin/check-n8n-backup-age
#!/usr/bin/env bash
set -euo pipefail
marker=/var/backups/n8n/last-success
max_age_seconds=108000 # 30 hours for a daily job; set from your recovery objective.
if [[ ! -s "$marker" ]]; then
  echo 'ERROR: no successful n8n backup marker' >&2
  exit 1
fi
stamp=$(cat "$marker")
[[ "$stamp" =~ ^[0-9]+$ ]] || { echo 'ERROR: invalid backup marker' >&2; exit 1; }
now=$(date -u +%s)
if (( stamp > now || now - stamp > max_age_seconds )); then
  echo 'ERROR: n8n backup is stale or server clock needs investigation' >&2
  exit 1
fi
Commande shell
sudo chown root:root /usr/local/sbin/check-n8n-backup-age
sudo chmod 700 /usr/local/sbin/check-n8n-backup-age
/etc/cron.d/n8n-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=REPLACE_WITH_YOUR_APPROVED_BACKUP_ALERT_MAILBOX
23 3 * * * root /usr/local/sbin/backup-n8n-to-spaces >> /var/log/n8n-recovery.log 2>&1 || { echo 'n8n backup failed; inspect the restricted server log'; exit 1; }
17 * * * * root /usr/local/sbin/check-n8n-backup-age
Commande shell
sudo bash -c 'set -a; . /etc/n8n-recovery/spaces.env; set +a; export AWS_DEFAULT_REGION=us-east-1; aws s3 ls s3://YOUR_PRIVATE_BUCKET/n8n/ --endpoint-url https://YOUR_SPACES_REGION.digitaloceanspaces.com'

Restaurer dans un environnement de test isolé avant d’en avoir besoin

Utilisez un Droplet de test neuf. Cette restauration crée une configuration Compose séparée avec le port 5678 lié à localhost et un réseau Docker sans sortie. Elle ne redémarre pas le Caddy ni la configuration Compose de production. Ouvrez le tableau de bord par tunnel SSH, sans DNS ni certificat public.

Cette procédure suppose la clé de chiffrement dans le volume .n8n et les fichiers locaux dans le lot. Elle ne couvre pas une clé injectée par environnement ni des montages externes. Téléchargez les images avant de bloquer le trafic sortant dans le pare-feu du test. Désactivez les workflows avant le premier démarrage. Vérifiez une donnée connue et un test manuel sans effet externe, puis détruisez le Droplet de test.

Commande shell
sudo -i
set -euo pipefail
apt-get update
apt-get install -y awscli age docker.io docker-compose-v2
docker compose version
install -d -m 700 /etc/n8n-recovery
nano /etc/n8n-recovery/restore-spaces.env
chmod 600 /etc/n8n-recovery/restore-spaces.env
set -a
. /etc/n8n-recovery/restore-spaces.env
set +a
export AWS_DEFAULT_REGION=us-east-1
STAMP=YYYY-MM-DDTHH-MM-SSZ
BUCKET=YOUR_PRIVATE_BUCKET
REGION=YOUR_SPACES_REGION
RESTORE_DIR=/srv/n8n-restore
[[ ! -e "$RESTORE_DIR" ]] || { echo 'Use a fresh restore directory'; exit 1; }
umask 077
install -d -m 700 "$RESTORE_DIR"
cd "$RESTORE_DIR"

aws s3 cp "s3://$BUCKET/n8n/n8n-volume-$STAMP.tar.gz.age" . --endpoint-url "https://$REGION.digitaloceanspaces.com"
aws s3 cp "s3://$BUCKET/n8n/n8n-config-$STAMP.tar.gz.age" . --endpoint-url "https://$REGION.digitaloceanspaces.com"
age -d -i /secure-path/n8n-recovery-key.txt -o n8n-volume.tar.gz "n8n-volume-$STAMP.tar.gz.age"
age -d -i /secure-path/n8n-recovery-key.txt -o n8n-config.tar.gz "n8n-config-$STAMP.tar.gz.age"
mkdir recovered-config
tar -xzf n8n-config.tar.gz -C recovered-config
tar -tzf n8n-volume.tar.gz | sed -n '1,40p'
# Keep recovered configuration for reference. Never source its .env or start its Compose stack.

# Copy the exact image digest recorded on production before the backup.
N8N_IMAGE=docker.n8n.io/n8nio/n8n@sha256:REPLACE_WITH_RECORDED_DIGEST
[[ "$N8N_IMAGE" =~ @sha256:[a-f0-9]{64}$ ]] || { echo 'Set the recorded image digest'; exit 1; }
docker pull "$N8N_IMAGE"
docker pull alpine:3.22
RESTORE_VOLUME=n8n_restore_data
if docker volume inspect "$RESTORE_VOLUME" >/dev/null 2>&1; then echo 'Refusing an existing volume'; exit 1; fi
docker volume create "$RESTORE_VOLUME"
docker run --rm -v "$RESTORE_VOLUME":/data -v "$RESTORE_DIR":/backup:ro alpine:3.22 tar -C /data -xzf /backup/n8n-volume.tar.gz
mkdir -p recovered-config/local_files
cat > compose.recovery.yml <<EOF
services:
  n8n:
    image: $N8N_IMAGE
    restart: "no"
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      N8N_HOST: localhost
      N8N_PORT: "5678"
      N8N_PROTOCOL: http
      WEBHOOK_URL: http://localhost:5678/
      N8N_SECURE_COOKIE: "false"
      N8N_DIAGNOSTICS_ENABLED: "false"
      N8N_VERSION_NOTIFICATIONS_ENABLED: "false"
    volumes:
      - n8n_data:/home/node/.n8n
      - ./recovered-config/local_files:/files:ro
    networks: [recovery]
volumes:
  n8n_data:
    external: true
    name: $RESTORE_VOLUME
networks:
  recovery:
    internal: true
EOF
docker compose -p n8n-recovery -f compose.recovery.yml config --quiet

# First deny outbound traffic in the separate test Cloud Firewall. Keep inbound SSH restricted to your IP.
# All required images were pulled in the previous step.
docker compose -p n8n-recovery -f compose.recovery.yml run --rm --no-deps n8n unpublish:workflow --all
docker compose -p n8n-recovery -f compose.recovery.yml up -d --pull never
curl --fail --connect-timeout 3 --max-time 5 --retry 30 --retry-delay 2 --retry-connrefused --retry-max-time 90 http://127.0.0.1:5678/healthz

Sur votre poste administrateur:

Commande shell
ssh -N -L 5678:127.0.0.1:5678 ADMIN_USER@TEST_DROPLET_IP
# Keep this terminal open and visit http://localhost:5678 in your browser.

Savoir quand cette petite installation ne suffit plus

Dépassez le Droplet unique lorsque vous avez besoin d’un objectif de délai de reprise documenté, de plusieurs opérateurs qui modifient des workflows, d’un grand volume d’exécutions ou d’un workflow dont l’arrêt peut créer un problème matériel pour un client ou une opération financière. À ce stade, utilisez une base de données gérée lorsque c’est adapté, testez les sauvegardes de base séparément, gérez les secrets hors du Droplet et notez qui peut approuver une reprise.

N’utilisez pas un workflow n8n comme unique moyen de sauvegarder n8n. Le travail de sauvegarde doit rester hors de l’application qu’il protège. Cette séparation simple permet de récupérer le service lorsque c’est le service de workflow qui a échoué.

Questions fréquentes

Une sauvegarde de Droplet suffit-elle pour n8n ?

Elle est utile, mais dépend d’un seul mécanisme de récupération de plateforme. Gardez aussi une archive chiffrée distincte du volume de données n8n et de la configuration dans un stockage objet privé, puis testez sa restauration dans un environnement neuf.

Pourquoi la clé de chiffrement n8n doit-elle figurer dans le plan de reprise ?

n8n utilise cette clé pour déchiffrer les identifiants stockés. Sans la clé correspondante, un stockage restauré peut contenir des identifiants qui ne peuvent pas être utilisés.

Puis-je sauvegarder une installation n8n avec PostgreSQL avec le script de volume ?

Pas à lui seul. Arrêtez l’application pendant la fenêtre de sauvegarde et ajoutez un export de base compatible avec la version, ainsi qu’un test de restauration. Considérez l’export de base et la configuration de l’application comme un seul ensemble de reprise.