CyberSDF

Reprenez la main sur votre numérique

Construire un premier workflow n8n vérifiable

Un premier workflow n8n peut rester local et lisible : un serveur Python sert un fichier JSON, n8n le lit, transforme la réponse en fichier puis l’écrit. Le banc du 15 septembre 2026 a utilisé n8n 2.35.7 sur Node v22.23.1 : le cas nominal a produit un fichier de 283 octets en 5,9268 secondes, tandis qu’une cible absente a renvoyé 404 et arrêté la chaîne avant l’écriture. Le point décisif est le répertoire explicite du serveur HTTP, pas le dossier courant du terminal. Pour préparer une automatisation plus large, commencez par le cadre des automatisations locales, puis exécutez ce montage isolé.

Sommaire
  1. Préparer n8n et un dossier de travail
  2. Servir le JSON dans le bon répertoire
  3. Comprendre la chaîne de quatre nœuds
  4. Rejouer l’exécution nominale
  5. Vérifier l’arrêt sur une cible absente
  6. Éviter le refus d’accès au fichier
  7. Conserver une exécution relisible
Terminal allumé dans une pièce sombre : invite et curseur ambre sur écran noir.

Préparer n8n et un dossier de travail

La documentation n8n demande une version de Node.js comprise entre 20.19 et 24.x inclus (installation avec npm). Le relevé employait Node v22.23.1 et npm 10.9.8. La version n8n était épinglée à 2.35.7 pour éviter qu’une mise à jour silencieuse ne change les versions de nœuds pendant la reprise.

rm -rf /tmp/n8n-bench
mkdir -p /tmp/n8n-bench/www /tmp/n8n-bench/out /tmp/n8n-bench/data
printf '%s\n' '{"source":"cybersdf-bench","valeur":42}' > /tmp/n8n-bench/www/ok.json
export N8N_USER_FOLDER=/tmp/n8n-bench/data
export N8N_RESTRICT_FILE_ACCESS_TO=/tmp/n8n-bench
npm install --prefix /tmp/n8n-bench --no-fund --no-audit n8n@2.35.7

Le fichier ok.json complet contient une seule ligne : {"source":"cybersdf-bench","valeur":42}. Le dossier www contient la cible HTTP, out recevra le résultat et data isole la base n8n. Le banc a mesuré 2,7 Go dans node_modules, 1 095 entrées de premier niveau et 3,9 Go pour le dossier complet. L’installation a duré 8 minutes et 26 secondes.

Servir le JSON dans le bon répertoire

Ouvrez un second terminal et laissez le serveur au premier plan. L’option --directory rend le chemin indépendant du dossier depuis lequel la commande est lancée :

python3 -m http.server 8099 --bind 127.0.0.1 --directory /tmp/n8n-bench/www

Le test de la cible et de l’erreur doit donner deux codes distincts :

$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8099/ok.json
200
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8099/absent.json
404

Un serveur lancé sans --directory /tmp/n8n-bench/www dépend du dossier courant et peut servir un autre fichier, ou répondre 404 alors que ok.json existe bien. Le chemin complet de l’essai est donc /tmp/n8n-bench/www.

Comprendre la chaîne de quatre nœuds

Le déclencheur manuel lance la chaîne. Lire la cible, configuré avec HTTP Request, récupère le JSON avec son statut. Le réglage neverError: false coupe l’exécution sur un 404. Convertir en fichier transforme la réponse en binaire JSON. Read/Write Files from Disk l’écrit ensuite dans /tmp/n8n-bench/out/resultat.json, à l’intérieur de la racine autorisée par N8N_RESTRICT_FILE_ACCESS_TO.

Le bloc suivant est l’export complet du cas nominal, et non une liste de nœuds : il contient l’identifiant, les quatre nœuds avec leurs paramètres et positions, les connexions, les réglages, l’état d’activité et les identifiants de version. Les métadonnées de partage propres à une instance ne sont pas nécessaires pour importer le workflow.

{
  "active": false,
  "activeVersionId": null,
  "connections": {
    "Demarrer a la main": {
      "main": [
        [
          {
            "node": "Lire la cible",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lire la cible": {
      "main": [
        [
          {
            "node": "Convertir en fichier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convertir en fichier": {
      "main": [
        [
          {
            "node": "Ecrire le resultat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "createdAt": "2026-09-15T09:36:10.439Z",
  "description": null,
  "id": "cSdfBenchNominal0001",
  "isArchived": false,
  "meta": null,
  "name": "CyberSDF bench n8n - cas nominal",
  "nodeGroups": [],
  "nodes": [
    {
      "parameters": {},
      "id": "a0000001-0000-4000-8000-000000000001",
      "name": "Demarrer a la main",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        260,
        300
      ]
    },
    {
      "parameters": {
        "url": "http://127.0.0.1:8099/ok.json",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": false,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "a0000002-0000-4000-8000-000000000002",
      "name": "Lire la cible",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        480,
        300
      ]
    },
    {
      "parameters": {
        "operation": "toJson",
        "options": {}
      },
      "id": "a0000003-0000-4000-8000-000000000003",
      "name": "Convertir en fichier",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        700,
        300
      ]
    },
    {
      "parameters": {
        "operation": "write",
        "fileName": "/tmp/n8n-bench/out/resultat.json",
        "dataPropertyName": "data",
        "options": {}
      },
      "id": "a0000004-0000-4000-8000-000000000004",
      "name": "Ecrire le resultat",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        920,
        300
      ]
    }
  ],
  "pinData": null,
  "settings": {},
  "sourceWorkflowId": null,
  "staticData": null,
  "tags": [],
  "triggerCount": 0,
  "updatedAt": "2026-09-15T09:36:10.439Z",
  "versionCounter": 1,
  "versionId": "b89c2ee1-2c99-4399-86c0-778568f45b37",
  "versionMetadata": {
    "name": null,
    "description": null
  }
}

Copiez ce bloc dans /tmp/n8n-bench/workflow-nominal.json, puis importez-le dans la base indiquée par N8N_USER_FOLDER :

/tmp/n8n-bench/node_modules/.bin/n8n import:workflow --input=/tmp/n8n-bench/workflow-nominal.json
/tmp/n8n-bench/node_modules/.bin/n8n list:workflow

Le champ id à la racine est nécessaire à l’import de cette version de n8n. Sans lui, le banc a renvoyé SQLITE_CONSTRAINT: NOT NULL constraint failed: workflow_entity.id. Le message ressemble à un problème de base, mais la correction se trouve dans le JSON.

Rejouer l’exécution nominale

Supprimez la sortie avant de lancer le workflow : sa présence après le test doit provenir de cette exécution, pas d’un passage précédent.

rm -f /tmp/n8n-bench/out/resultat.json
/tmp/n8n-bench/node_modules/.bin/n8n execute --id cSdfBenchNominal0001
printf 'rc=%s\n' "$?"
ls -l /tmp/n8n-bench/out/resultat.json
cat /tmp/n8n-bench/out/resultat.json
sha256sum /tmp/n8n-bench/out/resultat.json

La sortie relevée le 15 septembre 2026 est la suivante :

Execution was successful:
  "status": "success"
  "lastNodeExecuted": "Ecrire le resultat"
rc=0
duree_s=5.926827859
-rw------- 1 utilisateur utilisateur 283 Sep 15 09:36 resultat.json
[{"body":{"source":"cybersdf-bench","valeur":42},"headers":{"server":"SimpleHTTP/0.6 Python/3.12.3","date":"Tue, 15 Sep 2026 09:36:43 GMT","content-type":"application/json","content-length":"40","last-modified":"Tue, 15 Sep 2026 09:34:30 GMT"},"statusCode":200,"statusMessage":"OK"}]
b3842d47e38fa9605ac880f008b02517c4122155deb900da850b4406333589bc  resultat.json

Les quatre signaux concordent : le processus sort avec rc=0, n8n indique success, le fichier fait 283 octets et son contenu porte statusCode: 200. Le corps reçu conserve source et valeur, tandis que les en-têtes montrent que Python a servi le JSON. Ce sont ces signaux ensemble qui valident l’écriture.

Après avoir vérifié le fichier produit par ce workflow n8n, n8n n’a plus besoin du dossier de test pour cette exécution. Revenez au terminal où le serveur tourne au premier plan et appuyez sur Ctrl+C pour l’arrêter :

Ctrl+C

Vérifier l’arrêt sur une cible absente

Pour le cas d’échec, importez l’export complet ci-dessous dans la même base. Il conserve les quatre nœuds et leurs connexions, remplace /ok.json par /absent.json et donne à la sortie un nom différent.

{
  "active": false,
  "activeVersionId": null,
  "connections": {
    "Demarrer a la main": {
      "main": [
        [
          {
            "node": "Lire la cible",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lire la cible": {
      "main": [
        [
          {
            "node": "Convertir en fichier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convertir en fichier": {
      "main": [
        [
          {
            "node": "Ecrire le resultat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "createdAt": "2026-09-15T09:36:15.497Z",
  "description": null,
  "id": "cSdfBenchEchec000001",
  "isArchived": false,
  "meta": null,
  "name": "CyberSDF bench n8n - cas en echec",
  "nodeGroups": [],
  "nodes": [
    {
      "parameters": {},
      "id": "b0000001-0000-4000-8000-000000000001",
      "name": "Demarrer a la main",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        260,
        300
      ]
    },
    {
      "parameters": {
        "url": "http://127.0.0.1:8099/absent.json",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": false,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "b0000002-0000-4000-8000-000000000002",
      "name": "Lire la cible",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.5,
      "position": [
        480,
        300
      ]
    },
    {
      "parameters": {
        "operation": "toJson",
        "options": {}
      },
      "id": "b0000003-0000-4000-8000-000000000003",
      "name": "Convertir en fichier",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        700,
        300
      ]
    },
    {
      "parameters": {
        "operation": "write",
        "fileName": "/tmp/n8n-bench/out/echec-ne-doit-pas-exister.json",
        "dataPropertyName": "data",
        "options": {}
      },
      "id": "b0000004-0000-4000-8000-000000000004",
      "name": "Ecrire le resultat",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1.1,
      "position": [
        920,
        300
      ]
    }
  ],
  "pinData": null,
  "settings": {},
  "sourceWorkflowId": null,
  "staticData": null,
  "tags": [],
  "triggerCount": 0,
  "updatedAt": "2026-09-15T09:36:15.497Z",
  "versionCounter": 1,
  "versionId": "3a6fc3cd-9f2f-4c87-94b6-4e48b4444c9a",
  "versionMetadata": {
    "name": null,
    "description": null
  }
}
/tmp/n8n-bench/node_modules/.bin/n8n import:workflow --input=/tmp/n8n-bench/workflow-echec.json
rm -f /tmp/n8n-bench/out/echec-ne-doit-pas-exister.json
/tmp/n8n-bench/node_modules/.bin/n8n execute --id cSdfBenchEchec000001
printf 'rc=%s\n' "$?"
ls -l /tmp/n8n-bench/out/echec-ne-doit-pas-exister.json
The resource you are requesting could not be found
rc=1
duree_s=5.693487624
ls: cannot access '/tmp/n8n-bench/out/echec-ne-doit-pas-exister.json': No such file or directory

Le nœud HTTP reçoit 404 et l’exécution s’arrête avant Ecrire le resultat : aucun fichier d’échec n’est créé. C’est le réglage neverError: false qui rend le code de retour utile à un appelant ou à un planificateur. L’option neverError peut avoir un intérêt pour une étape non critique, mais elle ne doit pas masquer une réponse en erreur.

Éviter le refus d’accès au fichier

Depuis n8n 2.0, N8N_RESTRICT_FILE_ACCESS_TO vaut ~/.n8n-files par défaut. Le workflow écrit dans /tmp/n8n-bench/out ; l’export reste donc exécutable parce que la commande de préparation fixe explicitement la racine autorisée à /tmp/n8n-bench. Si cette variable est retirée, le banc obtient Access to the file is not allowed. et rc=1 avant l’écriture.

La limite est volontaire : un nœud d’écriture ne doit pas pouvoir déposer un fichier n’importe où sur la machine. Pour un autre dossier, changez la variable, le fileName des exports et les commandes de contrôle ensemble. Utilisez toujours un chemin absolu, comme le recommande la documentation officielle.

Conserver une exécution relisible

Un export JSON versionné permet de comparer les paramètres et les connexions après une modification. Pour une planification, la documentation de Schedule Trigger précise qu’un workflow doit être publié pour que le déclencheur prenne effet ; un essai manuel évite cette condition pendant la mise au point. Un identifiant de service, lui, ne doit pas être copié dans le JSON : l’import conserve la configuration mais pas les secrets.

Les mesures correspondent à n8n 2.35.7, Node v22.23.1, npm 10.9.8 et Linux 6.8.0-106-generic le 15 septembre 2026 ; un autre disque, une autre version ou un autre mode d’installation donnera d’autres temps et volumes. Pour préparer les entrées, sorties et conditions d’arrêt d’une routine, lisez Automatiser une petite routine ; pour comparer les outils selon votre usage, consultez Choisir un logiciel.