← Tous les guidesGitHub
Automatisation des flux

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.

Lire l’articleGitHub: n8n-io/n8n-docker-caddy

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.

  1. Se connecter au DropletTerminal local
    Remplacez chaque valeur surlignée avant d’exécuter la commande.
    ssh ADMIN_USER@YOUR_DROPLET_IP

    L’invite du shell distant apparaît avant de continuer.

  2. Ouvrir le dossier déployéDroplet connecté
    cd /opt/n8n-docker-caddy
  3. Vérifier les services Compose
    sudo docker compose ps
  4. Lister les services configurés
    sudo docker compose config --services
  5. Trouver le conteneur n8n
    N8N_CONTAINER=$(sudo docker compose ps -q n8n)
  6. Lire l’identifiant de l’image active
    N8N_IMAGE_ID=$(sudo docker inspect --format '{{.Image}}' "$N8N_CONTAINER")
  7. Conserver le digest exact avec l’horodatage de sauvegarde
    sudo docker image inspect --format '{{json .RepoDigests}}' "$N8N_IMAGE_ID"
  8. Lister les volumes Docker
    sudo docker volume ls
  9. Examiner les fichiers déployés
    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.

  1. Actualiser les métadonnées des paquetsDroplet connecté
    sudo apt-get update
  2. Installer les outils de sauvegarde
    sudo apt-get install -y awscli age
  3. Créer le dossier protégé
    sudo install -d -m 700 /etc/n8n-recovery
  4. Ouvrir le fichier d’environnement SpacesÉditeur de fichiers
    sudo nano /etc/n8n-recovery/spaces.env

    Enregistrez les variables d’accès, quittez l’éditeur, puis continuez.

  5. Protéger le fichier d’environnement SpacesDroplet connecté
    sudo chmod 600 /etc/n8n-recovery/spaces.env
  6. Ouvrir le fichier du destinataire ageÉditeur de fichiers
    sudo nano /etc/n8n-recovery/age-recipient.txt

    Enregistrez un destinataire public age1, quittez l’éditeur, puis continuez.

  7. Protéger le fichier du destinataire ageDroplet connecté
    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/local/sbin/backup-n8n-to-spacesContenu du fichier
Remplacez chaque valeur surlignée avant d’exécuter la commande.
#!/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"
Rendre le script exécutable et lancer la première sauvegardeDroplet connecté
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.

/etc/cron.d/n8n-backup : 03:23, heure du serveurContenu du fichier
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
  1. Lister les archives avec rootDroplet connecté
    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.

  1. Ouvrir un shell root sur le Droplet de testMachine de test isolée
    sudo -i
  2. Installer les outils de restauration
    set -euo pipefail
    apt-get update
    apt-get install -y awscli age docker.io docker-compose-v2
  3. Vérifier Docker Compose v2
    docker compose version
  4. Créer le dossier protégé des clés
    install -d -m 700 /etc/n8n-recovery
  5. Enregistrer la clé en lecture puis quitterÉditeur de fichiers
    nano /etc/n8n-recovery/restore-spaces.env
  6. Protéger et charger la clé en lectureMachine de test isolée
    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
  7. Définir l’horodatage choisi
    Remplacez chaque valeur surlignée avant d’exécuter la commande.
    STAMP=YYYY-MM-DDTHH-MM-SSZ
  8. Définir le compartiment privé
    Remplacez chaque valeur surlignée avant d’exécuter la commande.
    BUCKET=YOUR_PRIVATE_BUCKET
  9. Définir la région Spaces
    Remplacez chaque valeur surlignée avant d’exécuter la commande.
    REGION=YOUR_SPACES_REGION
  10. Créer un dossier de restauration neuf
    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"
  11. Télécharger l’archive du volume
    aws s3 cp "s3://$BUCKET/n8n/n8n-volume-$STAMP.tar.gz.age" . --endpoint-url "https://$REGION.digitaloceanspaces.com"
  12. Télécharger l’archive de configuration
    aws s3 cp "s3://$BUCKET/n8n/n8n-config-$STAMP.tar.gz.age" . --endpoint-url "https://$REGION.digitaloceanspaces.com"
  13. Déchiffrer l’archive du volume
    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"
  14. Déchiffrer l’archive de configuration
    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"
  15. Extraire la configuration pour référence
    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.
  16. Définir le digest d’image enregistré
    Remplacez chaque valeur surlignée avant d’exécuter la commande.
    N8N_IMAGE=docker.n8n.io/n8nio/n8n@sha256:REPLACE_WITH_RECORDED_DIGEST
  17. Préparer les volumes et le fichier Compose isolé
    [[ "$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
  18. Désactiver et démarrer après blocage du trafic sortant
    # 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
  19. Ouvrir le tunnel SSH depuis votre postePoste d’administration
    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.