SSD Nodes Learn
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-07-19

Héberger n8n sur un VPS : Docker + HTTPS

Exécutez n8n sur un VPS avec Docker Compose, Postgres et HTTPS derrière un proxy inverse : les pièges de WEBHOOK_URL, de la clé de chiffrement et chaque erreur.

Ce que vous allez construire

n8n est un outil d'automatisation de flux de travail : un éditeur visuel où un déclencheur, un webhook, une planification, l'envoi d'un formulaire, lance une chaîne de nœuds qui appellent des API, remodèlent les données et écrivent vers d'autres systèmes. Il est devenu la colle par défaut des flux d'agents IA, car il dialogue avec tous les fournisseurs de modèles et toutes les bases de données sans que vous ayez à écrire un service. Un seul docker run donne un éditeur fonctionnel en deux minutes. Ce guide porte sur les quatre-vingt-dix pour cent restants : le rendre durable avec Postgres au lieu du fichier SQLite par défaut, l'exposer en HTTPS et, la partie que presque tout le monde rate, faire en sorte que les webhooks distribuent une URL que le monde extérieur peut réellement atteindre.

La pile finale, c'est deux conteneurs sur un même réseau Docker : n8n lui-même et une base de données Postgres qui contient ses flux de travail et ses identifiants. Un proxy inverse sur l'hôte termine le TLS et transmet à n8n sur localhost, de sorte que rien n'est exposé à Internet en dehors de ce proxy. Il prend place aux côtés des autres services de la sélection d'auto-hébergement 2026.

Prérequis, et les limites honnêtes

Il vous faut un VPS avec au moins 1 Go de RAM ; prévoyez 2 Go une fois que les flux de travail font un vrai travail, car les exécutions plus le moteur d'exécution Node.js consomment de la mémoire, et voir le tueur de mémoire insuffisante (out-of-memory killer) emporter le conteneur en plein travail est une façon pénible de l'apprendre. Un seul vCPU suffit pour démarrer.

Il vous faut un domaine ou un sous-domaine, par exemple n8n.example.com, avec un enregistrement A pointant vers l'IP publique du VPS qui se résout avant que vous demandiez un certificat. Les ports 80 et 443 doivent être ouverts vers le proxy ; le port 5678 propre à n8n ne doit pas être exposé à Internet. Il vous faut Docker Engine et le plugin Compose ; si docker compose version échoue avec docker: 'compose' is not a docker command, vous avez l'ancien binaire autonome, et le plugin s'installe avec sudo apt install docker-compose-plugin.

SQLite convient pour un test, Postgres pour tout ce dont vous dépendez

La base de données par défaut de n8n est un fichier SQLite situé à /home/node/.n8n/database.sqlite. Pour un essai rapide, cela convient : ne montez aucun volume et vous le perdez à la première recréation du conteneur, ce qui est une leçon en soi. La raison de passer à Postgres n'est pas la vitesse brute ; c'est que SQLite ne détient qu'un seul verrou d'écriture, si bien qu'une instance qui fait tourner plusieurs flux de travail à la fois, ou le mode file d'attente que vous voudrez un jour, renvoie SQLITE_BUSY: database is locked en cas de concurrence. Postgres n'a pas ce plafond, se sauvegarde proprement avec pg_dump, et c'est ce que la documentation de n8n suppose pour un serveur dont vous dépendez. Changer plus tard implique de migrer les données à la main, donc si cette machine compte, démarrez sur Postgres.

Le DNS et le pare-feu

Faites pointer l'enregistrement et ouvrez d'abord les ports, pour que l'étape du certificat plus loin n'échoue pas sur un nom qui ne se résout pas.

dig +short n8n.example.com
curl -s ifconfig.me
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow OpenSSH
sudo ufw enable

N'ouvrez pas le 5678. Le fichier Compose lie n8n à 127.0.0.1:5678 afin que seul le proxy inverse de l'hôte puisse l'atteindre, et un ufw allow 5678 annulerait cette isolation.

Le fichier Compose

Créez un répertoire de travail et un docker-compose.yml. Voici toute la pile : deux services, un réseau privé, deux volumes nommés.

services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n_net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: docker.n8n.io/n8nio/n8n:2.29.10
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=n8n.example.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com/
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_PROXY_HOPS=1
      - GENERIC_TIMEZONE=Europe/London
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - n8n_net
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:
  n8n_data:

networks:
  n8n_net:

Quelques décisions méritent d'être énoncées clairement. DB_POSTGRESDB_HOST=postgres est le nom de service, que Docker résout sur le réseau partagé, et non localhost, qui à l'intérieur du conteneur n8n désigne n8n lui-même. Le depends_on avec condition: service_healthy empêche n8n de courir plus vite que Postgres au démarrage ; sans lui, n8n démarre, ne trouve aucune base de données et s'arrête. Le volume nommé n8n_data monté sur /home/node/.n8n contient la clé de chiffrement et, sur SQLite, la base de données : le seul répertoire que vous ne devez pas perdre. Épinglez l'image à une version exacte, jamais latest ; les raisons figurent dans la section mise à niveau ci-dessous.

Le fichier de secrets

Ne mettez jamais de mots de passe dans le fichier Compose. Placez-les dans un fichier .env à côté que Compose lit automatiquement, et générez-les pour qu'ils soient réellement aléatoires.

printf 'POSTGRES_PASSWORD=%s\n'  "$(openssl rand -hex 24)" >  .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
chmod 600 .env

La N8N_ENCRYPTION_KEY est la chaîne la plus importante ici : c'est la clé avec laquelle chaque identifiant stocké est chiffré. Définissez-la explicitement au lieu de laisser n8n en générer une, car une valeur que vous avez générée est une valeur que vous pouvez noter et restaurer. Une fois que n8n a chiffré son premier identifiant avec cette clé, la changer rend chaque identifiant indéchiffrable, alors définissez-la une seule fois, maintenant, et ne touchez plus jamais à cette ligne.

Les variables d'environnement qui décident si les webhooks fonctionnent

Quatre variables contrôlent la façon dont n8n se décrit au monde extérieur, et les régler de travers est la première question de support n8n.

  • N8N_HOST est le nom d'hôte public, n8n.example.com. Laissez-le à la valeur par défaut localhost derrière un proxy et l'éditeur tente de charger sa propre API depuis localhost dans votre navigateur, ce qui échoue.
  • N8N_PROTOCOL=https indique à n8n qu'il est servi via TLS, il marque donc son cookie de session Secure et construit des URL en https://.
  • N8N_PORT=5678 est le port sur lequel n8n écoute à l'intérieur du conteneur. Ce n'est pas le port public ; le proxy possède le 443.
  • WEBHOOK_URL=https://n8n.example.com/ est celle qui fait mal. n8n affiche les adresses de webhook que vous collez dans Stripe, GitHub ou tout appelant externe en les construisant à partir de ces valeurs. Si elle est absente ou erronée, n8n retombe sur N8N_HOST:N8N_PORT et vous donne https://n8n.example.com:5678/webhook/... ou, pire, http://localhost:5678/webhook/..., affichée sans erreur, plausible en apparence et inatteignable depuis Internet, si bien que les requêtes de l'appelant n'arrivent jamais, en silence. Définissez-la avec l'URL de base publique exacte, barre oblique finale comprise, puis vérifiez que le nœud webhook affiche une URL sans port.

N8N_PROXY_HOPS=1 indique au serveur Express de n8n de faire confiance à un proxy placé devant lui, de sorte que la limitation de débit et toute fonction qui lit l'IP du client voient la véritable adresse plutôt que celle du proxy. Une variable que vous ne définissez délibérément pas ici est N8N_RUNNERS_ENABLED : les exécuteurs de tâches (task runners), c'est-à-dire n8n exécutant la logique du nœud Code dans un processus isolé distinct, sont la valeur par défaut depuis la 1.69 et sont obligatoires à partir de la branche 2.x que ce guide épingle, donc l'ancienne activation manuelle est dépréciée. Définissez-la maintenant et n8n se contente de consigner un avis vous demandant de la retirer.

Premier démarrage

docker compose up -d
docker compose ps
docker compose logs -f n8n

Un premier démarrage sain se termine par une ligne Editor is now accessible via:, précédée d'une ligne n8n ready on ..., port 5678. docker compose ps doit montrer les deux conteneurs Up, avec postgres marqué (healthy). Si n8n reste bloqué dans une boucle Restarting, lisez les journaux : il s'agit presque toujours de la connexion à la base de données ou des permissions de volume traitées plus bas.

Le TLS avec un proxy inverse

n8n lui-même parle du HTTP en clair sur le 5678 ; quelque chose devant lui termine le HTTPS. Deux choix propres.

Si vous faites déjà tourner plusieurs conteneurs, placez n8n derrière un proxy inverse Traefik qui émet automatiquement les certificats TLS avec quelques étiquettes : Traefik demande et renouvelle le certificat pour vous.

Si c'est la seule application sur la machine, un hôte virtuel nginx avec un certificat Let's Encrypt est plus simple. Utilisez la configuration TLS Certbot et nginx pour Ubuntu 24.04 pour obtenir le certificat, puis ce bloc server :

server {
    listen 443 ssl;
    server_name n8n.example.com;

    ssl_certificate     /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600;
        client_max_body_size 16m;
    }
}

Les en-têtes Upgrade et Connection "upgrade" ne sont pas facultatifs. n8n pousse les mises à jour d'exécution en direct vers l'éditeur via un WebSocket, et sans ces deux lignes la page de connexion se charge puis se fige avec une bannière de connexion perdue. proxy_read_timeout 3600 évite que les exécutions de longue durée soient coupées au bout des 60 secondes par défaut de nginx. L'en-tête X-Forwarded-Proto $scheme est le compagnon de N8N_PROXY_HOPS=1 : il indique à n8n que la requête d'origine était en HTTPS même si le proxy l'atteint en HTTP en clair, de sorte que n8n ne décide pas que la connexion est non sécurisée et ne rejette pas son propre cookie.

Votre premier flux de travail, pour rendre la chose concrète

Ouvrez https://n8n.example.com/, créez le compte propriétaire (section suivante) et construisez le plus petit flux de travail qui prouve que le chemin fonctionne : un webhook en entrée, un appel HTTP, une réponse en sortie.

  1. Ajoutez un nœud Webhook. Réglez la méthode sur POST et un chemin comme hello. Il affiche deux URL, une Test URL et une Production URL, à l'origine de la moitié des rapports « mon webhook ne marche pas ». La Test URL répond à un seul appel, et uniquement pendant que vous avez cliqué sur Listen for test event ; elle expire ensuite. La Production URL répond dès que le flux de travail est Active.
  2. Ajoutez ensuite un nœud HTTP Request, pointé vers n'importe quelle API JSON publique : un GET vers https://api.github.com/zen renvoie une chaîne d'une ligne, ce qui suffit.
  3. Ajoutez un nœud Respond to Webhook, et réglez l'option Respond du nœud Webhook sur « Using Respond to Webhook node » pour que l'appelant récupère la sortie du nœud HTTP.
  4. Basculez le flux de travail sur Active (en haut à droite) et appelez-le : curl -X POST https://n8n.example.com/webhook/hello. Vous devriez récupérer la ligne zen : POST en entrée, appel d'API, réponse en sortie, la forme de la plupart des automatisations réelles.

Une variante planifiée remplace le nœud Webhook par un Schedule Trigger et appelle plutôt un point de terminaison de modèle : un modèle auto-hébergé avec Ollama tournant sur le même VPS est une manière soignée de construire un résumeur nocturne.

La gestion des utilisateurs, pas l'authentification basique

Les anciens guides n8n vous disent de définir N8N_BASIC_AUTH_ACTIVE=true. Ces variables ont été supprimées dans n8n 1.0 et ne font plus rien. L'authentification aujourd'hui, c'est le compte propriétaire : la première fois que vous chargez l'éditeur, n8n vous oblige à créer un propriétaire avec e-mail et mot de passe, et cette barrière est obligatoire ; il n'y a pas de mode anonyme. Créez-le immédiatement après le premier démarrage, avant de donner l'URL à qui que ce soit : entre docker compose up et ce premier envoi de formulaire, l'instance peut être revendiquée par le premier qui l'atteint. Une couche d'authentification basique sur le proxy inverse par-dessus est un verrou supplémentaire raisonnable, mais c'est un second facteur, pas la véritable authentification.

Sauvegardes : d'abord la clé de chiffrement, puis la base de données

Deux choses doivent être sauvegardées, et elles ne sont pas également remplaçables.

La N8N_ENCRYPTION_KEY. Chaque identifiant que vous stockez dans n8n, jetons d'API, mots de passe de base de données, secrets OAuth, est chiffré au repos avec cette clé. Les flux de travail dans Postgres sont inutiles sans elle : restaurez la base de données sur une nouvelle machine avec une clé différente et n8n ne peut déchiffrer un seul identifiant, sans récupération ni réinitialisation. Votre fichier .env contient la clé ; copiez-la quelque part en dehors du serveur, une entrée de gestionnaire de mots de passe est idéale, le jour où vous la créez. C'est la sauvegarde qui compte vraiment.

La base de données Postgres, pour les flux de travail, l'historique d'exécution et les identifiants chiffrés eux-mêmes :

docker compose exec -T postgres pg_dump -U n8n -d n8n \
  | gzip > n8n-db-$(date +%F).sql.gz

Exécutez cela selon un calendrier et copiez le dump en dehors de la machine. Pour restaurer sur un VPS neuf : montez la pile une fois pour que la base de données existe, arrêtez n8n, rechargez le dump avec psql, mettez la même N8N_ENCRYPTION_KEY dans .env, et démarrez n8n. La même clé plus le dump donne une instance fonctionnelle ; une nouvelle clé donne des flux de travail incapables d'utiliser le moindre identifiant.

Mises à niveau : épinglez le tag

Le fichier Compose épingle n8nio/n8n:2.29.10 plutôt que latest, et c'est volontaire. n8n publie une nouvelle version mineure presque chaque semaine et modifie parfois le schéma de base de données ou le comportement des nœuds entre elles, donc latest signifie qu'un pull non surveillé peut vous livrer une build qui migre votre base de données dès son démarrage. Épinglez une version, lisez les notes de version avant de monter de version (n8n y signale les changements incompatibles) et mettez à niveau de façon délibérée :

docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > pre-upgrade.sql.gz
# edit the image tag in docker-compose.yml, then:
docker compose pull n8n
docker compose up -d n8n
docker compose logs -f n8n

Les sauts de version majeure sont là où cela compte le plus. La branche 2.0, par exemple, a basculé N8N_BLOCK_ENV_ACCESS_IN_NODE sur true par défaut, si bien que tout nœud Code qui lisait process.env en perd l'accès silencieusement jusqu'à ce que vous le remettiez à false ; la même version a commencé à imposer des permissions strictes sur le fichier de paramètres. Lisez la page des changements incompatibles de la 2.0 avant de franchir une frontière majeure. n8n exécute automatiquement au démarrage toutes les migrations de base de données nécessaires : c'est exactement pourquoi le pg_dump d'avant mise à niveau n'est pas facultatif. Parce que les identifiants vivent chiffrés avec une clé dans .env et que les données vivent dans Postgres, les conteneurs sont jetables : vous mettez à niveau en les remplaçant, et revenez en arrière en épinglant le tag précédent et en restaurant le dump.

Modes de défaillance, avec les messages que vous verrez

The requested webhook "POST hello" is not registered. Un 404 provoqué par l'appel d'un webhook dont le flux de travail n'est pas Active, ou par l'appel du chemin de test quand personne n'écoute. Les chemins de test (/webhook-test/...) ne répondent que pendant que vous avez cliqué sur « Listen for test event » ; les chemins de production (/webhook/...) ne répondent que lorsque l'interrupteur du flux de travail est activé. Le message voisin This webhook is not registered for GET requests. Did you mean to make a POST request? signifie que la méthode est mauvaise : le nœud attend POST et vous avez envoyé GET.

L'URL du webhook affiche un :5678 ou localhost. Le nœud affiche https://n8n.example.com:5678/webhook/... ou http://localhost:5678/.... WEBHOOK_URL est absente ou erronée, donc n8n a construit l'adresse à partir de N8N_HOST:N8N_PORT au lieu de votre base publique. Définissez WEBHOOK_URL=https://n8n.example.com/, recréez le conteneur avec docker compose up -d, et le port disparaît.

There was a problem loading init data dans le navigateur. L'éditeur s'est chargé mais ne peut atteindre sa propre API dorsale. Derrière un proxy, il s'agit presque toujours d'un N8N_HOST ou d'un WEBHOOK_URL erroné, d'un proxy dépourvu des en-têtes WebSocket Upgrade, ou d'un N8N_PROTOCOL qui ne correspond pas à votre façon de vous connecter. Vérifiez les quatre variables tournées vers le public et que le proxy transmet bien Upgrade et Connection.

password authentication failed for user "n8n" dans les journaux, avec le conteneur qui redémarre. Le mot de passe que n8n envoie ne correspond pas à celui avec lequel la base de données a été initialisée. Le piège : Postgres ne lit POSTGRES_PASSWORD que lorsqu'il initialise un répertoire de données vide. Démarrez la pile une fois, puis changez POSTGRES_PASSWORD dans .env, et le volume postgres_data existant contient toujours l'ancien mot de passe. Remettez l'original, ou, si vous n'avez aucune donnée à conserver, faites docker compose down puis docker volume rm sur le volume postgres, et remontez-le à neuf.

EACCES: permission denied, open '/home/node/.n8n/config' au démarrage. n8n s'exécute en tant qu'utilisateur node (UID 1000) et ne peut pas écrire dans son répertoire de configuration. Cela touche ceux qui montent en bind un dossier hôte (./n8n_data:/home/node/.n8n) appartenant à root. Utilisez le volume nommé montré plus haut, ou si vous tenez à un montage bind, faites d'abord sudo chown -R 1000:1000 ./n8n_data.

Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. À partir de la branche 2.x, n8n impose 0600 sur ce fichier de paramètres par défaut et le corrige lui-même au démarrage : cette ligne de journal signifie qu'il a déjà corrigé le mode, généralement après un montage bind ou après qu'une restauration a recopié le fichier avec des permissions trop larges. Aucune action n'est nécessaire ; ne définissez N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false que si votre système de fichiers ne peut vraiment pas gérer les permissions.

Mismatching encryption keys : la ligne complète indique que la clé de chiffrement du fichier de paramètres /home/node/.n8n/config ne correspond pas à la N8N_ENCRYPTION_KEY de votre environnement. La clé de votre environnement diffère de celle que n8n a écrite dans son volume de données lors d'une exécution précédente, le plus souvent parce que n8n a généré une clé aléatoire lors d'un démarrage antérieur quand la variable n'était pas définie, et que vous en avez ensuite défini une différente. Remettez la clé d'origine dans .env, ou, seulement si vous n'avez vraiment aucun identifiant stocké digne d'être conservé, supprimez le fichier config à l'intérieur du volume n8n_data et laissez n8n le régénérer, en acceptant que les identifiants existants deviennent illisibles.

Une bannière de connexion à propos des cookies sécurisés : Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL, or using Safari. Vous avez défini N8N_PROTOCOL=https mais avez atteint n8n en HTTP en clair, généralement en visant directement l'IP et le port au lieu du proxy HTTPS. Atteignez-le via https://n8n.example.com/. Ne définissez N8N_SECURE_COOKIE=false que si vous ne pouvez vraiment pas utiliser HTTPS, et jamais sur une machine exposée à Internet.

Pour placer un modèle de langage dans ces flux de travail, voyez comment construire des flux d'IA avec Claude et n8n.

FAQ

Dois-je utiliser SQLite ou Postgres pour n8n ?

SQLite (par défaut) convient pour essayer n8n et pour une instance personnelle qui exécute un flux de travail à la fois. Passez à Postgres pour tout ce dont vous dépendez : le verrou d'écriture unique de SQLite renvoie database is locked en cas de concurrence, et Postgres se sauvegarde proprement avec pg_dump. Migrer plus tard est manuel, donc si la machine compte, démarrez sur Postgres.

Pourquoi mes webhooks n8n ne se déclenchent-ils jamais ?

Presque toujours WEBHOOK_URL. Absente ou erronée, n8n affiche des adresses de webhook construites à partir de N8N_HOST:N8N_PORT, souvent avec un :5678 ou localhost dedans, qui semblent valides mais sont inatteignables depuis Internet, si bien que les requêtes de l'appelant n'arrivent jamais. Définissez WEBHOOK_URL=https://n8n.example.com/ et vérifiez que le nœud affiche une URL sans port. La deuxième cause est l'appel d'un webhook dont le flux de travail n'est pas basculé sur Active, ce qui renvoie The requested webhook ... is not registered.

Que dois-je sauvegarder dans n8n ?

Deux choses. La N8N_ENCRYPTION_KEY de votre fichier .env, car chaque identifiant stocké est chiffré avec elle et la perdre les rend définitivement indéchiffrables : copiez-la en dehors du serveur le jour où vous la créez. Et un pg_dump de la base de données Postgres pour les flux de travail, l'historique et les identifiants. Une restauration a besoin des deux : la même clé plus le dump.

Comment placer n8n derrière HTTPS ?

n8n sert du HTTP en clair sur le port 5678 ; un proxy inverse devant lui termine le TLS. Liez n8n à 127.0.0.1:5678 pour que seul le proxy puisse l'atteindre, puis utilisez Traefik avec des certificats automatiques ou nginx avec un certificat Let's Encrypt. Définissez N8N_PROTOCOL=https et WEBHOOK_URL=https://votre-hote/, et assurez-vous que le proxy transmet les en-têtes WebSocket Upgrade, sinon l'éditeur se fige.

Comment mettre à niveau n8n en toute sécurité ?

Épinglez un tag d'image précis au lieu de latest, prenez d'abord un pg_dump car n8n exécute les migrations automatiquement au démarrage, lisez les notes de version pour les changements incompatibles, puis montez le tag et lancez docker compose pull n8n && docker compose up -d n8n. Le conteneur est jetable, donc revenez en arrière en épinglant le tag précédent et en restaurant le dump d'avant mise à niveau.