To self-host n8n, you need a Linux server with Docker, a subdomain pointing at it, and n8n's official Docker Compose file, which runs n8n behind Traefik with automatic HTTPS. The parts people get wrong come later: keeping the encryption key safe, setting the webhook URL, and backing up the right folder.
This guide follows n8n's official Docker Compose instructions (checked in October 2026, when the current stable version was 2.43.5) and adds the steps around them that the docs cover on other pages.
Before you start
n8n's own docs are honest about this: self-hosting "requires technical knowledge," and they recommend it for expert users, because mistakes "can lead to data loss, security issues, and downtime." You do not need to be an expert to follow this guide, but you will be the one who fixes things when they break.
You need:
- A Linux server with a public IP address. A small VPS works for personal use.
- A domain where you can add DNS records.
- Docker Engine and the Docker Compose plugin.
- SSH access and basic terminal skills.
A note on the license: n8n's Sustainable Use License allows self-hosting for your own internal business, personal and non-commercial use. It is not an OSI open-source license, and offering n8n as a paid service to others is not allowed. Our Zapier alternatives guide explains the details and lists fully open-source options.
Step 1: Install Docker
Install Docker Engine and Docker Compose with the official instructions for your distribution on docs.docker.com. Then check that both work:
docker --version
docker compose version
Optionally let your user run Docker without sudo:
sudo usermod -aG docker ${USER}
exec sg docker newgrp
Step 2: Point a subdomain at the server
Create an A record for the subdomain you want, for example n8n, pointing at your server's IP address. With the domain example.com, n8n will be reachable at https://n8n.example.com.
Wait until the record resolves before you start the stack, because Traefik requests the HTTPS certificate on the first start.
Step 3: Create the project folder and .env file
mkdir n8n-compose
cd n8n-compose
Create a file called .env in that folder:
DOMAIN_NAME=example.com
SUBDOMAIN=n8n
GENERIC_TIMEZONE=Europe/Berlin
SSL_EMAIL=user@example.com
GENERIC_TIMEZONE is used by the Schedule and Cron nodes. If you leave it out, n8n uses New York time. SSL_EMAIL is the address Let's Encrypt uses for certificate notices.
Then create a folder for files that n8n and the server share:
mkdir local-files
Inside n8n you reach it as /files, for example from the Read/Write Files from Disk node.
Step 4: The Docker Compose file
Create compose.yaml with the content from n8n's documentation:
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}/
- 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:
What this does:
- Traefik listens on ports 80 and 443, redirects HTTP to HTTPS and gets a Let's Encrypt certificate through the TLS challenge. It reads its routing rules from the labels on the n8n container. Only ports 80 and 443 are published, so Traefik's own API on port 8080 stays inside Docker.
- n8n listens on port 5678, but only on
127.0.0.1, so it is not reachable from outside except through Traefik. - The n8n_data volume holds
/home/node/.n8n, where n8n keeps its SQLite database and its encryption key. This is the volume you must back up. - The traefik_data volume stores the certificates.
Step 5: Set the environment variables that matter
The official file sets most of what you need. Three variables deserve a closer look.
N8N_ENCRYPTION_KEY
n8n encrypts every credential you save (API keys, passwords, OAuth tokens) before writing it to the database. On first launch it creates a random key and stores it in the config file in the .n8n folder, which here lives in the n8n_data volume.
If you lose that key, you lose your credentials. A restored database cannot be decrypted without it. You can let n8n keep the generated key and make sure your backups include it, or set your own key so it also lives in your password manager:
# add to .env
N8N_ENCRYPTION_KEY=put-a-long-random-string-here
# add under environment: in compose.yaml
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
You can generate a random string with openssl rand -hex 32. Do this on a fresh install. n8n only uses the variable if the key is not yet in its settings file, so on an existing instance the stored key stays in charge.
N8N_WEBHOOK_URL
Behind a reverse proxy, n8n cannot work out its public address by itself: it runs on port 5678 inside, while the world reaches it on 443. N8N_WEBHOOK_URL tells n8n the public address, so it shows correct URLs in the editor and registers correct webhooks with other services.
The variable is called N8N_WEBHOOK_URL from n8n 2.30.0. Older versions and older tutorials use WEBHOOK_URL, which is deprecated from 2.35.0 and logs a warning. The official compose file above already uses the new name.
N8N_PROXY_HOPS
n8n's reverse proxy docs also recommend setting N8N_PROXY_HOPS=1 when n8n runs behind one proxy, and making sure the proxy passes the X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto headers. Traefik sends these headers by default. Add the variable under environment::
- N8N_PROXY_HOPS=1
Step 6: Start n8n
sudo docker compose up -d
Open https://n8n.example.com and create the owner account. n8n is only reachable over HTTPS. If the page does not load, check your firewall (ports 80 and 443 must be open) and whether DNS points to the right IP.
To stop it:
sudo docker compose stop
Step 7: Backups
According to n8n's backup docs, a complete backup of this setup is the .n8n folder. With the default SQLite database it holds everything: the database with users, workflows, credentials and execution data, plus the encryption key. If you later switch to PostgreSQL, back up the database with its own tools and keep backing up .n8n for the key.
Stop n8n before you copy the folder, because copying the SQLite file while n8n writes to it can give you a broken backup. With Docker Compose, the volume name gets the project folder as a prefix, so here it is n8n-compose_n8n_data:
cd n8n-compose
sudo docker compose stop n8n
sudo docker run --rm -v n8n-compose_n8n_data:/data -v "$(pwd)":/backup alpine \
tar czf /backup/n8n-data-$(date +%F).tar.gz -C /data .
sudo docker compose start n8n
Copy the archive to another machine or to object storage. A backup on the same server will not help when the server is gone.
For extra safety you can also export workflows and credentials as JSON with n8n's CLI. Write them inside the volume, because other folders in the container are lost when it is recreated:
sudo docker compose exec -u node n8n n8n export:workflow --backup --output=/home/node/.n8n/backups/workflows/
sudo docker compose exec -u node n8n n8n export:credentials --backup --output=/home/node/.n8n/backups/credentials/
These exports contain workflows and credentials only, not users, execution history, variables or settings. They are handy for moving workflows, but they do not replace the folder backup. Credentials in them are still encrypted with your key.
Step 8: Updates
n8n releases a new minor version most weeks. Its docs recommend updating often, at least once a month, so you never have to jump many versions at once. Read the release notes for breaking changes first, and take a backup before every update.
cd n8n-compose
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
The compose file uses n8nio/n8n without a tag, which means the latest stable version. If you prefer to decide when to update, pin a version in compose.yaml, for example image: n8nio/n8n:2.43.5, and change it when you are ready.
Security checklist
- Keep the server's operating system updated and allow only ports 22, 80 and 443 in the firewall.
- Use a strong owner password and turn on two-factor authentication for your account.
- Do not share the
.envfile or backups: together they contain the keys to all your connected services. - Install community nodes only from sources you trust, since they run code on your server.
The managed route
If you do not want to look after a server, you have two options. n8n offers its own paid n8n Cloud. Or you use a managed host that runs the open-source edition for you: on blitz.cloud, n8n installs in one click with HTTPS included, hosted in the EU. There is a free plan without a credit card, where apps sleep after 30 minutes without visitors. That is fine for building and testing workflows, but scheduled workflows need an app that stays awake.
Whichever route you choose, export your workflows from time to time. They are plain JSON and move easily between instances.
FAQ
Is self-hosted n8n free?
The Community edition is free and has, in n8n's words, "almost the complete feature set." Features such as SSO, projects, environments and Git version control need a paid license. You still pay for the server.
What happens if I lose N8N_ENCRYPTION_KEY?
Your saved credentials cannot be decrypted. Workflows survive, but you will have to enter every API key and reconnect every account again. Keep the key in your backups and in a password manager.
Should I use WEBHOOK_URL or N8N_WEBHOOK_URL?
N8N_WEBHOOK_URL on n8n 2.30.0 and later. WEBHOOK_URL still works on recent versions but is deprecated from 2.35.0.
Do I need PostgreSQL?
Not to get started. The official compose setup uses SQLite, stored in the n8n_data volume. n8n supports PostgreSQL for larger setups; its docs explain how to switch.
Can I run n8n without a domain?
For local testing, yes, on http://localhost:5678. To receive webhooks from other services, you need a public HTTPS address, which in practice means a domain or a managed host that provides one.