Self-host n8n with Docker and PostgreSQL on Ubuntu 26.04

A command line interface showing the text ubuntu@ubuntu:~$ sudo with a blinking cursor

If you want a self-hosted automation stack that won’t disappear behind a paywall or rate-limit your webhooks, n8n on Docker with PostgreSQL is a solid choice. The whole setup takes about 20 minutes and gives you a production-ready environment with persistent storage, HTTPS, and automatic SSL renewal.

This guide walks through every step: Docker installation, Compose configuration, Nginx reverse proxy setup, and Let’s Encrypt certificate issuance. You’ll end up with n8n running securely at your own domain.

Prerequisites

  • Ubuntu 26.04 server (root or sudo-privileged user)
  • A domain name with an A record pointing to your server’s IP address

Step 1: Update System Packages

Before touching anything else, bring all packages current:

apt update -y && apt upgrade -y

Step 2: Install Docker

a golden docker logo on a black background

Install the required dependencies first:

apt install software-properties-common apt-transport-https ca-certificates -y

Add the official Docker GPG key:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Register the Docker repository:

echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Update the package index and install Docker:

apt update -y
apt install docker-ce docker-ce-cli containerd.io -y

Start the service and set it to launch on boot:

systemctl start docker && systemctl enable docker

Confirm it’s running:

systemctl status docker

You should see Active: active (running) in the output before moving on.

️ Step 3: Create the Project Directory and Config Files

Create a dedicated directory for the project:

mkdir ~/n8n
cd ~/n8n

The .env file

Create the environment file and fill in your credentials and domain:

nano .env
POSTGRES_USER=n8n
POSTGRES_PASSWORD=StrongPostgresPasswordHere
POSTGRES_DB=n8n

N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=Strongn8nAdminPasswordHere

N8N_HOST=yourdomain.com
N8N_PORT=5678
N8N_PROTOCOL=https

WEBHOOK_URL=https://yourdomain.com/

Replace StrongPostgresPasswordHere, Strongn8nAdminPasswordHere, and yourdomain.com with your actual values before saving.

The docker-compose.yml file

Create the Compose file:

nano docker-compose.yml
services:

  postgres:
    image: postgres:16
    container_name: n8n-postgres
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n-network

  n8n:
    image: docker.n8n.io/n8nio/n8n
    container_name: n8n
    restart: always
    ports:
      - "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_BASIC_AUTH_ACTIVE: ${N8N_BASIC_AUTH_ACTIVE}
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: ${N8N_PORT}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      WEBHOOK_URL: ${WEBHOOK_URL}
      GENERIC_TIMEZONE: America/Chicago
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
    networks:
      - n8n-network

volumes:
  postgres_data:
  n8n_data:

networks:
  n8n-network:

Save and close the file. The depends_on directive ensures PostgreSQL starts before n8n tries to connect.

Step 4: Start the Containers

text

Spin up both containers in detached mode:

docker compose up -d

Once it finishes, verify both containers are running:

docker ps

You should see two entries: n8n running on port 5678 and n8n-postgres on port 5432. You can reach the n8n interface at http://YourServerIPAddress:5678 or http://YourDomainName.com:5678 at this point, but you’ll see a security warning. The next step clears that.

Step 5: Set Up Nginx as a Reverse Proxy with HTTPS

Install Nginx:

apt install nginx -y
systemctl start nginx && systemctl enable nginx

Create the Nginx config for n8n:

nano /etc/nginx/sites-available/n8n.conf
server {

    server_name yourdomain.com;

    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;
    }

}

Enable the config and verify it:

ln -s /etc/nginx/sites-available/n8n.conf /etc/nginx/sites-enabled/
nginx -t

A successful test returns syntax is ok and test is successful. Restart Nginx:

systemctl restart nginx

Issue the SSL certificate

Install Certbot with the Nginx plugin:

apt install certbot python3-certbot-nginx -y

Request a free Let’s Encrypt certificate for your domain:

certbot --nginx -d yourdomain.com

Certbot will ask for an email address and terms acceptance. Once complete, it automatically updates your Nginx config for HTTPS and schedules automatic renewals. Your n8n instance is now accessible securely at https://yourdomain.com.

✅ Step 6: Complete the n8n Setup Wizard

Open your domain in a browser. You’ll walk through a short setup wizard:

  1. Create the owner account by filling in your name, email, and password, then click Next.
  2. Enter your company details on the following screen.
  3. Skip the license screen if you don’t need paid features.
  4. You’ll land on the n8n admin dashboard, ready to build workflows.

Common Pitfalls

  • DNS not propagated yet: Certbot will fail if your A record hasn’t resolved to your server IP. Confirm with dig yourdomain.com before running Certbot.
  • Port 5678 blocked by firewall: If you can’t reach n8n directly before setting up Nginx, check that your firewall allows port 5678 (and 80/443 for Nginx).
  • Wrong timezone in workflows: The Compose file sets GENERIC_TIMEZONE: America/Chicago. Change this to your timezone before starting the containers if you’re scheduling time-based workflows.
  • Weak passwords in .env: The .env file holds your Postgres and n8n admin credentials in plain text. Lock down file permissions with chmod 600 .env after creation.
Stay on top of AI & Automation with BizStack Newsletter
BizStack  —  Entrepreneur’s Business Stack
Logo