Exécuter un serveur n8n privé que votre équipe sait vraiment restaurer
Déployez n8n sur un Droplet DigitalOcean, limitez les accès, sauvegardez ses données chiffrées dans un compartiment Spaces privé et prouvez une restauration avant qu’une panne ne devienne une urgence.
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.
- Remplacez chaque valeur surlignée avant d’exécuter la commande.
ssh ADMIN_USER@YOUR_DROPLET_IPL’invite du shell distant apparaît avant de continuer.
cd /opt/n8n-docker-caddysudo docker compose pssudo docker compose config --servicesN8N_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 lssudo 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.
sudo apt-get updatesudo apt-get install -y awscli agesudo install -d -m 700 /etc/n8n-recoverysudo nano /etc/n8n-recovery/spaces.envEnregistrez les variables d’accès, quittez l’éditeur, puis continuez.
sudo chmod 600 /etc/n8n-recovery/spaces.envsudo nano /etc/n8n-recovery/age-recipient.txtEnregistrez un destinataire public age1, quittez l’éditeur, puis continuez.
sudo chmod 600 /etc/n8n-recovery/age-recipient.txt
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/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"
STAMP=$(date -u +%Y-%m-%dT%H-%M-%SZ)
VOLUME_ARCHIVE="$BACKUP_DIR/n8n-volume-$STAMP.tar.gz"
CONFIG_ARCHIVE="$BACKUP_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
for required in .env; do
[[ -f "$required" ]] || { echo "Missing required $required in $APP_DIR" >&2; exit 1; }
done
sudo docker compose stop
trap "sudo docker compose start" EXIT
sudo docker run --rm -v "$VOLUME_NAME":/data:ro -v "$BACKUP_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
trap - EXIT
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
rm -f "$VOLUME_ARCHIVE" "$CONFIG_ARCHIVE" "$VOLUME_ARCHIVE.age" "$CONFIG_ARCHIVE.age"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-spacesPlanifier 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.
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
23 3 * * * root /usr/local/sbin/backup-n8n-to-spaces >> /var/log/n8n-recovery.log 2>&1
- Remplacez chaque valeur surlignée avant d’exécuter la commande.
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.
Avant de continuer : Exécutez cette procédure uniquement sur une machine de test neuve. Elle installe des paquets, restaure un nouveau volume Docker et démarre n8n sur localhost avec un réseau interne.
sudo -iset -euo pipefail apt-get update apt-get install -y awscli age docker.io docker-compose-v2docker compose versioninstall -d -m 700 /etc/n8n-recoverynano /etc/n8n-recovery/restore-spaces.envchmod 600 /etc/n8n-recovery/restore-spaces.env set -a . /etc/n8n-recovery/restore-spaces.env set +a export AWS_DEFAULT_REGION=us-east-1- Remplacez chaque valeur surlignée avant d’exécuter la commande.
STAMP=YYYY-MM-DDTHH-MM-SSZ - Remplacez chaque valeur surlignée avant d’exécuter la commande.
BUCKET=YOUR_PRIVATE_BUCKET - Remplacez chaque valeur surlignée avant d’exécuter la commande.
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"- Remplacez chaque valeur surlignée avant d’exécuter la commande.
age -d -i /secure-path/n8n-recovery-key.txt -o n8n-volume.tar.gz "n8n-volume-$STAMP.tar.gz.age" - Remplacez chaque valeur surlignée avant d’exécuter la commande.
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.- Remplacez chaque valeur surlignée avant d’exécuter la commande.
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 update:workflow --all --active=false 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- Remplacez chaque valeur surlignée avant d’exécuter la commande.
ssh -N -L 5678:127.0.0.1:5678 ADMIN_USER@TEST_DROPLET_IP
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é.
Vérifier le résultat
- Résultat attendu
- Par le tunnel SSH sur http://localhost:5678, un administrateur ouvre un workflow connu et exécute un test manuel inoffensif, avec le trafic sortant bloqué.
- Arrêter si
- Arrêtez si les archives n’ont pas le même horodatage, si la clé de chiffrement manque, si la cible est le Droplet de production, si le pare-feu de test autorise encore le trafic sortant avant la désactivation des workflows ou si un identifiant récupéré apparaît dans un journal ou une capture.
- Étape suivante
- Consignez la date et le résultat de la restauration, renouvelez tout identifiant exposé pendant le test et détruisez l’environnement temporaire.