Install and secure

Automate your workflows with n8n on a VPS

Install n8n with Docker behind Nginx and HTTPS on an Ubuntu VPS: unlimited executions, your data on your own server, commands tested on Ubuntu 26.04 LTS.

Published on

n8n connects your applications together: a form that feeds a spreadsheet, an order that triggers an email, an AI agent that sorts your messages. Hosted on your own VPS, it has no execution limit and your data stays on your server. This guide installs it with Docker, behind Nginx and an HTTPS certificate. Every command was run on Ubuntu 26.04.1 LTS on September 27, 2026, in a test environment and then on a QuantylCloud VPS.

Before you start

  • an Ubuntu VPS, secured by following Secure your Linux VPS;
  • 2 vCPU and 4 GB of memory: n8n runs on less, but busy workflows or AI nodes quickly use up memory (see Create a VPS in Morocco);
  • a subdomain, for example n8n.exemple.ma, whose A DNS record points to the server's IP address. Replace it with your own in all the commands;
  • for the first workflow in step 7, an SMTP account: the one from your business mailbox or from an email delivery service.

Step 1 — Open the web ports

Open ports 80 and 443 in the Firewall tab of the server's page in the console, then in ufw:

sudo ufw allow 80,443/tcp

Do not open n8n's port 5678: it will only be reachable through Nginx.

Step 2 — Install Docker

sudo apt update
sudo apt install -y docker.io docker-compose-v2

These are Ubuntu's Docker packages, updated with the rest of the system. Check with docker compose version.

Step 3 — Describe the n8n service

sudo mkdir -p /opt/n8n
sudo tee /opt/n8n/compose.yaml >/dev/null <<'YAML'
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=n8n.exemple.ma
      - N8N_PROTOCOL=https
      - N8N_WEBHOOK_URL=https://n8n.exemple.ma/
      - N8N_PROXY_HOPS=1
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - GENERIC_TIMEZONE=Africa/Casablanca
      - TZ=Africa/Casablanca
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:
YAML

Three lines deserve a closer look:

  • 127.0.0.1:5678:5678 : n8n only listens on the machine itself. Written 5678:5678, the port would be open to the Internet despite ufw, because Docker sets its own firewall rules, which take precedence over ufw's. We checked this in testing.
  • N8N_WEBHOOK_URL : the public address of your webhooks. Without it, n8n would display http://…:5678 URLs that nobody can reach.
  • GENERIC_TIMEZONE : the time of your scheduled triggers. A workflow “every day at 9 am” will run at 9 am, Casablanca time.

Step 4 — Start n8n

cd /opt/n8n
sudo docker compose up -d
curl -s http://127.0.0.1:5678/healthz

The first start downloads the image and prepares the database: allow one to two minutes before the last command answers {"status":"ok"}. The logs can be read with sudo docker compose logs. A warning about the “task runners” mode may appear there: it does not get in the way of a single-user instance.

Step 5 — Publish n8n behind Nginx, over HTTPS

sudo apt install -y nginx
sudo tee /etc/nginx/sites-available/n8n >/dev/null <<'CONF'
server {
    listen 80;
    listen [::]:80;
    server_name n8n.exemple.ma;
    client_max_body_size 16M;

    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-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_read_timeout 3600s;
    }
}
CONF
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

The Upgrade and Connection lines let WebSockets through: without them, n8n's editor opens but does not see your executions live. Then get the certificate:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d n8n.exemple.ma

As in the guide Deploy WordPress on a VPS, certbot asks for an email address, redirects HTTP to HTTPS and renews the certificate by itself. In testing, the certificate came from Let's Encrypt's test authority; against the real one, the DNS must already point to the server and port 80 be open in both firewalls.

Step 6 — Create the owner account, right away

Open https://n8n.exemple.ma. n8n asks you to create the owner account: do it immediately. As long as this account does not exist, the first visitor to the address can create it in your place and take control of the instance. Choose a long password, then enable two-factor authentication in your account settings.

Step 7 — A first workflow: receive a form by email

A concrete example: your site's contact form sends its fields to n8n, which forwards them to you by email. In the editor, click Create workflow, then copy this text and paste it into the workspace with Ctrl+V (Cmd+V on Mac):

{
  "nodes": [
    {
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [0, 0],
      "parameters": { "httpMethod": "POST", "path": "contact", "options": {} }
    },
    {
      "name": "Envoyer l'email",
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [220, 0],
      "parameters": {
        "fromEmail": "n8n@exemple.ma",
        "toEmail": "vous@exemple.ma",
        "subject": "=Nouveau message de {{ $json.body.nom }}",
        "emailFormat": "text",
        "text": "=De : {{ $json.body.nom }} <{{ $json.body.email }}>\n\n{{ $json.body.message }}",
        "options": { "appendAttribution": false }
      }
    }
  ],
  "connections": {
    "Webhook": { "main": [[{ "node": "Envoyer l'email", "type": "main", "index": 0 }]] }
  }
}

n8n may ask whether the text comes from an AI tool: answer Skip. Two nodes appear, connected. Open Envoyer l'email (the “send the email” node) with a double click, replace the two addresses with your own, then, under Credential, click Connect to SMTP (if you already have credentials, open the list and choose Create new credential). Enter your SMTP account:

  • User and Password: your account's username and password;
  • Host: the SMTP server;
  • Port 587 with SSL/TLS turned off (the connection is encrypted by STARTTLS), or 465 with SSL/TLS turned on, depending on what your provider states.

Click Save: n8n tests the connection and reports an error if the settings do not fit. Close the node, then click Publish and name this version: as long as the credential is missing, this button stays grayed out. The workflow is live. Test it from your computer:

curl -X POST https://n8n.exemple.ma/webhook/contact -H 'Content-Type: application/json' -d '{"nom":"Salma","email":"salma@exemple.ma","message":"Bonjour, je voudrais un devis."}'

The response {"message":"Workflow was started"} arrives immediately, and the email “Nouveau message de Salma” (the subject defined in the workflow) within the minute. All your form has to do is send its fields nom, email and message to this address. It is public: so that a stranger cannot use it, enable authentication in the Webhook node's settings before putting it into production.

Back up, and know how to restore

Your workflows, your connection credentials and the key that encrypts them are in the Docker volume n8n_n8n_data: Compose prefixes the volume name with the folder's name, n8n. They survive updates, not the loss of the server. Without the key, restored credentials are unreadable: first write it down in your password manager.

cd /opt/n8n
sudo docker compose exec n8n cat /home/node/.n8n/config

Then create a full archive, with n8n stopped for a few seconds so that it is consistent:

cd /opt/n8n
sudo docker compose stop
sudo tar -czf ~/n8n-sauvegarde-$(date +%F).tar.gz -C /var/lib/docker/volumes/n8n_n8n_data/_data .
sudo docker compose start

A backup left on the server disappears with it: bring it back to your computer, for example with scp gestion@ADRESSE_IP:n8n-sauvegarde-*.tar.gz .. The daily backup option on the server's page, in the console, adds a copy of the whole server.

To restore on a new server, follow steps 1 to 3, copy the archive into the gestion folder, then, replacing the date with your archive's:

cd /opt/n8n
sudo docker compose create
sudo tar -xzf ~/n8n-sauvegarde-AAAA-MM-JJ.tar.gz -C /var/lib/docker/volumes/n8n_n8n_data/_data
sudo docker compose up -d

Carry on with step 5. In testing, after deleting the volume and restoring, the account, the workflow and the SMTP credential came back, and the email went out again. Run this test once yourself: a backup that has never been restored remains a hypothesis.

Update

n8n publishes a new version almost every week. Back up, then:

cd /opt/n8n
sudo docker compose pull
sudo docker compose up -d

Ubuntu's automatic updates do not touch n8n: this command is what moves it forward. As it stands, it always takes the latest version, including a major version change that may require adapting your workflows. To choose the moment, pin the version in compose.yaml, for example image: docker.n8n.io/n8nio/n8n:2.40.7: you will change this number after reading the release notes.

Frequently asked questions

Why host n8n yourself rather than on n8n Cloud?

On your VPS, the number of executions is limited only by the server's power, and your data and credentials stay with you. In return, updates and backups are your responsibility.

Which plan should I choose for n8n?

2 vCPU and 4 GB of memory suit most uses. Very frequent workflows, large files or AI nodes may need more.

My webhooks show an address in http://…:5678, why?

The N8N_WEBHOOK_URL variable is missing or misspelled in compose.yaml. Fix it with your https address, then run sudo docker compose up -d again.

Do I need to open port 5678?

No. n8n only listens on the machine itself and Nginx publishes it over HTTPS on port 443. Opening 5678 would expose n8n without encryption.