How to Self-Host n8n on a VPS
Install n8n on Ubuntu 24.04 with n8n's Docker Compose and Traefik setup, then set the webhook URL, back up the encryption key and update safely.
n8n is a workflow automation tool. You connect apps and APIs as nodes on a visual canvas, and workflows run on triggers, schedules or incoming webhooks. This guide installs it by hand on Ubuntu 24.04 with n8n's own Docker Compose setup, which runs n8n next to Traefik, a reverse proxy that gets a Let's Encrypt certificate and serves n8n over HTTPS.
Prefer one click? Deploy n8n from the VeloxaHost marketplace and skip the manual steps; this guide is for when you want to run every command yourself.
What you need
- A VeloxaHost server with the Ubuntu 24.04 LTS image. You get root, over SSH or in the browser terminal on the server's page.
- A domain or subdomain, such as
n8n.example.com. - A dedicated IPv4 address. Traefik needs ports 80 and 443, and its TLS challenge runs on 443. The shared IPv4 address every server gets has no 80 or 443, only a block of forwarded ports. A dedicated IPv4 is a paid add-on (the console shows the current price): choose it as "Network access" on the create page, or take one from the IP addresses page and attach it. If none is free in the region, the request is refused.
If n8n is only for you and no outside service calls its webhooks, skip the domain and address and use the SSH tunnel variant at the end of the install steps.
n8n's docs give no fixed minimum for a single container. Their Kubernetes example requests 250 MB of memory with a 500 MB limit. The optional n8n Assistant sandbox stack needs at least 4 GB of RAM and 2 vCPUs.
A Veloxa 1 (1 vCPU, 2 GB RAM, 50 GB disk) at $8 a month, billed at $0.02 an hour and capped at the monthly price, fits a small instance. For n8n Assistant or heavy workflows, take a Veloxa 2 (2 vCPU, 4 GB RAM, 80 GB disk) at $15 a month, billed at $0.03 an hour and capped at the monthly price.
Install n8n
The dedicated IPv4 is routed to the server, which must answer on it. Add it to the interface (check the name with
ip addr), and to the network configuration so it survives a reboot.ip addr add 198.51.100.5/32 dev eth0Create a DNS A record for
n8n(or your subdomain) pointing at that address.Install Docker Engine and the Compose plugin from Docker's apt repository, as in Docker's Ubuntu guide.
sudoalso works as root.# Add Docker's official GPG key: sudo apt update sudo apt install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: sudo tee /etc/apt/sources.list.d/docker.sources <<EOF Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") Components: stable Architectures: $(dpkg --print-architecture) Signed-By: /etc/apt/keyrings/docker.asc EOF sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCheck that it works:
sudo docker run hello-world docker --version docker compose versionCreate a project directory, a
local-filesdirectory (shared with n8n as/filesfor file nodes) and an.envfile with your domain, timezone and certificate email.mkdir n8n-compose cd n8n-compose mkdir local-files# .env DOMAIN_NAME=example.com SUBDOMAIN=n8n GENERIC_TIMEZONE=America/New_York SSL_EMAIL=you@example.comCreate
compose.yaml. This is n8n's file plusN8N_PROXY_HOPS=1, which n8n's docs ask for behind a reverse proxy.services: traefik: image: "traefik" restart: always command: - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entryPoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true" - "--certificatesresolvers.mytlschallenge.acme.email=${SSL_EMAIL}" - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - traefik_data:/letsencrypt - /var/run/docker.sock:/var/run/docker.sock:ro n8n: image: n8nio/n8n restart: always ports: - "127.0.0.1:5678:5678" labels: - traefik.enable=true - traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`) - traefik.http.routers.n8n.tls=true - traefik.http.routers.n8n.entrypoints=web,websecure - traefik.http.routers.n8n.tls.certresolver=mytlschallenge - traefik.http.middlewares.n8n.headers.SSLRedirect=true - traefik.http.middlewares.n8n.headers.STSSeconds=315360000 - traefik.http.middlewares.n8n.headers.browserXSSFilter=true - traefik.http.middlewares.n8n.headers.contentTypeNosniff=true - traefik.http.middlewares.n8n.headers.forceSTSHeader=true - traefik.http.middlewares.n8n.headers.SSLHost=${DOMAIN_NAME} - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true - traefik.http.middlewares.n8n.headers.STSPreload=true - traefik.http.routers.n8n.middlewares=n8n@docker environment: - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME} - N8N_PORT=5678 - N8N_PROTOCOL=https - NODE_ENV=production - N8N_WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/ - N8N_PROXY_HOPS=1 - GENERIC_TIMEZONE=${GENERIC_TIMEZONE} - TZ=${GENERIC_TIMEZONE} - N8N_RESTRICT_FILE_ACCESS_TO=/files volumes: - n8n_data:/home/node/.n8n - ./local-files:/files volumes: n8n_data: traefik_data:N8N_HOSTandN8N_PROTOCOLgive n8n its public host name and scheme. n8n otherwise builds webhook URLs from protocol, host and port, which behind a proxy would include port 5678, soN8N_WEBHOOK_URLsets the public URL that external services get. It exists from n8n 2.30.0 and replacesWEBHOOK_URL, deprecated from 2.35.0.Start the stack and check the logs.
sudo docker compose up -d sudo docker compose logs -f n8n sudo docker compose logs traefik
Private-only variant (SSH tunnel). Without a dedicated IPv4, use this compose.yaml with no Traefik, then tunnel in through the first port of your block, which forwards to SSH.
services:
n8n:
image: n8nio/n8n
restart: always
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
- N8N_RESTRICT_FILE_ACCESS_TO=/files
volumes:
- n8n_data:/home/node/.n8n
- ./local-files:/files
volumes:
n8n_data:
ssh -p 20000 -L 5678:127.0.0.1:5678 root@203.0.113.10
Open http://localhost:5678. Schedules and outgoing calls work; webhooks from outside services cannot reach n8n.
First login
Open https://n8n.example.com (or http://localhost:5678 over the tunnel). The signup screen creates the owner account; the password needs eight characters, a number and a capital letter. Do it right after the first start, because whoever completes it first becomes the owner.
SMTP is optional, but without it users cannot reset passwords. Outbound TCP port 25 is blocked, so set N8N_EMAIL_MODE=smtp and N8N_SMTP_PORT to your provider's 587 or 465.
Secure it
In the console, go to Firewalls, New Firewall, Add Rule. Allow TCP 80 and 443 from 0.0.0.0/0 and TCP 22 only from YOUR.IP/32, then attach the firewall on the server's page. Until one is attached, all traffic is allowed.
Ports published by Docker bypass ufw, so rely on the VeloxaHost firewall. The compose file publishes 5678 on 127.0.0.1 only, so outside traffic reaches n8n through Traefik.
From n8n's docs:
- Keep
N8N_SECURE_COOKIEat its default,true. - Keep
N8N_RESTRICT_FILE_ACCESS_TO=/filesto limit file nodes to that directory. - Add
N8N_PUBLIC_API_DISABLED=trueif you do not use the public REST API. N8N_DIAGNOSTICS_ENABLED=falsestops telemetry, but also turns off Ask AI in the Code node.- Run a security audit now and then:
sudo docker compose exec -u node n8n n8n audit
Back it up
With SQLite, everything is in the n8n_data volume at /home/node/.n8n: the database and the config file holding the credential encryption key (unless you set N8N_ENCRYPTION_KEY yourself). Without the key, restored credentials cannot be decrypted, so store a copy safely.
sudo docker compose exec -u node n8n cat /home/node/.n8n/config
For a full backup, stop n8n so the SQLite file is consistent and archive the volume. Check the container name first.
sudo docker compose stop n8n
sudo docker compose ps --all
sudo docker run --rm --volumes-from n8n-compose-n8n-1 -v $(pwd):/backup ubuntu tar cvf /backup/n8n-data.tar /home/node/.n8n
sudo docker compose up -d
The CLI also exports workflows and credentials as JSON. Write into /files (the host's local-files), since other container paths are lost on recreate. These exports skip users, executions, variables and the key, so they do not replace the volume backup.
sudo docker compose exec -u node n8n n8n export:workflow --backup --output=/files/backups/workflows/
sudo docker compose exec -u node n8n n8n export:credentials --backup --output=/files/backups/credentials/
On VeloxaHost, snapshots are instant and free (5 on Starter, 30 on Pay as you go): server page, Snapshots, name, Create. Restoring overwrites the current state. Automatic snapshots run every hour, 6 hours, 12 hours, daily or weekly, kept for 1, 3, 7, 14 or 30 days. The Backups page creates, downloads and restores server backups (as a new server), on a schedule of every 6 hours, 12 hours, daily or weekly, keeping 1 to 10. Download one now and then for an off-site copy.
Update it
n8n ships a minor version most weeks; update at least monthly and read the release notes for breaking changes. Take a snapshot first, then:
cd n8n-compose
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
The untagged n8nio/n8n image is the stable release, 2.43.7 when checked. To control timing, pin a tag such as n8nio/n8n:2.43.7.
Troubleshooting
No site or no certificate. Check that the A record resolves to the dedicated IPv4, the address is on the interface and the firewall allows 80 and 443. Then read sudo docker compose logs traefik.
Webhook URLs show localhost or port 5678. Set N8N_WEBHOOK_URL to the full HTTPS URL with a trailing slash and run sudo docker compose up -d.
A secure cookie warning. You opened n8n over plain HTTP. Use the HTTPS domain or the tunnel to localhost rather than setting N8N_SECURE_COOKIE=false.
"Connection Lost" or "JavaScript heap out of memory". A workflow needs more memory. Move to a larger size, or split the work into smaller batches and sub-workflows.
FAQ
What license is n8n released under?
n8n calls itself fair-code and is released under the Sustainable Use License, which is not OSI-approved. It allows use and modification for your own internal business purposes or for non-commercial or personal use. Files marked .ee are under the separate n8n Enterprise License.
Why does n8n need a dedicated IPv4 address on VeloxaHost?
Traefik serves HTTPS on ports 80 and 443 and gets its certificate through a TLS challenge on 443. The shared IPv4 address only offers a block of forwarded ports. A dedicated address also keeps your DNS record valid if the server moves to another host, which can change the shared address and ports.
Do webhooks work over the SSH tunnel?
No. The tunnel only connects your own computer. Outside services need a public HTTPS URL, so use the dedicated IPv4 setup for any workflow started by an incoming webhook.
Where does n8n keep workflows and credentials?
In the n8n_data volume at /home/node/.n8n. With SQLite it holds the database and the config file with the encryption key. Back them up together, because credentials are encrypted with that key.
Which VeloxaHost size fits n8n?
A Veloxa 1 (1 vCPU, 2 GB RAM, 50 GB disk) at $8 a month, billed at $0.02 an hour and capped at the monthly price, suits a small instance. For n8n Assistant (at least 4 GB of RAM and 2 vCPUs) or many parallel executions, choose a Veloxa 2 (2 vCPU, 4 GB RAM, 80 GB disk) at $15 a month, billed at $0.03 an hour and capped at the monthly price.
Commands and settings checked on 2026-10-18 against the official documentation: docs.n8n.io, github.com, docs.docker.com. Software changes; if a step no longer matches, the vendor's docs win.
Ready to deploy?
Start a free trial, or compare plans to match your team's scale.