🛠️ Tutorials · ⏱ 9 min read

Deploy n8n with Docker and HTTPS in 2026: Complete Tutorial (Postgres, Reverse Proxy, Persistence)

Step-by-step 2026 guide to self-host n8n, the no-code automation platform, using Docker Compose. Features PostgreSQL, workflow persistence, automatic HTTPS via Caddy and Let's Encrypt, credential encryption, backups, and hardening. Ready-to-use configs.

S By Selfhostr Team · independent tests
Deploy n8n with Docker and HTTPS in 2026: Complete Tutorial (Postgres, Reverse Proxy, Persistence)
ⓘ This article may contain affiliate links (no extra cost to you, it supports our tests). See the disclosure.
💾
2 GB
Min RAM
🐘
PostgreSQL
Database
🌪️
Caddy
Reverse Proxy
💸
Server only
Cost Model

👍 What we like

  • Self-hosted with no execution limits or operation costs
  • Data stays private and never passes through third-party clouds
  • Automatic HTTPS and Let's Encrypt via Caddy reverse proxy
  • Connects hundreds of services including APIs, databases, and AI

👎 What to watch

  • Requires manual configuration of DNS, Docker, and encryption keys
  • Sensitive API credentials demand strict security and backup practices
  • PostgreSQL is mandatory for production, not just SQLite
  • Encryption key loss makes all stored credentials unreadable
▶ Video tutorial: deploy n8n
📑 Contents

You’re chaining repetitive tasks across multiple apps and paying Zapier or Make based on operation volume? n8n changes the game. It’s an open-source, self-hostable workflow automation platform that connects hundreds of services (APIs, databases, webhooks, AI) in visual scenarios. When self-hosted, you have no execution limits, your data never passes through a third-party cloud, and the cost is reduced to just your server.

However, n8n handles your most sensitive API credentials (Stripe keys, Google tokens, database access…). It must therefore be deployed with mandatory HTTPS, a reliable PostgreSQL database instead of SQLite, a persistent encryption key, and serious backups. In this tutorial, we deploy n8n via Docker Compose with PostgreSQL, behind a Caddy reverse proxy that handles HTTPS and Let’s Encrypt automatically.

Prerequisites

  • A secure VPS running Ubuntu 24.04 (or Debian 12), with 2 GB of RAM sufficient to start. If you haven’t already, follow our guide to install and secure an Ubuntu VPS. A small VPS from Hetzner or Infomaniak is more than enough.
  • Docker and Docker Compose installed (command in step 1).
  • A domain name for which you control the DNS. We will use n8n.exemple.fr. A domain is essential: many third-party services refuse to register webhooks pointing to a bare IP address.
  • Ports 80 and 443 open for Let’s Encrypt and HTTPS traffic.

Step 1: Install Docker and Docker Compose

sudo apt update
sudo apt install -y 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
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" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

Log out and log back in, then verify:

docker --version && docker compose version

Step 2: Configure DNS

At your DNS provider, create an A record (and AAAA if IPv6):

TypeNameValue
An8n.exemple.fr203.0.113.10

Verify propagation:

dig +short n8n.exemple.fr

Step 3: Create the shared Docker network

docker network create web

If you are already following our Caddy reverse proxy tutorial, this network might already exist: ignore any error message.

Step 4: Prepare the folder and .env file

n8n strictly requires a persistent encryption key: this is what encrypts your stored credentials. If it changes, all your saved credentials become unreadable. We generate it once and freeze it in .env.

mkdir -p ~/n8n && cd ~/n8n
nano .env
# --- Domain ---
N8N_HOST=n8n.exemple.fr
N8N_PROTOCOL=https
# Full public URL for webhooks (no trailing slash)
WEBHOOK_URL=https://n8n.exemple.fr/

# --- Encryption Key (NEVER change it after the first startup) ---
N8N_ENCRYPTION_KEY=replace_with_generated_key

# --- PostgreSQL Database ---
POSTGRES_USER=n8n
POSTGRES_PASSWORD=replace_with_a_long_password
POSTGRES_DB=n8n

# --- Miscellaneous ---
GENERIC_TIMEZONE=Europe/Paris
TZ=Europe/Paris

Generate the encryption key and a database password:

echo "N8N_ENCRYPTION_KEY : $(openssl rand -hex 24)"
echo "POSTGRES_PASSWORD : $(openssl rand -base64 32)"

Record these values in .env. Note the encryption key in your password manager: you will need it to restore a backup on another server.

Step 5: The n8n docker-compose.yml

nano docker-compose.yml
services:
  postgres:
    image: postgres:16-alpine
    container_name: n8n_postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - n8n_pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - n8n-internal

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    environment:
      # --- PostgreSQL Connection ---
      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}
      # --- Domain and Webhooks ---
      N8N_HOST: ${N8N_HOST}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      N8N_PORT: 5678
      WEBHOOK_URL: ${WEBHOOK_URL}
      N8N_EDITOR_BASE_URL: ${WEBHOOK_URL}
      # --- Security ---
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_SECURE_COOKIE: "true"
      # Essential behind a reverse proxy: trusts the X-Forwarded header
      N8N_PROXY_HOPS: 1
      # --- Miscellaneous ---
      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      TZ: ${TZ}
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - web
      - n8n-internal

volumes:
  n8n_pgdata:
  n8n_data:

networks:
  web:
    external: true
  n8n-internal:

The n8n service is on two networks: n8n-internal to reach PostgreSQL, and web to be reached by Caddy. PostgreSQL is only on the internal network. No ports: section: n8n is never exposed directly. The N8N_PROXY_HOPS: 1 parameter is crucial behind a reverse proxy; otherwise, n8n complains about the X-Forwarded-For header.

Step 6: The Caddyfile for automatic HTTPS

If you already have a Caddy setup, add the site block. Otherwise, create a Caddy folder:

mkdir -p ~/caddy && cd ~/caddy
nano Caddyfile
{
    email admin@exemple.fr
}

n8n.exemple.fr {
    encode gzip zstd

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        -Server
    }

    reverse_proxy n8n:5678
}

The internal port of n8n is 5678. Caddy natively handles the WebSockets used by the workflow editor.

Then the Caddy docker-compose.yml:

nano docker-compose.yml
services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - web

volumes:
  caddy_data:
  caddy_config:

networks:
  web:
    external: true

Step 7: Start the services

cd ~/n8n && docker compose up -d
cd ~/caddy && docker compose up -d

Follow the logs to verify the database connection and certificate issuance:

cd ~/n8n && docker compose logs -f n8n

When n8n displays Editor is now accessible via ..., visit https://n8n.exemple.fr. The owner account creation screen will appear.

Step 8: Create the owner account and first workflow

  1. Enter an email and a strong password: this account is the owner of the instance.
  2. Create a simple first workflow to validate everything, for example, a “Manual” trigger followed by an “HTTP Request” node pointing to a public API, and execute it.
  3. Test a webhook: create a workflow with a “Webhook” node, activate it, and call the provided URL (it correctly uses https://n8n.exemple.fr/... thanks to WEBHOOK_URL). This is the decisive test: if the webhook responds over HTTPS, your deployment is correct.

Then add your credentials (API keys for the services to automate): they are encrypted with your N8N_ENCRYPTION_KEY before being stored in the database.

Step 9: Backups

All the useful state of n8n lives in PostgreSQL (workflows, encrypted credentials, executions). The encryption key, however, is in .env and the n8n_data volume. Back up both.

nano ~/n8n/backup.sh
#!/bin/bash
set -euo pipefail
cd "$HOME/n8n"
BACKUP_DIR="$HOME/n8n/backups"
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"

# Consistent dump of the PostgreSQL database
docker compose exec -T postgres \
  pg_dump -U n8n -d n8n | gzip > "$BACKUP_DIR/n8n-db-$STAMP.sql.gz"

# Backup of the .env file (contains the encryption key — keep it secure!)
cp .env "$BACKUP_DIR/.env-$STAMP"

# Keep 14 days
find "$BACKUP_DIR" -name 'n8n-db-*.sql.gz' -mtime +14 -delete
find "$BACKUP_DIR" -name '.env-*' -mtime +14 -delete
chmod +x ~/n8n/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * $HOME/n8n/backup.sh") | crontab -

A database backup without the encryption key is unusable: you could restore the workflows but not decrypt the credentials. Send these backups off-site and encrypted using our guide automatic backup with Restic and Backblaze.

Step 10: Final verification

# Containers are running and the database is healthy
docker compose -f ~/n8n/docker-compose.yml ps

# HTTP redirects to HTTPS (code 308)
curl -sI http://n8n.exemple.fr | head -1

# HTTPS responds
curl -sI https://n8n.exemple.fr | head -1

Security and Hardening

  • Strict UFW firewall. Open only ports 80, 443, and SSH. The n8n port 5678 and PostgreSQL port 5432 must never be exposed — the network configuration handles this already.
  • Strong owner password and, if you manage a team, enable two-factor authentication and separate user accounts.
  • Protected encryption key. The .env file contains secrets: chmod 600 .env and never commit it to version control.
  • Controlled updates. n8n releases frequently. Back up first, then docker compose pull && docker compose up -d. Watch release notes for breaking changes.
  • Public webhooks. Any workflow exposing a webhook is publicly accessible: secure them with authentication (header, token) in the Webhook node.

To harden the VPS, follow install and secure an Ubuntu VPS.

Common Pitfalls and Troubleshooting

  • “Your encryption key has changed”. You modified or lost N8N_ENCRYPTION_KEY. Restore the original key from your backup, otherwise the encrypted credentials are unrecoverable.
  • Proxy error / insecure cookie. N8N_PROXY_HOPS or N8N_SECURE_COOKIE is missing. Check these variables and ensure N8N_PROTOCOL is set to https.
  • Webhooks point to localhost or the IP. WEBHOOK_URL is not correctly defined. It must be the full public HTTPS URL.
  • n8n cannot connect to the database. Check PostgreSQL credentials and ensure the postgres service is healthy (docker compose ps). The depends_on … condition: service_healthy prevents premature startups.

FAQ

Is self-hosted n8n really free?

The Community version of n8n is free and has no execution limits: you only pay for your server. Some advanced features (enterprise SSO, environments, certain collaboration options) belong to a paid Enterprise offer, but for automating personal workflows or those of a small team, the Community version covers the vast majority of needs.

Why use PostgreSQL instead of the default SQLite database?

SQLite works for testing, but quickly shows its limits with concurrent executions and large volumes of history. PostgreSQL handles load, hot backups, and data growth much better. For an instance you plan to use seriously, PostgreSQL is the right choice from the start.

What happens if I lose my encryption key?

Your workflows remain recoverable, but all saved credentials become unreadable: you will have to re-enter them one by one. This is why the key must be noted in your password manager and backed up with the database. Never regenerate it on an existing instance.

Why don’t my webhooks work with external services?

Most often, it’s because WEBHOOK_URL does not reflect the public HTTPS URL, or the domain is not reachable from the Internet. Many services (Stripe, GitHub…) refuse to register a webhook pointing to a bare IP or over HTTP. The HTTPS reverse proxy in this tutorial solves this problem.

Can I run AI in my n8n workflows?

Yes. n8n includes nodes for LLMs (OpenAI, local models via Ollama, AI agents). When self-hosted, you can even connect n8n to a local model to keep all data on-premise. However, be mindful of RAM: AI workflows and parallel executions consume more resources.

n8n, Activepieces, or Node-RED: which one to choose?

n8n offers the best balance between power, number of integrations, and ease of use; Activepieces aims for simplicity; Node-RED is king for home automation and IoT. For automating web applications and APIs, n8n is generally the most versatile. Our comparison n8n vs Activepieces vs Node-RED details each profile.

You now have an n8n instance over HTTPS, backed by PostgreSQL, with persisted and backed-up encrypted workflows and credentials — with no execution limits. It’s the tool that automates your entire homelab and daily tasks. To follow n8n updates, AI nodes, and best practices for self-hosting, subscribe to our Telegram watch bot.

Tags: n8nDockerPostgreSQLCaddyLet's Encryptautomationself-hostingreverse proxy

Related

🛠️ Tutorials

Deploy Immich with Docker in 2026: Complete Guide (HTTPS, Reverse Proxy, Backups)

Step-by-step 2026 tutorial to self-host Immich, the Google Photos alternative, using Docker Compose. Features include photo/video gallery, facial recognition, automatic HTTPS via Caddy and Let's Encrypt, backups, and hardening. Ready-to-use configs included.

Read
🛠️ Tutorials

Deploy Jellyfin with Docker in 2026: Complete Guide (Hardware Transcoding, HTTPS, Reverse Proxy)

Step-by-step 2026 tutorial to self-host Jellyfin, the open-source media server, using Docker Compose. Covers film/series libraries, Intel/NVIDIA hardware transcoding, automatic HTTPS via Caddy and Let's Encrypt, remote access, and hardening. Ready-to-use configs included.

Read
🛠️ Tutorials

Automated HTTPS Reverse Proxy with Caddy and Docker in 2026 (Effortless Let's Encrypt Certs)

2026 tutorial on deploying a Caddy reverse proxy with Docker: automatic HTTPS and Let's Encrypt certificates, multi-service routing, security headers, and seamless renewal. Ready-to-use Caddyfile and docker-compose configs.

Read