- A complete setup: n8n, a Postgres database and automatic HTTPS, in one Docker Compose file you can copy.
- Four settings decide whether your n8n survives its first update: the database, the encryption key, the public address and never running two copies.
- What we measured running n8n for customers: how long it takes to start, how long an update is offline, and the settings that fail without a word.
This is a walkthrough for running n8n on a server you control. By the end you will have n8n on your own domain with HTTPS, its data in Postgres, and a way to back it up and update it. It takes about half an hour if you have used a terminal before.
We host n8n for customers, so we have a view on this, and we say where at the end. Most of what follows applies wherever you run it.
The three ways to run n8n
- n8n Cloud. Run by the makers of n8n. Nothing to set up, and plans are priced by how many workflow runs you make.
- Your own server. Full control and no run limits. You do the setup, the updates and the backups. This guide is about this option.
- Managed hosting. Someone else runs the server, and you still get your own instance and database. That is what we sell, at the end of the post.
What you need
- A Linux server you can reach over SSH, with Docker and the Compose plugin installed. Leave a few gigabytes of disk free: the n8n image alone is about 1 GB.
- A domain or subdomain you control, such as
n8n.example.com, with anArecord pointing at the server's IP address. - Ports 80 and 443 open to the internet, so HTTPS certificates can be issued.
Step 1: the settings file
Make a folder for n8n and create a file called .env in it. Everything secret or
specific to you lives here, and nowhere else:
mkdir ~/n8n && cd ~/n8n
cat > .env <<EOF
N8N_HOST=n8n.example.com
TIMEZONE=Asia/Colombo
POSTGRES_PASSWORD=$(openssl rand -hex 24)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env
Change N8N_HOST to your domain and TIMEZONE to yours. The two random values are made
for you.
Copy N8N_ENCRYPTION_KEY somewhere safe now, off the server. Every password and API
key you save in n8n is locked with it. Lose the key and every saved credential has to be
entered again by hand; there is no way to recover them.
Step 2: the Compose file
Create docker-compose.yml beside it:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
retries: 5
n8n:
image: n8nio/n8n:2.43.3
restart: unless-stopped
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: https
N8N_PROXY_HOPS: 1
WEBHOOK_URL: https://${N8N_HOST}/
N8N_WEBHOOK_URL: https://${N8N_HOST}/
N8N_EDITOR_BASE_URL: https://${N8N_HOST}/
GENERIC_TIMEZONE: ${TIMEZONE}
TZ: ${TIMEZONE}
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
volumes:
db_data:
n8n_data:
caddy_data:
What each part is doing:
postgreskeeps your workflows, credentials and run history. Without it n8n writes everything into a small file inside its own container.n8npins an exact version (2.43.3is the one we run at the time of writing). A floating tag likelatestupdates n8n whenever the image is pulled, which is not when you want an update to happen.- The
DB_settings point n8n at Postgres. n8n does not read theDATABASE_URLsetting most apps use, so it needs these spelled out. - The three address settings tell n8n its public address, so the webhook links it hands out and the editor both work behind HTTPS.
caddyputs HTTPS in front of n8n and renews the certificate itself.
Step 3: HTTPS
Create a file called Caddyfile with your domain in it:
n8n.example.com {
reverse_proxy n8n:5678
}
Then start everything:
docker compose up -d
docker compose logs -f n8n
When the log says the editor is accessible, open https://n8n.example.com. n8n asks you
to create the owner account. That is the account that manages everything else, so give
it a strong password.
Step 4: backups
Your n8n is now two things you cannot afford to lose: the database and the encryption
key. The key is in .env, and you copied it in Step 1. For the database, a nightly dump
is enough to start with:
docker compose exec -T postgres pg_dump -U n8n n8n | gzip > ~/n8n-backup-$(date +%F).sql.gz
Put that in a nightly cron job, and copy the files off the server. A backup that only lives on the machine it is protecting goes down with that machine.
Step 5: updating n8n
Read the release notes for the version you are moving to, take a backup, then change the
tag in docker-compose.yml and run:
docker compose pull n8n
docker compose up -d n8n
Compose stops the old n8n and starts the new one, and n8n updates its database on the way up. Expect it to be offline for a short while; the numbers we measured are below.
Four mistakes that only show up later
Each of these works perfectly on day one and fails on a later restart or update.
1. No database, or the wrong settings for it. Without the DB_ settings, n8n writes
to a file inside the container. Recreate the container, which every update does, and
your workflows are gone. Setting DATABASE_URL looks right and is ignored.
2. An encryption key nobody wrote down. If you do not set N8N_ENCRYPTION_KEY, n8n
makes one up and saves it in its own folder. That works as long as the folder survives.
The day it does not, every saved credential stops opening. Set the key yourself and keep
a copy off the server. The same key is also what lets credentials move with you to a new
server.
3. A wrong public address. Behind a proxy, n8n can only guess its own address, and it
often guesses http://localhost:5678. Then the webhook links it shows you point nowhere.
n8n 2.x has also renamed the webhook setting from WEBHOOK_URL to N8N_WEBHOOK_URL
and says so in its log, while 1.x reads only the old name. Setting both, as above, works
on either.
4. Two copies at once. If two n8n processes ever share one database, both run every scheduled workflow, so every scheduled email goes out twice. This is easy to cause by accident: plenty of hosting platforms start the new version before stopping the old one, to avoid downtime. For n8n that default is wrong. Stop the old copy first, and accept a short gap.
What we measured running n8n
These come from running n8n 2.43.3 on our own servers in October 2026.
- First start, against an empty database with its setup included, took 14 to 16 seconds.
- The web address answers before the editor is ready. A health check on
/passed about 35 seconds before n8n reported the editor as ready. If something checks whether n8n is up, point it at/healthz/readinessinstead. - An update is offline for about half a minute: around 18 seconds with nothing answering, then around 10 seconds where the editor loaded but could not reach its own API yet. That gap is what stopping the old copy first costs, and it is worth it.
- Pull from
n8nio/n8n. Thedocker.n8n.ioaddress in some n8n examples refused our downloads with rate-limit errors, whilen8nio/n8nserved the same image. - Python Code steps need extra setup. n8n 2.x runs Python code in a separate runner container that this guide does not include. JavaScript Code steps work as they are.
Point n8n's AI steps at more models
n8n's AI steps use an OpenAI credential, and that credential has a Base URL field. Any
service that speaks the same API works there. Ours is https://api.roar-ai.com/v1: with
a Roar AI key in the credential, the same AI steps can use any model we serve, and the
spend shows up against that key. This works whether your n8n runs on your server or ours.
Or skip the server
If the steps above are more upkeep than you want, we run this setup for you. n8n hosting on Roar AI launches n8n from your console with its own database, an encryption key kept in your own settings, addresses that follow your domain, updates that stop the old copy first, and its AI steps already connected to every model we serve. You can run it in Colombo, Bangalore or Singapore, and an Australian team's n8n runs in Singapore for now (why).
It has limits worth knowing before you choose it. Python Code steps don't work. A webhook that waits for its workflow to finish must answer within about 2 minutes. Each update has the short gap described above.
n8n is a trademark of n8n GmbH. Roar AI hosts and supports n8n and is not affiliated with or endorsed by n8n GmbH.