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-publicnetwork 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-publicandn8n-network).

Step 1: 📝Create Your Oracle Cloud Account
Visit oracle.com/cloud/free
Sign up for a free account
Complete the verification process (requires credit card for verification, but won't be charged)
Wait for account activation
Step 2: 🖥️✨ Set Up Your Compute Instance
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

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

Create a Internet gateway for the VCN


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
Configure Network Security
Go to Networking → Virtual Cloud Networks
Click on your VCN
Click on Security Lists → Default Security List
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 🛠️
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=trueLabel 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.jsonFirewall/DNS: Ports 80/443 must be open, and
n8n.example.commust resolve to your VPS IP.
Traefik fails to obtain ACME certificate when
N8N_EDITOR_BASE_URLis 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_URLuntil 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).





