TL;DR: Self-hosting n8n gives you complete control, unlimited executions, and costs ~$12/month instead of $49+ on n8n Cloud. This guide walks you through Docker Compose installation with automatic SSL certificates via Traefik, optional PostgreSQL database integration, and production-ready configuration. Perfect for automation enthusiasts who want data ownership without the cloud subscription.

Last Tuesday morning, David decided to move his automation infrastructure off n8n Cloud. Not because he was unhappy with the service—he just couldn't resist the gravitational pull of complete control. "I want my workflows running on my own server," he announced to his coffee mug, already Googling "n8n self hosted setup."

Four hours later, after wrestling with Docker configs and SSL certificates, his n8n instance was humming along beautifully on a DigitalOcean droplet. Total monthly cost: $12. Previous n8n Cloud bill: $49. His satisfaction: immeasurable.

If you're considering the same journey, this guide will save you those four hours. You'll learn how to install n8n self-hosted using Docker Compose, configure SSL with automatic certificate renewal, connect a PostgreSQL database, and avoid the gotchas that David discovered the hard way.

What Does "Self-Hosted n8n" Actually Mean?

n8n is an open-source workflow automation tool that you can run anywhere: your laptop, a VPS, a Raspberry Pi in your closet, or enterprise Kubernetes infrastructure. "Self-hosted" simply means you're running the software on infrastructure you control, rather than using n8n Cloud.

If you're new to workflow automation, check out our n8n beginner's guide to understand the fundamentals before diving into self-hosting.

Self-hosted gives you:

  • Complete data ownership (workflows and credentials never leave your server)
  • No execution limits (run as many workflows as your hardware supports)
  • Custom integrations (add private APIs and internal tools)
  • Cost savings at scale (no per-execution fees)
  • Full configuration control (customize everything)

n8n Cloud is better if you want:

  • Zero maintenance (automatic updates, backups, scaling)
  • Instant setup (working instance in 60 seconds)
  • Enterprise support (SLA, dedicated help)
  • Team collaboration features out of the box

For this tutorial, we're going self-hosted. You'll need basic command-line comfort and a server to deploy to.

Prerequisites: What You'll Need

Before we start, gather these essentials:

1. A Linux Server

You need a server running Ubuntu 22.04 or similar. Options include:

  • DigitalOcean ($12/mo for 2GB RAM droplet)
  • Hetzner Cloud (€4.51/mo for 2GB RAM)
  • Linode ($12/mo for 2GB RAM)
  • AWS EC2 or Google Cloud Compute if you prefer the big clouds

Recommended specs: 2GB RAM minimum, 2 CPU cores, 20GB storage. n8n is lightweight, but your workflows might not be.

2. A Domain Name

You'll need a domain pointed at your server. If you don't have one:

Set up an A record pointing n8n.yourdomain.com to your server's IP address.

3. Docker and Docker Compose

We'll install these in Step 1, but verify your server supports them. Most modern Linux distributions do.

4. SSH Access

You need terminal access to your server. On Mac/Linux, use the built-in ssh command. On Windows, use PuTTY or Windows Terminal.

Ready? Let's build this.

Step 1: Install Docker and Docker Compose

SSH into your server:

ssh root@your-server-ip

Update your package list and install dependencies:

sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

Add Docker's official GPG key and repository:

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

echo "deb [arch=$(dpkg --print-architecture) 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

Install Docker Engine and Docker Compose:

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

Verify the installation:

docker --version
docker compose version

You should see version numbers for both. Current stable versions as of February 2026: Docker 25.x, Compose 2.24.x.

Optional: Run Docker Without sudo

To avoid typing sudo before every Docker command, add your user to the docker group:

sudo usermod -aG docker ${USER}
exec sg docker newgrp

Verify it worked:

docker ps

If you see a table (even empty), you're set.

Step 2: Configure DNS

Before proceeding, ensure your DNS A record is live. Test it with:

dig n8n.yourdomain.com

You should see your server's IP in the response. DNS changes can take 5 minutes to 48 hours to propagate, depending on your registrar. If it's not resolving yet, grab coffee and check back in 15 minutes.

Step 3: Create Your n8n Project Directory

Create a dedicated directory for your n8n setup:

mkdir ~/n8n-compose
cd ~/n8n-compose

Inside this directory, create an environment file to store configuration:

nano .env

Paste this configuration, replacing placeholders with your actual values:

# Domain configuration
DOMAIN_NAME=yourdomain.com
SUBDOMAIN=n8n

# Timezone (adjust to yours: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
GENERIC_TIMEZONE=America/New_York

# Email for Let's Encrypt SSL certificates
SSL_EMAIL=you@yourdomain.com

Save and exit (Ctrl+X, then Y, then Enter).

The above configuration will make n8n accessible at https://n8n.yourdomain.com. Adjust SUBDOMAIN if you prefer a different subdomain.

Step 4: Create the Local Files Directory

n8n's Read/Write Files from Disk node can access files from the host system. Create a shared directory:

mkdir ~/n8n-compose/local-files

This directory will be mounted inside the container at /files, giving your workflows a safe place to read and write data.

Step 5: Create the Docker Compose Configuration

Now for the main event. Create a compose.yaml file:

nano compose.yaml

Paste this complete Docker Compose configuration:

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: docker.n8n.io/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
      - N8N_RUNNERS_ENABLED=true
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - TZ=${GENERIC_TIMEZONE}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files

volumes:
  n8n_data:
  traefik_data:

Save and exit.

What's Happening Here?

This Docker Compose file creates two containers:

1. Traefik — A reverse proxy that handles HTTPS and automatic SSL certificate renewal via Let's Encrypt
2. n8n — Your workflow automation powerhouse

Traefik automatically detects n8n via Docker labels, requests an SSL certificate, and routes HTTPS traffic. You never touch certificate files manually.

For more details on Docker Compose configuration, check the official Docker Compose documentation.

Step 6: Launch n8n

Start everything with one command:

docker compose up -d

Docker will pull the images (this takes 1-2 minutes on first run) and start the containers. Watch the logs to confirm everything's working:

docker compose logs -f

Look for these happy messages:

  • Successfully created certificate (from Traefik)
  • Editor is now accessible via (from n8n)

Press Ctrl+C to exit the logs. Your containers keep running in the background.

Step 7: Access Your n8n Instance

Open your browser and navigate to:

https://n8n.yourdomain.com

If everything worked, you'll see the n8n setup wizard. Create your admin account and you're in.

If it doesn't work:

  • Check DNS: dig n8n.yourdomain.com should return your server IP
  • Check firewall: Ports 80 and 443 must be open
  • Check logs: docker compose logs traefik and docker compose logs n8n
  • Wait 2 minutes for Let's Encrypt certificate issuance

By default, n8n uses SQLite to store workflows and execution data. For production use, PostgreSQL is more robust.

Stop your containers:

docker compose down

Edit compose.yaml and add a PostgreSQL service. Here's the complete updated file:

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

  postgres:
    image: postgres:16
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=n8n_secure_password_change_me
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h localhost -U n8n -d n8n"]
      interval: 5s
      timeout: 5s
      retries: 10

  n8n:
    image: docker.n8n.io/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:
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=n8n_secure_password_change_me
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_RUNNERS_ENABLED=true
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - TZ=${GENERIC_TIMEZONE}
    volumes:
      - n8n_data:/home/node/.n8n
      - ./local-files:/files
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  n8n_data:
  traefik_data:
  postgres_data:

Change the password! Replace n8n_secure_password_change_me with a strong password in both the postgres and n8n service definitions.

Restart everything:

docker compose up -d

n8n will migrate your existing workflows from SQLite to PostgreSQL automatically.

Troubleshooting Common Issues

Certificate Generation Fails

Symptom: Traefik logs show unable to generate a certificate

Fix: Ensure ports 80 and 443 are open and your DNS A record is correct. Let's Encrypt needs to reach your server on port 80 for the HTTP challenge.

Can't Access n8n After Setup

Symptom: Browser shows "connection refused" or timeout

Fix: Check your firewall rules. On most cloud providers, you need to explicitly allow inbound traffic on ports 80 and 443 via their web console.

Workflows Aren't Executing

Symptom: Trigger nodes show no activity

Fix: Check the WEBHOOK_URL environment variable. It must match your actual domain exactly (including https://).

PostgreSQL Connection Errors

Symptom: n8n logs show ECONNREFUSED or database connection failed

Fix: Verify PostgreSQL is running (docker compose ps) and the credentials in your compose.yaml match between the postgres and n8n services.

Updating Your Self-Hosted n8n

n8n releases weekly. To update:

cd ~/n8n-compose
docker compose pull
docker compose down
docker compose up -d

Your data persists in Docker volumes, so updates are safe. Always check the n8n changelog for breaking changes before updating.

Frequently Asked Questions

What are the minimum server requirements for n8n?

For basic workflows, 1GB RAM works, but 2GB is recommended for production use. Most n8n instances run comfortably on a $6-12/month VPS. If you're running complex workflows with heavy API integrations or processing large files, consider 4GB RAM. CPU requirements are minimal—2 cores handle most workloads. Storage needs vary based on your execution history and file operations; 20GB is a safe starting point.

Can I migrate from n8n Cloud to self-hosted?

Yes! n8n Cloud provides workflow export functionality. Navigate to your workflows, select "Export," and download the JSON file. In your self-hosted instance, use "Import from File" to restore them. Credentials must be re-entered manually for security reasons. Execution history doesn't transfer, but active workflows will resume running once you activate them in your self-hosted environment.

How do I backup my self-hosted n8n instance?

For Docker-based setups, backup the named volumes: docker volume ls shows all volumes. Use docker run --rm -v n8n_data:/data -v $(pwd):/backup ubuntu tar czf /backup/n8n-backup.tar.gz /data to create a compressed backup. For PostgreSQL, use docker compose exec postgres pg_dump -U n8n n8n > backup.sql. Schedule these commands via cron for automatic backups. Store backups off-server (S3, Backblaze, etc.) for disaster recovery.

Does self-hosting support team collaboration?

Yes, but it requires configuration. n8n supports multi-user setups with role-based access control. Set N8N_USER_MANAGEMENT_DISABLED=false in your environment variables to enable user management. You can create additional users through the admin panel once enabled. For SSO integration (LDAP, SAML), check the n8n Docker Compose documentation for enterprise authentication options.

What happens if my server crashes?

Workflows stop executing until the server recovers. If you're using PostgreSQL (recommended), your workflow data and execution history are preserved. With restart: always in your Docker Compose file, containers automatically restart when the server reboots. For mission-critical workflows, consider setting up monitoring (UptimeRobot, Healthchecks.io) and automatic failover to a backup server. Workflow execution queues don't automatically retry after crashes—you'll need to manually re-trigger or implement retry logic within workflows.

Can I run n8n behind Cloudflare?

Yes, but disable Cloudflare's proxy (set DNS to "DNS only" mode) for webhook endpoints. Cloudflare's default proxy can timeout long-running workflows. If you need Cloudflare's security features, use it only for the main editor UI and create a separate subdomain (webhooks.yourdomain.com) for webhook traffic that bypasses Cloudflare. Alternatively, configure Cloudflare Page Rules to disable proxy specifically for /webhook/* paths.

What's Next?

You now have a production-ready n8n instance running on your own infrastructure. Here's what to explore:

1. Create your first workflow — Start with the n8n quickstart tutorials
2. Set up backups — Export your workflows regularly or backup the n8n_data volume
3. Monitor resource usage — Use docker stats to watch CPU and memory
4. Add authentication — Configure SSO if your team uses it
5. Explore integrations — n8n connects to 400+ services

Looking for workflow inspiration? Check out our guides on 10 n8n workflows every solopreneur needs and AI automation for beginners to see what's possible with your new self-hosted setup.

Self-hosting n8n means you're in control. No execution limits, no data leaving your infrastructure, and no surprise billing. The setup takes an hour, but the freedom lasts as long as you keep the server running.

David's n8n instance has been running for six months now, executing thousands of workflows daily without a single hiccup. His only regret? Not doing this sooner.

Last updated: February 15, 2026