Install n8n on Ubuntu Server Step by Step
I’ve installed n8n on fresh Ubuntu VPS instances where one small misstep (like a missing volume or a wrong webhook URL) broke automations later.
Install n8n on Ubuntu Server Step by Step to get a stable, secure setup with persistent data, HTTPS, and clean updates.
What you need before you start
Have these ready so you don’t pause mid-install:
- An Ubuntu server (22.04 LTS or 24.04 LTS) with SSH access and a sudo user.
- At least 1 GB RAM for light usage (2 GB+ is better if you run many workflows).
- (Recommended) A domain or subdomain pointing to your server’s public IP if you want HTTPS and reliable webhooks.
If you want to review the official project pages once (and only once), use these links: n8n, Docker on Ubuntu, Ubuntu, Caddy.
Step 1: Update Ubuntu and set a safe baseline
Start with upgrades, then enable a firewall. This reduces “mystery” failures later.
sudo apt updatesudo apt -y upgradesudo reboot
After reconnecting by SSH, enable UFW and allow SSH so you don’t lock yourself out.
sudo apt -y install ufwsudo ufw allow OpenSSH sudo ufw enablesudo ufw status
Real-world pitfall (and fix): UFW is easy to misconfigure when you’re moving fast. If you plan to expose n8n to the internet through HTTPS, you’ll also allow ports 80/443 later (not 5678).
Step 2: Install Docker Engine and Docker Compose (plugin)
Docker Compose is the cleanest way to run n8n on a server because it makes upgrades and restarts predictable.
sudo apt -y install ca-certificates curl gnupgsudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt updatesudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Verify installation:
docker --versiondocker compose versionsudo systemctl status docker --no-pager
(Optional but convenient) run Docker without sudo:
sudo usermod -aG docker $USERnewgrp docker
Real-world pitfall (and fix): Docker group membership grants powerful access. If this is a shared server, keep Docker restricted to admin users only, and prefer sudo.
Step 3: Create the n8n directory and persistent volumes
The fastest way to lose an n8n instance is running it without persistent storage. Use a dedicated directory and volumes.
sudo mkdir -p /opt/n8nsudo chown -R $USER:$USER /opt/n8ncd /opt/n8n
Step 4: Create a production-ready docker-compose.yml
This setup keeps n8n bound to localhost so the public internet can’t hit port 5678 directly. You’ll publish it safely via HTTPS in the next step.
First, generate and save a strong encryption key. This key protects stored credentials. If you change it later, previously saved credentials can become unusable.
openssl rand -hex 32
Create the Compose file:
nano docker-compose.yml
services:n8n: image: n8nio/n8n:latest restart: unless-stopped ports: - "127.0.0.1:5678:5678" environment: - TZ=America/New_York - N8N_HOST=n8n.example.com - N8N_PORT=5678 - N8N_PROTOCOL=https - WEBHOOK_URL=https://n8n.example.com/ - N8N_PROXY_HOPS=1 - N8N_ENCRYPTION_KEY=PASTE_YOUR_64_CHAR_HEX_KEY_HERE volumes: - n8n_data:/home/node/.n8n volumes:n8n_data:
Replace:
- TZ with your preferred US timezone.
- N8N_HOST and WEBHOOK_URL with your real domain.
- N8N_ENCRYPTION_KEY with the value you generated and stored safely.
Real-world pitfall (and fix): The most common “everything works except webhooks” problem is a wrong webhook base URL behind a reverse proxy. Setting WEBHOOK_URL and N8N_PROXY_HOPS avoids broken callback URLs when n8n is internally on 5678 but publicly on 443.
Step 5: Start n8n and confirm it’s healthy
Bring the stack up, then confirm the container is running.
docker compose up -ddocker compose psdocker compose logs -f --tail=100
At this point, n8n is listening on 127.0.0.1:5678 only. That’s intentional. Next you’ll publish it through HTTPS.
Step 6: Add HTTPS with a reverse proxy (Caddy)
Caddy is a strong fit on Ubuntu servers because it can automatically handle TLS certificates. You’ll proxy from 443 to n8n on localhost:5678.
Install Caddy and allow web ports:
sudo apt -y install caddysudo ufw allow 80sudo ufw allow 443
Create a Caddyfile:
sudo nano /etc/caddy/Caddyfile
n8n.example.com {reverse_proxy 127.0.0.1:5678}
Reload Caddy:
sudo caddy validate --config /etc/caddy/Caddyfilesudo systemctl reload caddy
Now open https://n8n.example.com and complete the initial setup in the UI.
Real-world pitfall (and fix): If your DNS isn’t pointing correctly, Caddy can’t complete TLS issuance. Confirm the A record resolves to your server IP, then retry. Keep n8n bound to localhost so random traffic never hits 5678 directly.
Docker vs npm install: what you should use on a server
| Option | Best for | Main downside | How to reduce the downside |
|---|---|---|---|
| Docker Compose | Production servers, predictable upgrades, simple restarts | Extra layer (Docker) to secure and update | Pin versions when needed, schedule updates, keep backups of volumes |
| npm / Node install | Custom Node environments and advanced host-level tuning | More moving parts (Node versions, process manager, updates) | Use a process manager and strict version control for Node and n8n |
Hardening that actually matters for n8n
- Keep port 5678 private: bind it to
127.0.0.1as shown, and expose only 80/443 publicly. - Protect the encryption key: store it in a password manager. If it changes, stored credentials can break.
- Use a dedicated domain: it prevents mixed-content and webhook confusion across environments.
- Limit server access: prefer SSH keys, disable password SSH if possible, and keep sudo access minimal.
Backups and upgrades without drama
When n8n becomes business-critical, you need a repeatable upgrade pattern and a fast rollback plan.
Backup the named volume regularly (store the resulting archive off the server):
cd /opt/n8ndocker compose down docker run --rm -v n8n_n8n_data:/data -v $(pwd):/backup ubuntu tar czf /backup/n8n_data_backup.tar.gz -C /data .docker compose up -d
Upgrade n8n safely:
cd /opt/n8ndocker compose pull docker compose up -ddocker image prune -f
Real-world pitfall (and fix): “latest” is convenient, but it can surprise you. If you run revenue-impacting workflows, pin the image tag to a specific n8n version and upgrade intentionally after a backup.
Troubleshooting the top issues
- UI loads, but webhooks show localhost:5678: confirm
WEBHOOK_URLmatches your HTTPS domain and ends with a trailing slash. - Reverse proxy works, but integrations fail callbacks: re-check the domain in
N8N_HOSTandWEBHOOK_URL, then restart the container. - Credentials suddenly fail after a change: verify
N8N_ENCRYPTION_KEYnever changed between deployments. - Can’t access n8n publicly: confirm DNS points to the server, UFW allows 80/443, and Caddy is running.
FAQ: deep answers for common n8n on Ubuntu questions
Can you install n8n on Ubuntu without Docker?
Yes, but you’ll need a managed Node environment, a process manager, and a tighter upgrade routine. On most Ubuntu servers, Docker Compose stays easier to maintain because your runtime, ports, volumes, and restarts are all declared in one place.
What’s the safest way to expose n8n on the internet?
Keep n8n on 127.0.0.1:5678 and expose only HTTPS through a reverse proxy on 443. This prevents direct scanning of n8n and forces all traffic through TLS.
How do you fix n8n webhook URLs when using a reverse proxy?
Set WEBHOOK_URL to your public HTTPS base URL and keep it consistent. This ensures n8n registers correct callback URLs with providers and shows the right URLs in the editor.
Do you need a domain for n8n?
If you use triggers that rely on inbound webhooks (Stripe, GitHub, Slack, Calendars, and many others), a domain is strongly recommended because it gives you stable HTTPS URLs and reduces configuration mismatches.
Where is n8n data stored in this setup?
All persistent n8n state is stored inside the Docker named volume (n8n_data). That’s why the volume backup is non-negotiable if you care about workflows, credentials, and history.
How do you move n8n to a new server?
Export your volume backup, copy docker-compose.yml, keep the same N8N_ENCRYPTION_KEY, then restore the volume on the new machine. If the encryption key changes, credentials can stop working even if workflows import correctly.
Conclusion
Once n8n is running behind HTTPS with persistent storage and a fixed webhook URL, it behaves like a production service instead of a fragile hobby install. Keep your encryption key safe, back up the volume, upgrade intentionally, and you’ll have an Ubuntu-based n8n instance you can trust for real workflows.

