Skip to main content

Command Palette

Search for a command to run...

How to Self-Host n8n for Free Forever on Oracle Cloud

Updated
8 min read
How to Self-Host n8n for Free Forever on Oracle Cloud

n8n stands out as one of the most powerful open-source low-code AI workflow automation tools available. While cloud-hosted n8n can get expensive quickly, Oracle Cloud's Always Free tier offers an incredible opportunity to run n8n completely free, forever.

In this comprehensive guide, I'll walk you through setting up n8n on Oracle Cloud Infrastructure (OCI) using their generous Always Free tier, which includes compute instances that never expire.

💡Why Oracle Cloud's Always Free Tier?

Oracle Cloud's Always Free tier is genuinely impressive:

  • 2 AMD-based Compute VMs with 1/8 OCPU and 1 GB memory each

  • Up to 4 Arm-based Ampere A1 cores and 24 GB of memory (can be used as one VM or split)

  • 200 GB total Block Volume storage

  • 10 GB Object Storage

  • Always Free - no time limits, no credit expiration

The Arm-based instances are particularly powerful for running n8n, offering excellent performance for automation workflows.

For a more detailed overview of the Oracle Free Tier, refer to Oracle Free Tier & Always Free Resources.

✅ Prerequisites

Before we begin, you'll need:

  • Oracle Cloud account (free sign-up)

  • A registered Domain name (recommended for HTTPS)

🏗️ Architecture

This setup demonstrates how to run n8n securely inside an OCI Compute VM using Docker and Traefik as the reverse proxy.

1. Traffic Flow

  • A user accesses n8n.example.com, which resolves via DNS to the VM’s public IP.

  • Requests on ports 80/443 reach the Traefik container inside the VM.

  • Traefik forwards HTTPS traffic through the traefik-public network to the n8n container.

2. Security & Certificates

  • Traefik manages SSL/TLS certificates automatically with Let’s Encrypt CA.

  • Certificates are issued and renewed using the ACME TLS challenge.

  • All certificates are stored securely in ./letsencrypt/acme.json.

3. Service Discovery

  • Traefik integrates with the Docker Socket to dynamically discover running containers.

  • This eliminates manual configuration whenever services are added or updated.

4. Application Layer

  • n8n container hosts the workflow automation platform.

  • Postgres container provides persistent database storage, connected via the n8n-network (port 5432).

5. Infrastructure

  • All components (Traefik, n8n, Postgres) run as Docker containers inside a single OCI Compute VM.

  • Networking is logically separated using Docker networks (traefik-public and n8n-network).

Step 1: 📝Create Your Oracle Cloud Account

  1. Visit oracle.com/cloud/free

  2. Sign up for a free account

  3. Complete the verification process (requires credit card for verification, but won't be charged)

  4. Wait for account activation

Step 2: 🖥️✨ Set Up Your Compute Instance

  1. Create a Virtual Cloud Network

    • Log into your OCI Console

    • Navigate to Networking → Virtual cloud networks

  • We will take a /16 subnet for the VCN, in this case 10.0.0.0/16

  1. Create a /24 subnet inside the VCN where the VM instance will be connected

    • 10.0.1.0/24

    • Create it as a Public Subnet

  1. Create a Internet gateway for the VCN

  2. Create the VM Instance

    • Navigate to Compute → Instances

    • Click Create Instance

    • Image and Shape:

      • Image: Ubuntu 22.04 LTS (Always Free-eligible)

      • Shape: VM.Standard.A1.Flex (Arm-based)

      • OCPU: 2 (or all 4 if you want maximum performance)

      • Memory: 12 GB (or up to 24 GB)

  • In the Networking tab, Select the VCN & subnet that we already created at the beginning

SSH Keys:

  • Generate a new key pair and download both public and private keys

  • Keep the private key secure - you'll need it to access your server

Click Create and wait for the instance to provision

  1. Configure Network Security

    1. Go to Networking → Virtual Cloud Networks

    2. Click on your VCN

    3. Click on Security ListsDefault Security List

    4. Add ingress rules:

       Port 22 (SSH): 0.0.0.0/0
       Port 80 (HTTP): 0.0.0.0/0
       Port 443 (HTTPS): 0.0.0.0/0
       Port 5678 (n8n): 0.0.0.0/0 (temporary, we'll remove this later)
      

Step 3: 🔑 Connect to Your Instance

1. Note your instance's public IP address

2. Connect via SSH:

bash ssh -i /path/to/your/private-key ubuntu@YOUR_PUBLIC_IP

For Windows users with PuTTY, convert the private key to .ppk format first

Step 4: ⚙️ Prepare the Server

Update the System

bash sudo apt update && sudo apt upgrade -y

Install Docker and Docker Compose

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

#install the latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Add your user to docker group
sudo usermod -aG docker $USER

Reconnect to your server after logging out.

Step 5: 🐳 Deploy n8n with Docker Compose

Prepare the project directories and files on the VM

~/n8n-oracle-cloud/
├── traefik/                 
│   └── acme.json            # ACME storage for Let's Encrypt certificates
├── prod/                    
    ├── .env                 # Environment variables
    └── docker-compose.yaml  # Main Compose stack

🚀 All the code is available on my GitHub Repo!
👉 Clone this repository to your VM to get all the necessary code 🖥️💻

# Example: clone repo to home
cd ~
git clone https://github.com/sumitsaz23/n8n-docker-traefik-postgres.git n8n-oracle-cloud
cd n8n-oracle-cloud

Secure acme.json for Traefik

Traefik requires acme.json to be present and readable/writable by the Traefik container but with strict permissions (600)

# create acme file and set permissions
touch traefik/acme.json
chmod 600 traefik/acme.json

Create docker-compose.yml file:


services:

  # -------------------------
  # Postgres (self-managed)
  # -------------------------
  postgres:
    image: postgres:16-alpine   # Postgres 16 (lightweight alpine)
    container_name: n8n_postgres
    restart: unless-stopped
    # Named volume for persistent DB files
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${DB_POSTGRESDB_USER}
      - POSTGRES_DB=${DB_POSTGRESDB_DATABASE}
      - POSTGRES_PASSWORD=${DB_POSTGRESDB_PASSWORD} 
      - PGDATA=/var/lib/postgresql/data/pgdata
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_POSTGRESDB_USER} -d ${DB_POSTGRESDB_DATABASE} || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s
    networks:
      - n8n-network
    mem_limit: 2g
    cpus: 1.0

  # -------------------------
  # n8n main (web UI + webhooks)
  # -------------------------
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n_main
    restart: unless-stopped
    depends_on:
      - postgres
    volumes:
      # named volume for user-related files, credentials, workflows, logs, etc
      - n8n_data:/home/node/.n8n
    environment:
      # Database (Postgres) - prefer file-based secret usage
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=${DB_POSTGRESDB_PORT}
      - DB_POSTGRESDB_DATABASE=${DB_POSTGRESDB_DATABASE}
      - DB_POSTGRESDB_USER=${DB_POSTGRESDB_USER}
      - DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD}

      # n8n app settings
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - N8N_REINSTALL_MISSING_PACKAGES=true
      - N8N_RUNNERS_ENABLED=true
      - WEBHOOK_URL=https://${N8N_HOSTNAME}             # actual public URL; override in .env
      - GENERIC_TIMEZONE=${TZ}                         # e.g., "UTC" or "Asia/Kolkata"

      # Basic auth for UI - use secret file variant
      - N8N_BASIC_AUTH_ACTIVE=${N8N_BASIC_AUTH_ACTIVE}
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}

    networks:
      - n8n-network
      - traefik-public
    ports:
    #   # bind to container local port only — Traefik will route external traffic
      - 127.0.0.1:5678:5678
    labels:
      - "traefik.enable=true"
    # Tell Traefik which network to use to connect to this service
      - "traefik.docker.network=n8nstack_traefik-public"

    # --- HTTPS Router ---
      - "traefik.http.routers.n8n.rule=Host(`${N8N_HOSTNAME}`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=letsencrypt"

      # Traefik headers middleware for better security

      - traefik.http.routers.n8n.tls=true
      - 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=${N8N_HOSTNAME}
      - traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
      - traefik.http.middlewares.n8n.headers.STSPreload=true
      - traefik.http.routers.n8n.middlewares=n8n@docker

    mem_limit: 4g
    cpus: 2

  # -------------------------
  # Traefik (reverse proxy / TLS automation)
  # -------------------------

  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    command:
    - --api.dashboard=true
    - --api.insecure=false  # Secure the dashboard
    - --providers.docker=true
    - --providers.docker.exposedbydefault=false
    - --providers.docker.network=n8nstack_traefik-public  # Specify network
    - --entrypoints.web.address=:80
    - --entrypoints.websecure.address=:443
    # Let's Encrypt configuration
    - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
    - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
    - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}
    - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    - --log.level=INFO  # Use INFO instead of DEBUG for production
    ports:
    - "80:80"
    - "443:443"
    - "8080:8080"
    volumes:
    #- traefik_data:/letsencrypt
    - /home/ubuntu/traefik/acme.json:/letsencrypt/acme.json
    - /home/ubuntu/traefik/log:/var/log/traefik
    - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
    - n8n-network
    - traefik-public

    mem_limit: 512m
    cpus: 0.5
# -------------------------
# Networks & Volumes
# -------------------------
networks:
  n8n-network:
    driver: bridge
  traefik-public:
    driver: bridge
volumes:
  pgdata:
    name: n8n_pgdata
  n8n_data:
    name: n8n_data

create .env file

# -------------------------
# General (non-sensitive)
# -------------------------
COMPOSE_PROJECT_NAME=n8nstack    #environment variable in Docker Compose used to define the project name for a set of Docker services
TZ=Asia/Kolkata                  # timezone for containers (set to your preferred timezone)
N8N_HOSTNAME=n8n.example.com   # <-- Replace with your public domain (used for WEBHOOK_URL & Traefik rule)
LETSENCRYPT_EMAIL=email@example.com

# -------------------------
# Postgres settings
# -------------------------
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8nuser
DB_POSTGRESDB_PASSWORD=dbsupersecret # in Production , do not put passwords in .env

# -------------------------
# n8n auth / behavior
# -------------------------
N8N_BASIC_AUTH_PASSWORD=n8nsupersecret  # in Production , do not put passwords in .env
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin

# Optional metrics/queue settings
N8N_METRICS=true
N8N_METRICS_INCLUDE_QUEUE_METRICS=true

# -------------------------
# Resource & tuning (example values)
# -------------------------
# For Postgres: max connections ≈ (typical) 100 (adjust in postgres.conf if needed)
DB_POOL_SIZE=20

Launch the docker compose config

docker-compose up -d

This will create the networks, volumes & the containers

Step 5: 🔍Verify

Verify if n8n has successfully initiated

docker compose logs n8n

#Verify if traefik ACME is able to successfully get a certificate
docker compose logs -f traefik

Now check the n8n portal using your browser

⚠️ Issues Faced & Fixes 🛠️

  1. Traefik only shows the default self‑signed certificate

Symptom: When you open https://n8n.example.com, your browser shows the Traefik default certificate instead of a valid Let’s Encrypt cert.

Causes & Fixes:

  • Resolver name mismatch: The resolver name in your labels must match the resolver defined in Traefik’s command args.

    • Traefik command:

        --certificatesresolvers.myresolver.acme.email=you@example.com
        --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
        --certificatesresolvers.myresolver.acme.tlschallenge=true
      
    • Label must match:

        - "traefik.http.routers.n8n.tls.certresolver=myresolver"
      
  • acme.json permissions: Ensure the file exists and is writable by Traefik:

      touch ./traefik/acme.json
      chmod 600 ./traefik/acme.json
    
  • Firewall/DNS: Ports 80/443 must be open, and n8n.example.com must resolve to your VPS IP.

  1. Traefik fails to obtain ACME certificate when N8N_EDITOR_BASE_URL is set

Symptom: Traefik logs show certificate request failures, and n8n only loads behind the default cert. The issue appears right after setting N8N_EDITOR_BASE_URL=https://n8n.example.com/.

Cause: With TLS‑ALPN challenge, Traefik passes the ACME validation request through to the backend. If n8n enforces HTTPS at this point, the validation breaks.

Fixes:

  • Option A: Deploy without N8N_EDITOR_BASE_URL until the cert is issued, then set it and restart n8n.

  • Option B (better): Switch to HTTP‑01 challenge, which bypasses n8n entirely during validation.

    --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

  • Option C: Use DNS‑01 challenge if your DNS provider supports it (best for Cloudflare/Route53).