EUR
european

EUR

usa

USD

Ex. VAT Ex. VAT 0%

Installing n8n with Docker and PostgreSQL

n8n is a workflow automation platform that you can run on your own server. A self-hosted instance keeps your workflows, credentials and execution data under your control and has no limits on the number of workflows or executions beyond what the server can handle.

This guide sets up n8n on a dedicated server with PostgreSQL, HTTPS and backups. It uses three containers managed by Docker Compose:

  • n8n, the automation platform itself;
  • PostgreSQL, the database for workflows, credentials and execution history;
  • Caddy, a reverse proxy that gets and renews an HTTPS certificate automatically.

By default n8n stores its data in SQLite. This guide uses PostgreSQL instead: it can be backed up while n8n is running, and it is the database n8n recommends for self-hosting.

The steps were tested on Ubuntu 24.04 LTS with n8n 2.46.7, PostgreSQL 17, Docker 29 and Docker Compose v5.

Before you start

You will need:

  • a server running Ubuntu 24.04 LTS with root access over SSH;
  • a domain or subdomain for n8n, for example n8n.example.com;
  • an A record for that name pointing to the server's IPv4 address.

If the domain also has an AAAA record, the server must be reachable over IPv6 at that address. Otherwise Let's Encrypt may try IPv6 first and fail to issue the certificate.

Check that the record resolves to your server before you continue:

dig +short n8n.example.com

The command should return the server's IP address. Certificate issuance in Caddy depends on this, so wait until DNS has propagated.

All commands below are run as root. If you work as a regular user with sudo rights, add sudo before each command.

Install Docker

Install Docker Engine and the Compose plugin from the official Docker repository. The docker.io package from the Ubuntu repository is older and does not include Compose v2.

apt update && apt install -y ca-certificates curl install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc

Add the repository:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list

Install the packages:

apt update apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Check that both tools are available:

docker --version docker compose version

Create the project directory and secrets

All n8n files will live in /opt/n8n:

mkdir -p /opt/n8n && cd /opt/n8n

Create the .env file with your domain, time zone and generated secrets. Replace n8n.example.com and the time zone with your own values:

cat > .env <<EOF DOMAIN=n8n.example.com GENERIC_TIMEZONE=Europe/Amsterdam POSTGRES_USER=n8n POSTGRES_DB=n8n POSTGRES_PASSWORD=$(openssl rand -hex 24) N8N_ENCRYPTION_KEY=$(openssl rand -hex 32) EOF chmod 600 .env

GENERIC_TIMEZONE sets the time zone used by the Schedule Trigger and other time-based nodes. Without it, n8n uses America/New_York.

N8N_ENCRYPTION_KEY is the key n8n uses to encrypt the credentials you save in it: API tokens, passwords, OAuth secrets. If this key is lost, the credentials in a restored database cannot be decrypted and every one of them has to be entered again.

Warning

Copy the .env file to a safe place outside the server right away, for example a password manager. A database backup without N8N_ENCRYPTION_KEY restores your workflows, but not your credentials.

Create the Docker Compose file

Create /opt/n8n/compose.yaml:

services: postgres: image: postgres:17 restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 n8n: image: docker.n8n.io/n8nio/n8n restart: unless-stopped depends_on: postgres: condition: service_healthy 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: ${DOMAIN} N8N_PROTOCOL: https N8N_PROXY_HOPS: 1 N8N_WEBHOOK_URL: https://${DOMAIN}/ GENERIC_TIMEZONE: ${GENERIC_TIMEZONE} TZ: ${GENERIC_TIMEZONE} ports: - "127.0.0.1:5678:5678" volumes: - n8n_data:/home/node/.n8n caddy: image: caddy:2 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config volumes: postgres_data: n8n_data: caddy_data: caddy_config:

A few details in this file matter for security and stability

PostgreSQL has no ports section. The database is reachable only by the other containers in the project, never from the internet.

The n8n port is published as 127.0.0.1:5678. Users reach n8n only through Caddy over HTTPS, while you can still check n8n locally on the server.

Warning

Do not shorten the n8n port line to "5678:5678". Docker adds its own firewall rules for published ports, and they bypass UFW. Without the 127.0.0.1 prefix, n8n becomes reachable over plain HTTP from anywhere, even if UFW blocks port 5678.

The depends_on condition with a healthcheck makes n8n wait until PostgreSQL is ready to accept connections. Without it, n8n can start first on a cold boot and fail to connect to the database.

N8N_PROXY_HOPS tells n8n that it runs behind one reverse proxy, so it reads the real client address from the proxy headers. N8N_WEBHOOK_URL sets the public address n8n shows for webhook URLs. In n8n versions before 2.30, this variable is called WEBHOOK_URL.

Configure HTTPS with Caddy

Create /opt/n8n/Caddyfile with your domain:

n8n.example.com { reverse_proxy n8n:5678 }

That is the whole configuration. On first start, Caddy requests a Let's Encrypt certificate for the domain, redirects HTTP to HTTPS and renews the certificate before it expires. Ports 80 and 443 must be open for this to work.

Configure the firewall

Allow SSH first, then HTTP and HTTPS, and enable UFW:

ufw allow OpenSSH ufw allow 80/tcp ufw allow 443/tcp ufw --force enable ufw status

Info

If UFW was already enabled with your own rules, run ufw status before making changes and keep the rules you need. Allowing OpenSSH before enabling UFW keeps your current SSH session from being cut off.

Start n8n

Start the containers in the background:

cd /opt/n8n docker compose up -d

The first start downloads the images and takes a minute or two. Check that all three containers are running and PostgreSQL is healthy:

docker compose ps

Look at the n8n log:

docker compose logs n8n --tail 50

The log should end with a line like:

Editor is now accessible via: https://n8n.example.com

Check that n8n answers locally and through Caddy:

curl -s http://127.0.0.1:5678/healthz curl -s https://n8n.example.com/healthz

Both commands should return {"status":"ok"}.

If the second command fails, check the Caddy log with docker compose logs caddy. The most common cause is a DNS record that does not point to this server yet, or ports 80 and 443 being closed.

Create the owner account

Open https://n8n.example.com in your browser. On the first visit, n8n asks you to create the owner account. This account has full rights over the instance and manages all other users.

Enter your email, first and last name, and a password, then click Next. The password must be at least 8 characters long and contain at least one number and one capital letter. Use a strong one: the n8n editor is open to the internet, and anyone who gets into it can run commands on the server through your workflows.

After the first login, n8n shows a short survey, Customize n8n to you. The questions are optional. Click Get started to close it, with or without answering.

Next, n8n offers a free license key for several Community Edition features: advanced debugging, execution search and tagging, and folders for workflows. The key is sent to the email you enter. Click Send me a free license key to get it, or Skip if you do not need these features.

The overview page opens. The installation is complete and you can create your first workflow.

Back up n8n

An n8n backup consists of two parts: a PostgreSQL dump with your workflows, credentials, users and execution history, and the .env file with N8N_ENCRYPTION_KEY. Neither part is useful without the other.

In this setup, n8n keeps all of its state in PostgreSQL. The n8n_data volume does not need to be backed up: a full restore works from the database dump and the .env file alone.

Create a backup directory and dump the database:

mkdir -p /opt/n8n-backup cd /opt/n8n docker compose exec -T postgres pg_dump -U n8n -d n8n -Fc > /opt/n8n-backup/n8n-db.dump cp .env compose.yaml Caddyfile /opt/n8n-backup/

The dump is made while n8n is running, so no downtime is needed.

A backup that stays on the same server will not help if the server's disk fails. Copy /opt/n8n-backup to external storage on a regular schedule. You can use INTROSERV Cloud Storage for this, as described in the guide Connecting INTROSERV Cloud Storage to the server.

Restore from a backup

To restore n8n on a new server, install Docker as described above and copy the backup directory to /opt/n8n-backup on that server. Then put the configuration files in place:

mkdir -p /opt/n8n cp /opt/n8n-backup/.env /opt/n8n-backup/compose.yaml /opt/n8n-backup/Caddyfile /opt/n8n/ chmod 600 /opt/n8n/.env

If the new server has a different IP address, update the domain's A record first.

Start only the database:

cd /opt/n8n docker compose up -d postgres

Wait until docker compose ps shows PostgreSQL as healthy, then restore the dump:

docker compose exec -T postgres pg_restore -U n8n -d n8n --clean --if-exists < /opt/n8n-backup/n8n-db.dump

Start the rest of the stack:

docker compose up -d

Log in with the same owner account and check that your workflows and credentials are in place. Saved credentials are decrypted with N8N_ENCRYPTION_KEY from the restored .env file, so they work without being entered again.

Update n8n

n8n releases updates often. To update to the latest version, pull the new images and recreate the containers:

cd /opt/n8n docker compose pull docker compose up -d docker image prune -f

The last command removes the old images that are no longer used.

Check the new version:

docker compose exec n8n n8n --version

Tip

Make a backup before every update. On start, a new n8n version can migrate the database schema, so the reliable way back to the previous version is to restore a dump made before the update.

The PostgreSQL image is pinned to major version 17, so docker compose pull only installs minor updates of PostgreSQL. Moving to a new major version requires a dump and restore and is not done automatically.

What's next

Your n8n instance is ready for work. From here you can:

  1. invite other users to the instance;
  2. set up SMTP so n8n can send invitations and password reset emails;
  3. connect your services and build your first workflows.

If you plan to run AI workflows with local language models, see the guide Installing the n8n AI Starter Kit with Ollama.

VAT

  • Other

    Ex. VAT

    0%
  • austria

    Austria

    20%
  • Belgium

    Belgium

    21%
  • Bulgaria

    Bulgaria

    20%
  • Croatia

    Croatia

    25%
  • Cyprus

    Cyprus

    19%
  • Czech Republic

    Czech Republic

    21%
  • Denmark

    Denmark

    25%
  • Estonia

    Estonia

    22%
  • France

    France

    20%
  • Finland

    Finland

    24%
  • Germany

    Germany

    19%
  • Greece

    Greece

    24%
  • Hungary

    Hungary

    27%
  • Ireland

    Ireland

    23%
  • Italy

    Italy

    22%
  • Latvia

    Latvia

    21%
  • Lithuania

    Lithuania

    21%
  • Luxembourg

    Luxembourg

    17%
  • Malta

    Malta

    18%
  • Netherlands

    Netherlands

    21%
  • Poland

    Poland

    23%
  • Portugal

    Portugal

    23%
  • Romania

    Romania

    19%
  • Slovakia

    Slovakia

    20%
  • Slovenia

    Slovenia

    22%
  • Spain

    Spain

    21%
  • Sweden

    Sweden

    25%
  • USA

    USA

    0%
european
states
  • kingdom
  • germany
  • Español
  • French
  • Hrvatska
  • Italiano
  • Poland
  • Slovenski
  • ukraine
  • Türkçe
  • Русский
  • Other
  • Austria
  • Belgium
  • Bulgaria
  • Croatia
  • Cyprus
  • Czech Republic
  • Denmark
  • Estonia
  • Finland
  • France
  • Germany
  • Greece
  • Hungary
  • Ireland
  • Italy
  • Latvia
  • Lithuania
  • Luxembourg
  • Malta
  • Netherlands
  • Poland
  • Portugal
  • Romania
  • Slovakia
  • Slovenia
  • Spain
  • Sweden
  • USA