Cloud and Self-Hosting
Self-Hosting n8n on Ubuntu 24.04: A Step-by-Step Guide
By Amjid Ali · · Updated · 6 min read

In this article
- Quick answer: install n8n on Ubuntu 24.04 in 8 steps
- What changed in n8n 2.x (and why old guides break)
- Prerequisites
- Step 1: Update Ubuntu and set the firewall
- Step 2: Install Docker Engine and Docker Compose
- Step 3: Create the .env file with an encryption key
- Step 4: Write the Docker Compose file (n8n + PostgreSQL)
- Step 5: Start n8n and verify it
- Step 6: Nginx reverse proxy with WebSockets
- Step 7: Free SSL certificate with Let's Encrypt
- Step 8: Create the owner account
- Backups: what to save and how
- How to update n8n safely
- Troubleshooting common n8n self-hosting errors
- Alternative: install n8n with npm and PM2
- Which method should you choose?
- Next steps
To self-host n8n on an Ubuntu 24.04 VPS, install Docker Engine from Docker's repository, run n8n and PostgreSQL together with Docker Compose, and put Nginx with a free Let's Encrypt certificate in front of it. Set N8N_ENCRYPTION_KEY, N8N_HOST, N8N_PROTOCOL=https and N8N_WEBHOOK_URL so credentials stay readable after a rebuild and webhooks use your domain. The whole setup takes about 20 minutes on a fresh VPS or homelab VM.
This guide was rewritten in October 2026 for n8n 2.x (the current stable release is 2.43.7). The original version of this post used npm and PM2; that method is still covered at the end for anyone who needs it.
Quick answer: install n8n on Ubuntu 24.04 in 8 steps
- Point a DNS A record (for example
n8n.example.com) at your server's public IP. - Update Ubuntu and open ports 22, 80 and 443 in UFW.
- Install Docker Engine and the Compose plugin from Docker's official apt repository.
- Create
~/n8nwith a.envfile holding your domain, a Postgres password and a generatedN8N_ENCRYPTION_KEY. - Create
compose.yamlwith apostgresservice and ann8nservice bound to127.0.0.1:5678. - Run
docker compose up -dand checkdocker compose psshows both containers healthy. - Install Nginx, add a reverse proxy with WebSocket headers, then run
certbot --nginxfor HTTPS. - Open
https://n8n.example.com, create the owner account, and set up backups.
What changed in n8n 2.x (and why old guides break)
If you followed an older tutorial, including the first version of this one, these changes matter:
N8N_WEBHOOK_URLreplacesWEBHOOK_URL. The n8n docs sayWEBHOOK_URLis deprecated from 2.35.0 and logs a warning. Use the new name.- Basic auth variables are gone.
N8N_BASIC_AUTH_ACTIVE,N8N_BASIC_AUTH_USERandN8N_BASIC_AUTH_PASSWORDdid nothing after n8n 1.0 introduced user management. You create an owner account on first visit instead. - Task runners are on by default.
N8N_RUNNERS_ENABLEDis deprecated in 2.0 and no longer needs to be set. Code nodes run in a task runner. WEBHOOK_TUNNEL_URLis obsolete. Drop it from any old.envfile.- Node.js 20.19 to 24.x is required for an npm install. Docker images bundle the right version, which is one more reason to use Docker.
Prerequisites
- An Ubuntu 24.04 LTS server: a cloud VPS or a VM on your own Proxmox host. Plan on 2 vCPU, 4 GB RAM and 20 GB disk for n8n plus Postgres.
- A domain or subdomain you control, with an A record pointing at the server.
- SSH access as a user with
sudo.
Get a server on DigitalOcean with USD 200 free credit
Check that DNS resolves before you start, because Let's Encrypt will fail otherwise:
dig +short n8n.example.com
The output must be your server's public IP.
Step 1: Update Ubuntu and set the firewall
sudo apt update && sudo apt upgrade -y
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
Do not open port 5678. Docker writes its own iptables rules and a port published as 5678:5678 is reachable from the internet even when UFW says it is blocked. That is why the compose file below binds n8n to 127.0.0.1 only.
Step 2: Install Docker Engine and Docker Compose
Use Docker's own repository rather than the docker.io package from Ubuntu, so you get the current Engine and the docker compose plugin. These commands are from the Docker documentation:
sudo apt update
sudo apt install -y 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
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 -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Let your user run Docker without sudo, then log out and back in:
sudo usermod -aG docker $USER
Verify:
docker --version
docker compose version
docker run --rm hello-world
New to containers? My Docker for beginners guide explains images, volumes and Compose.
Step 3: Create the .env file with an encryption key
mkdir -p ~/n8n && cd ~/n8n
Generate the secrets and write the .env file in one go. Change the domain and time zone first:
cat > .env <<EOF
N8N_VERSION=2.43.7
N8N_DOMAIN=n8n.example.com
GENERIC_TIMEZONE=Australia/Sydney
POSTGRES_USER=n8n
POSTGRES_DB=n8n
POSTGRES_PASSWORD=$(openssl rand -hex 24)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
cat .env
Copy the N8N_ENCRYPTION_KEY value into your password manager now. n8n encrypts every saved credential with it. If you rebuild the server and restore the database with a different key, every credential fails with "Credentials could not be decrypted".
I pin N8N_VERSION instead of using latest so an update only happens when I choose it. Check the current stable number on the n8n releases page.
Step 4: Write the Docker Compose file (n8n + PostgreSQL)
n8n uses SQLite by default, which is fine for testing. For production, PostgreSQL copes much better with execution history and concurrent workflows (and you avoid the SQLite clean-up problem).
nano compose.yaml
services:
postgres:
image: postgres:18
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- PGDATA=/var/lib/postgresql/data
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
n8n:
image: n8nio/n8n:${N8N_VERSION}
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_HOST=${N8N_DOMAIN}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- N8N_WEBHOOK_URL=https://${N8N_DOMAIN}/
- N8N_PROXY_HOPS=1
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
- NODE_ENV=production
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=336
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
volumes:
db_data:
n8n_data:
What the important variables do:
| Variable | Why it is set |
|---|---|
N8N_ENCRYPTION_KEY |
Encrypts saved credentials. Must stay the same forever for this database. |
N8N_HOST, N8N_PROTOCOL |
The public host name and https, so links and cookies are correct. |
N8N_WEBHOOK_URL |
Public base URL for webhooks and OAuth callbacks. Without it, n8n shows http://localhost:5678/webhook/.... |
N8N_PROXY_HOPS=1 |
Tells n8n it sits behind one reverse proxy (Nginx), which fixes the X-Forwarded-For / ERR_ERL_UNEXPECTED_X_FORWARDED_FOR warning. |
EXECUTIONS_DATA_PRUNE, EXECUTIONS_DATA_MAX_AGE |
Deletes execution history older than 336 hours (14 days) so the database does not grow forever. |
DB_POSTGRESDB_* |
Connects n8n to the postgres container by its service name. |
The official n8n-hosting withPostgres example adds a separate non-root database user and an external task runner container. That is worth doing as you grow; the file above keeps it to two containers so it is easy to read and run.
Step 5: Start n8n and verify it
docker compose up -d
docker compose ps
Both services should show Up, with Postgres (healthy). Check the n8n log:
docker compose logs -f n8n
You want to see a line like Editor is now accessible via: followed by your domain. Press Ctrl+C to leave the log. Then test locally on the server:
curl -I http://127.0.0.1:5678/healthz
An HTTP/1.1 200 OK means n8n is up. It is not reachable from outside yet, which is what we want until HTTPS is in place.
Step 6: Nginx reverse proxy with WebSockets
The n8n editor uses a WebSocket (or server-sent events) connection for live execution updates, so the proxy must pass the upgrade headers. Without them the editor shows "Connection lost".
sudo apt install -y nginx
sudo nano /etc/nginx/sites-available/n8n
server {
listen 80;
server_name n8n.example.com;
client_max_body_size 50m;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 300s;
}
}
Enable it and remove the default site:
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
nginx -t must print syntax is ok and test is successful.
Prefer not to open any ports at all? A Cloudflare Tunnel works well in front of n8n, see my n8n with Docker, Nginx and Cloudflare Tunnel guide.
Step 7: Free SSL certificate with Let's Encrypt
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d n8n.example.com --redirect -m you@example.com --agree-tos -n
Certbot adds the listen 443 ssl block and an HTTP to HTTPS redirect. Confirm automatic renewal:
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
The dry run should end with Congratulations, all simulated renewals succeeded.
Step 8: Create the owner account
Open https://n8n.example.com. n8n asks you to create the owner account (email, name, password). This replaces the old basic auth variables. Then:
- turn on two-factor authentication under Settings > Personal;
- create a test workflow with a Webhook node and check that the production URL starts with
https://n8n.example.com/webhook/. If it showslocalhost:5678,N8N_WEBHOOK_URLis not reaching the container.
Backups: what to save and how
You need three things to restore n8n: the Postgres database, the n8n_data volume and the encryption key (already in your password manager).
A nightly database dump:
mkdir -p ~/n8n/backups
cd ~/n8n
docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > backups/n8n-$(date +%F).sql.gz
Export workflows and credentials as JSON as well, which is handy for Git or moving single workflows. The --backup flag means all items, pretty printed, one file each:
docker compose exec -u node n8n n8n export:workflow --backup --output=/home/node/.n8n/backups/workflows/
docker compose exec -u node n8n n8n export:credentials --backup --output=/home/node/.n8n/backups/credentials/
Credentials exported this way stay encrypted with your key. Copy the backups off the server, for example to a NAS or with rclone to Google Drive. You can put the pg_dump line in crontab -e to run every night.
How to update n8n safely
- Read the release notes for every version you are skipping.
- Take the database dump above.
- Change
N8N_VERSIONin.env, then:
cd ~/n8n
docker compose pull
docker compose up -d
docker compose logs -f n8n
n8n runs any database migrations on start. If something breaks, set the old version back in .env, restore the dump and run docker compose up -d again. Keep an eye on security advisories too: in late 2025 a critical remote code execution bug (CVE-2025-68613) made prompt updates essential.
Troubleshooting common n8n self-hosting errors
"Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL": you are opening n8n over plain HTTP. Finish Step 7 and use the HTTPS address. Only for a private test on a LAN, add N8N_SECURE_COOKIE=false.
Webhook URLs show http://localhost:5678: set N8N_WEBHOOK_URL=https://your-domain/ (with the trailing slash) and run docker compose up -d to recreate the container. Check it arrived with docker compose exec n8n env | grep N8N_.
"Connection lost" in the editor: the proxy is not passing WebSocket upgrade headers. Compare your Nginx block with Step 6, or if you use Cloudflare, make sure WebSockets are enabled for the zone.
ERR_ERL_UNEXPECTED_X_FORWARDED_FOR in the log: set N8N_PROXY_HOPS to the number of proxies in front of n8n (1 for Nginx only, 2 for Cloudflare plus Nginx).
"Credentials could not be decrypted" after a restore or move: the encryption key differs from the one used to save them. Put the original N8N_ENCRYPTION_KEY back. There is no way to recover credentials without it.
EACCES: permission denied on /home/node/.n8n: happens with bind mounts owned by root. The container runs as user node (UID 1000), so fix it with sudo chown -R 1000:1000 ./n8n_data, or use a named volume as in this guide.
n8n restarts in a loop and the log shows a database connection error: Postgres was not ready or the password in .env changed after the database was created. The password is only applied when the db_data volume is first initialised.
Alternative: install n8n with npm and PM2
This is how the original version of this post (and the video below) installed n8n. It still works on Ubuntu 24.04, but you manage Node.js and updates yourself.
n8n needs Node.js 20.19 to 24.x. Install Node.js 22 LTS from NodeSource, then n8n and PM2:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
node --version
sudo npm install -g n8n pm2
Create the environment file. PM2 does not read .env files by itself, so we pass it through an ecosystem file:
mkdir -p ~/.n8n
nano ~/n8n-ecosystem.config.js
module.exports = {
apps: [{
name: "n8n",
script: "n8n",
env: {
N8N_HOST: "n8n.example.com",
N8N_PROTOCOL: "https",
N8N_PORT: "5678",
N8N_LISTEN_ADDRESS: "127.0.0.1",
N8N_WEBHOOK_URL: "https://n8n.example.com/",
N8N_PROXY_HOPS: "1",
N8N_ENCRYPTION_KEY: "paste-a-key-from-openssl-rand-hex-32",
GENERIC_TIMEZONE: "Australia/Sydney"
}
}]
};
Start it and make it survive reboots:
pm2 start ~/n8n-ecosystem.config.js
pm2 save
pm2 startup
pm2 startup prints one sudo env PATH=... command; run exactly what it prints. Then add the same Nginx and Certbot steps (6 and 7). To update an npm install, follow my safe n8n npm update guide.
Which method should you choose?
| Docker Compose | npm + PM2 | |
|---|---|---|
| Node.js version | Bundled in the image | You install and upgrade it |
| Updates | Change tag, pull, up -d |
npm update -g n8n, check Node.js range |
| Rollback | Set the old tag | Reinstall a specific npm version |
| Postgres | Same Compose file | Install and secure separately |
| Best for | Production VPS or homelab VM | Quick tests, no Docker allowed |
For anything beyond a quick test, Docker Compose is the better choice. If you are on Oracle's free tier, my n8n on Oracle Cloud Always Free guide covers the extra firewall steps Oracle needs.
Next steps
- Build your first assistant: WhatsApp AI agent with n8n.
- Add a site chatbot: n8n chatbot for WordPress.
- Browse the n8n templates I have published.
New to n8n? You can also try n8n Cloud before you self-host.
Frequently asked questions
What is the best way to install n8n on Ubuntu 24.04?+
For a server that will run real workflows, use Docker Compose with PostgreSQL behind a reverse proxy with HTTPS. n8n's own documentation recommends Docker Compose for self-hosting, and Docker makes updates a pull and restart. The npm method still works (it needs Node.js 20.19 to 24.x) and suits a quick test or a machine where you cannot run Docker.
How much RAM does n8n need on a VPS?+
n8n itself starts comfortably on 1 to 2 GB of RAM, but with PostgreSQL in the same stack and a few active workflows, 2 vCPU and 4 GB RAM is a sensible starting size. Workflows that process large files, many items or AI agents need more, so watch memory with docker stats and resize when it stays high.
What is N8N_ENCRYPTION_KEY and why must I back it up?+
N8N_ENCRYPTION_KEY is the key n8n uses to encrypt the credentials stored in its database. If you do not set it, n8n generates a random one on first start and saves it in the /home/node/.n8n folder. Set your own key, keep a copy outside the server, and reuse it after a rebuild: a restored database with a different key cannot decrypt any saved credential.
Should I use WEBHOOK_URL or N8N_WEBHOOK_URL?+
Use N8N_WEBHOOK_URL on current n8n 2.x. The n8n docs say it replaces WEBHOOK_URL, which is deprecated from n8n 2.35.0 and now logs a warning. Set it to your public HTTPS address with a trailing slash, for example https://n8n.example.com/, so webhook and OAuth callback URLs point at your domain instead of localhost:5678.
Is self-hosted n8n free?+
The self-hosted Community Edition is free to run under n8n's Sustainable Use License, which allows internal business use. You pay only for your server. Some features, such as SSO, environments and some enterprise controls, need a paid license key.
How do I update self-hosted n8n in Docker?+
Back up first, change the pinned version in your .env file, then run docker compose pull followed by docker compose up -d in the project folder. Read the n8n release notes before a jump across many versions, and keep the previous version number so you can roll back.
Sources
- Docker Compose server setup, n8n documentation
- Configure the webhook URL, n8n documentation
- Deployment environment variables, n8n documentation
- CLI commands, n8n documentation
- n8n-hosting withPostgres example, GitHub (n8n-io)
- n8n releases, GitHub (n8n-io)
- Install Docker Engine on Ubuntu, Docker documentation
Related videos
Video library- Cloud and Self-Hosting
Set a Static IP and Hostname on Ubuntu Server 24.04 (Netplan)
· 9:02 · 1.6K views
Watch on YouTube - Cloud and Self-Hosting
Ubuntu Server Auto Updates with Crontab | Automate System Updates on Ubuntu 24.04
· 6:17 · 303 views
Watch on YouTube - Cloud and Self-Hosting
How to Install OpenClaw on Ubuntu 24.04 | OpenClaw Setup in Proxmox VM
· 8:22 · 5.9K views
Watch on YouTube
Keep reading
Cloud and Self-HostingDocker for Beginners: A Step-by-Step Guide to ContainerizationAre you new to Docker and curious about how containerization can revolutionize your workflow? In this beginner-friendly guide, we’ll walk you through ... · 2 min read
Cloud and Self-HostingBuilding the Ultimate Homelab: A Beginner’s Guide to Getting StartedAre you ready to dive into the world of homelabs but unsure where to start? Whether you're a tech enthusiast, IT professional, or automation lover,... · 2 min read
Cloud and Self-HostingHow to Achieve Scalability and Flexibility in Your Homelab: A Practical GuideIn today's fast-evolving tech landscape, achieving scalability and flexibility in your homelab is no longer a luxury—it's a necessity. Whether you're ... · 2 min read