How to Self-Host n8n with Docker, PostgreSQL & HTTPS on a Linux VPS

Author Avatar Digital Bhatti
• September 25, 2026 • Automation & Tools

Self-hosting n8n gives you control over the server, database, credentials, upgrades and network path—but it also makes you responsible for reliability, security and backups.

For a production-minded Linux VPS deployment, a practical stack is:

  • Docker Engine + Docker Compose.
  • Persistent n8n storage.
  • PostgreSQL for more serious workloads.
  • Nginx, Caddy or another reverse proxy.
  • HTTPS.
  • A dedicated subdomain such as n8n.example.com.
  • Backups and monitoring.
Research methodology

This guide is based on current n8n 2.x self-hosting documentation for Docker Compose, PostgreSQL, reverse-proxy webhook URLs, encryption keys, execution-data retention, scaling, CLI backup/export tooling, updates and monitoring. The deployment below intentionally focuses on the core n8n + PostgreSQL path rather than the larger Assistant/sandbox stack.

Last verified: September 26, 2026. n8n release behavior, Docker images, environment variables and deployment architecture can change; verify the current official documentation before upgrading or changing production configuration.

Version snapshot used in this guide

n8n: 2.40.6 is the current stable release verified on September 26, 2026. 2.41.2 is a pre-release and is not used in the production Compose example.

PostgreSQL: n8n currently supports majors 17 and 18 as the latest actively maintained versions plus 16 for compatibility, and recommends the latest minor within the chosen major.

Reverse proxy: N8N_WEBHOOK_URL replaces the deprecated WEBHOOK_URL variable from n8n 2.35.0 onward.

Production Rule

A Running Container Is Not Yet a Production Deployment

For a public production instance, plan persistent data, a stable encryption key, HTTPS, correct public webhook URLs, backups, controlled upgrades and monitoring before relying on the server for critical automations.


1. What You Need Before You Start

  • A Linux VPS you control.
  • Root or sudo access.
  • A domain or subdomain.
  • Docker Engine.
  • Docker Compose v2.
  • Enough RAM/CPU for your actual workflows and any optional components you deploy.

If you have not chosen infrastructure yet, use our Best Hosting for n8n guide. This article assumes the server already exists.

Do not copy a universal VPS size from a tutorial. n8n’s current full Docker Compose example includes an Assistant sandbox stack and documents higher minimum resources for that stack. A simpler n8n + database deployment has different requirements, so size the VPS from the features and workflows you actually run.


2. Update the VPS First

sudo apt update
sudo apt upgrade -y

Install only the packages you actually need and keep the operating system patched.


3. Install Docker and Docker Compose

Use Docker's current installation instructions for your Linux distribution, then verify:

docker --version
docker compose version

Avoid old tutorials that still assume the legacy standalone docker-compose binary.


4. Create the n8n Project Directory

mkdir -p ~/n8n
cd ~/n8n

5. Create a Dedicated Subdomain

A clean setup might use:

n8n.example.com

Point the hostname to the VPS using the appropriate DNS record.

For A/AAAA/CNAME and TTL troubleshooting, use our DNS Configuration Best Practices Guide.


6. Create an Environment File

Keep secrets out of your Compose file where practical.

nano .env

Example placeholders:

N8N_VERSION=2.40.6

N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
N8N_WEBHOOK_URL=https://n8n.example.com/
N8N_PROXY_HOPS=1
N8N_ENCRYPTION_KEY=replace-with-a-long-random-stable-secret

GENERIC_TIMEZONE=Asia/Karachi
TZ=Asia/Karachi

POSTGRES_DB=n8n
POSTGRES_USER=n8n
POSTGRES_PASSWORD=replace-with-a-strong-random-password

Do not commit this file to a public repository. Restrict access to the deployment directory and include the encryption key in protected recovery documentation; losing the key can make stored credentials unusable after a rebuild.


7. Use Persistent Storage

Never run production n8n as a disposable container with no persistent data.

The n8n container needs persistent storage for instance data and configuration.

PostgreSQL also needs a persistent database volume.


8. Docker Compose Example With PostgreSQL

services:
  postgres:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      PGDATA: /var/lib/postgresql/data
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -h localhost -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10

  n8n:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "127.0.0.1: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_HOST: ${N8N_HOST}
      N8N_PROTOCOL: ${N8N_PROTOCOL}
      N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL}
      N8N_PROXY_HOPS: ${N8N_PROXY_HOPS}

      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
      N8N_RUNNERS_ENABLED: "true"

      GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
      TZ: ${TZ}
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  postgres_data:
  n8n_data:

This is a production-oriented starting template, not a universal architecture. It pins the n8n application version, waits for PostgreSQL readiness, keeps port 5678 local to the host, persists both services and enables current n8n file-permission/task-runner settings used in the official Docker guidance.

PostgreSQL image note: postgres:18 pins the major version, not an exact patch release or image digest. That is useful for staying on the latest PostgreSQL 18 maintenance image when you deliberately pull, but stricter change-control environments may prefer an explicitly tested patch tag or digest and a documented upgrade process.

Do not remove PGDATA casually on PostgreSQL 18 n8n's current Compose documentation notes that PostgreSQL 18 changed its default data-directory behavior. Keeping PGDATA=/var/lib/postgresql/data makes the mounted path explicit and prevents a deployment from unexpectedly starting against an empty database path.

9. Why Use PostgreSQL?

Self-hosted n8n uses SQLite by default and also supports PostgreSQL. Current n8n documentation recommends PostgreSQL for production-oriented Compose deployments and currently supports PostgreSQL 17 and 18 as the latest actively maintained majors plus 16 for compatibility.

Use the latest minor release within the selected PostgreSQL major. n8n does not extend that support statement to every “PostgreSQL-compatible” database; its documentation specifically says compatible derivatives such as CockroachDB, YugabyteDB and AlloyDB are not supported substitutes, while Aurora PostgreSQL remains experimental.

If you start on SQLite and later switch databases, do not assume changing environment variables migrates existing data automatically; use the documented export/import or database-migration path and test it separately.


10. Start the Stack

docker compose config
docker compose up -d
docker compose ps

docker compose config renders the effective Compose configuration and catches many interpolation/formatting mistakes before startup. Do not publish or share its output if it expands secrets from .env.

Check logs if the n8n or Postgres service fails:

docker compose logs -f n8n
docker compose logs -f postgres

Once the stack is up, verify both basic reachability and database readiness:

curl -fsS http://127.0.0.1:5678/healthz
curl -fsS http://127.0.0.1:5678/healthz/readiness

n8n documents an important difference: /healthz proves the instance is reachable, while /healthz/readiness returns success only when the database is connected and migrated so the instance is ready to accept traffic.


11. Keep Port 5678 Private Behind the Reverse Proxy

The example above binds n8n to:

127.0.0.1:5678

That avoids exposing the backend directly when Nginx or another reverse proxy is the intended public entry point.


12. Reverse Proxy With Nginx

When n8n is behind a reverse proxy, configure the proxy and n8n’s public URL settings together. Use the current n8n reverse-proxy webhook guidance for the active release.

server {
    listen 80;
    server_name n8n.example.com;

    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Then add HTTPS using your preferred certificate automation method. Validate the Nginx configuration before reloading it:

sudo nginx -t

n8n's current reverse-proxy guidance requires the last proxy in the request path to pass X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto, and recommends N8N_PROXY_HOPS=1 when one reverse proxy sits in front of n8n.


13. Use N8N_WEBHOOK_URL Behind the Reverse Proxy

Behind a reverse proxy, n8n cannot reliably derive its public webhook address from the internal 5678 listener. Current n8n documentation therefore uses:

N8N_WEBHOOK_URL=https://n8n.example.com/
N8N_PROXY_HOPS=1

WEBHOOK_URL is deprecated from n8n 2.35.0. If an older deployment still uses it, migrate to N8N_WEBHOOK_URL and confirm the generated webhook URL in the editor after restart.

If webhook URLs still show localhost, port 5678 or the wrong scheme, verify N8N_WEBHOOK_URL, N8N_PROXY_HOPS and the forwarded proxy headers together.


14. Use HTTPS

n8n stores credentials and connects to third-party services.

Do not expose the editor publicly over plain HTTP.

Use HTTPS and keep the server, Docker, reverse proxy and n8n patched.


15. Run a Real Webhook Test Before Calling the Deployment Ready

A working editor does not prove that externally triggered workflows are correctly registered behind the reverse proxy.

Create a temporary workflow with:

  1. A Webhook node using GET and a unique path such as deployment-check.
  2. A Respond to Webhook step that returns a simple body such as {"ok":true}.
  3. The workflow published/activated only for the duration of the validation if it is not intended to remain.

Confirm that the production URL shown by n8n starts with your public HTTPS hostname, then call it from a machine outside the VPS:

curl -i https://n8n.example.com/webhook/deployment-check

Verify the expected HTTP status/body and then confirm an execution appears in n8n. This tests DNS, TLS, reverse proxying, the public webhook URL and n8n's execution path together.

Do not fabricate the evidence When Digital Bhatti adds first-hand proof to this guide, capture the real generated webhook URL, curl response and execution record from a controlled deployment, with secrets and tokens redacted.

16. Protect Secrets and Credentials

Do not put real:

  • Database passwords.
  • OAuth client secrets.
  • API keys.
  • n8n encryption keys.

inside public repositories, screenshots or tutorials.

For the VPS layer, continue with our Linux Cloud VPS Security Hardening Guide.


17. Set and Protect a Stable n8n Encryption Key

n8n creates an encryption key automatically on first launch and stores it in the n8n user folder. For controlled self-hosted production deployments, setting a stable N8N_ENCRYPTION_KEY explicitly makes recovery and multi-process consistency easier to reason about.

Do not casually replace or lose the key during a rebuild. Keep it in your secret-management or protected deployment configuration and include recovery planning for it alongside database and n8n-data backups.

Use n8n’s current encryption-key guidance for the active release.


18. Set the Correct Timezone

Scheduled workflows depend on time interpretation.

Set a timezone appropriate for your operation, for example:

GENERIC_TIMEZONE=Asia/Karachi
TZ=Asia/Karachi

GENERIC_TIMEZONE controls schedule-oriented n8n behavior, while TZ controls the container/system timezone used by commands and scripts. Use the values that match the intended operation rather than assuming the server's physical location.


19. Back Up Both n8n and PostgreSQL

A recoverable backup set covers:

  • The PostgreSQL database.
  • The n8n persistent data mounted at /home/node/.n8n.
  • The exact N8N_ENCRYPTION_KEY and other required secrets.
  • Compose and environment configuration.
  • Reverse-proxy/TLS configuration or reproducible infrastructure configuration.
  • The deployed n8n version/tag and PostgreSQL major version.

Do not assume copying one Docker volume automatically creates a consistent PostgreSQL backup while services are writing to it. Use a database-aware dump/snapshot process, back up /home/node/.n8n separately, and protect the encryption key with the same seriousness as the database.

n8n's CLI can also export workflows, credentials or broader database entities. Those exports are useful for migration/versioning, but the documentation explicitly distinguishes workflow/credential exports from a complete self-hosted instance backup.


20. PostgreSQL Backup Example

docker compose exec -T postgres \
  sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' \
  > n8n-postgres-backup.sql

The sh -c wrapper above expands POSTGRES_USER and POSTGRES_DB inside the PostgreSQL container, where the Compose environment variables exist. Test restoration on a non-production environment periodically.


21. Prove the Backup With a Disposable Restore Test

A backup is not proven until you can restore it.

Use a disposable VPS or isolated Compose project and perform this sequence:

  1. Install the same Docker/Compose baseline.
  2. Restore the protected .env values, especially the same N8N_ENCRYPTION_KEY.
  3. Restore the n8n data volume into the test deployment.
  4. Create an empty PostgreSQL database and restore the database dump with the matching PostgreSQL tooling.
  5. Start the same pinned n8n version used by production.
  6. Check /healthz/readiness.
  7. Open the editor and confirm workflows/credentials are present.
  8. Run a harmless workflow and the temporary webhook validation.
  9. Only after the restore works should you treat the backup process as operationally verified.

Do not practice destructive restore commands against the live production database. A recovery drill belongs in an isolated environment unless you are executing an actual incident runbook.


22. Update n8n Conservatively

A sensible update workflow is:

1. Read release notes + breaking changes
2. Record current N8N_VERSION
3. Back up PostgreSQL + n8n data + encryption key/config
4. Verify a recent restore test exists
5. Change N8N_VERSION to the intended stable version
6. docker compose pull n8n
7. docker compose up -d
8. Check logs + /healthz/readiness
9. Open editor
10. Test critical workflows
11. Test public webhooks
12. Keep the prior version + pre-upgrade backup available for rollback

The verified stable release at this update is 2.40.6. n8n publishes newer pre-releases separately; do not move production to a pre-release merely because its version number is higher.

Do not blindly auto-upgrade production instances without a tested recovery path. Also review breaking changes before downgrading: database migrations or feature changes can make a simple image rollback unsafe.


23. Monitor the n8n Instance Externally

Monitor at least:

  • The public HTTPS endpoint.
  • /healthz/readiness for database-ready application state.
  • Certificate expiry.
  • Critical webhook availability.

For a self-hosted monitoring stack, use our Uptime Kuma Monitoring Guide.

n8n documents /healthz, /healthz/readiness and an optional /metrics endpoint for self-hosted monitoring. /healthz does not check database status; readiness is the better baseline for “can this instance accept traffic?” monitoring. Metrics are disabled by default and should be enabled/protected intentionally.


24. Do Not Confuse Uptime With Workflow Health

An HTTP 200 response only proves that one endpoint responded.

Critical workflows can still fail because of:

  • Expired API credentials.
  • OAuth failures.
  • Database issues.
  • Rate limits.
  • Third-party outages.
  • Workflow logic errors.

Use execution monitoring and workflow-specific alerts as well.


25. Control Execution-Data Retention Before the Database Grows Unexpectedly

Execution history can become a significant database/storage cost on busy instances. Current n8n documentation enables execution pruning by default.

The documented defaults are:

  • EXECUTIONS_DATA_PRUNE=true.
  • EXECUTIONS_DATA_MAX_AGE=336 hours (14 days).
  • EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000.

Those defaults are not automatically the right retention policy for every business. Decide how much execution history you need for debugging, audit and compliance, then monitor database growth.

For example, a deliberate seven-day retention limit could use:

EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168

Do not copy that value without considering your own recovery/debugging requirements.


26. When Queue Mode Becomes Relevant

Queue mode is for workloads that need execution workers separated from the main n8n process.

Do not enable queue mode merely because a tutorial calls it “production-ready.”

It adds architectural complexity and should be justified by:

  • Higher execution concurrency.
  • Worker scaling needs.
  • Execution isolation requirements.

Follow the current official queue-mode documentation before implementing it.


27. Binary Data Can Change Storage Requirements

Workflows that handle large files can use much more disk and memory than simple API automation.

Examples include:

  • Images.
  • Video.
  • Documents.
  • Large CSV exports.

Capacity planning should reflect the actual workflow payloads and the configured execution-data storage mode. Large binary data can affect the n8n data volume, database, object storage or other configured backend depending on your deployment.


28. Resource Sizing: Avoid Universal Numbers

There is no single correct VPS size for every n8n deployment.

Resource usage depends on:

  • Concurrent executions.
  • Workflow duration.
  • Code nodes.
  • AI/model integrations.
  • Binary data.
  • Database size.
  • Execution retention.

Start conservatively, measure, and resize based on real resource usage.


29. Docker Compose Production Checklist

  • Docker Compose v2 installed.
  • Dedicated n8n hostname.
  • Persistent n8n volume.
  • Persistent Postgres volume.
  • Backend port bound locally.
  • HTTPS reverse proxy.
  • Pinned stable N8N_VERSION.
  • Correct N8N_WEBHOOK_URL and N8N_PROXY_HOPS.
  • Required forwarded proxy headers.
  • Strong database password.
  • Stable protected n8n encryption key.
  • Secrets excluded from Git.
  • Backups scheduled.
  • Database + n8n volume + encryption-key backup set documented.
  • Restore procedure tested on an isolated environment.
  • Public webhook test completed.
  • Execution retention/pruning policy reviewed.
  • External /healthz/readiness monitoring enabled where appropriate.
  • Explicit image/version update procedure documented.

30. Common n8n Self-Hosting Mistakes

  • Running without persistent volumes.
  • Publishing port 5678 directly when a reverse proxy is intended.
  • Using HTTP for public access.
  • Using deprecated WEBHOOK_URL instead of N8N_WEBHOOK_URL on current n8n.
  • Forgetting N8N_PROXY_HOPS or required forwarded headers.
  • Keeping secrets in a public repository.
  • Assuming every PostgreSQL-compatible database is supported.
  • Removing the PostgreSQL 18 PGDATA override while keeping the old volume path.
  • Losing or casually changing the encryption key used for stored credentials.
  • Upgrading an unpinned production image without a restore-tested backup.
  • Assuming a successful homepage check means workflows are healthy.
  • Copying an old Docker Compose file without checking current n8n docs.
  • Calling a backup “tested” when it has never been restored.
  • Letting execution history grow without reviewing pruning/retention.

31. Final n8n Self-Hosting Decision Framework

Need n8n quickly with no server operations?
   ↓
Use managed n8n Cloud

Need infrastructure control?
   ↓
Self-host on VPS / cloud

Production deployment?
   ↓
Pinned stable version + persistent storage + Postgres + HTTPS

Behind reverse proxy?
   ↓
N8N_WEBHOOK_URL + N8N_PROXY_HOPS + forwarded headers

Ready to rely on webhooks?
   ↓
Run external webhook validation

Critical automations?
   ↓
DB + n8n data + encryption-key backup + restore test
   ↓
Readiness monitoring + execution alerts + retention policy

A reliable n8n deployment is not just a running container. It is a maintained system with persistent data, correct public URLs, secure access, backups and monitoring.


Frequently Asked Questions

Can I self-host n8n with Docker?

Yes. n8n provides current documentation for Docker and Docker Compose self-hosting.

Should I use Docker Compose?

It is a practical choice when you want a reproducible multi-service deployment with n8n, PostgreSQL and persistent volumes.

Can I use SQLite?

Yes for light use and experimentation. Current n8n documentation recommends Postgres for more serious production instances.

Do I need HTTPS?

Use HTTPS for public production access because n8n handles credentials and third-party integrations.

Why are my webhook URLs wrong?

On current n8n, use N8N_WEBHOOK_URL rather than the deprecated WEBHOOK_URL, set N8N_PROXY_HOPS to match your proxy path, and forward X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto from the last proxy.

Should port 5678 be public?

Not necessarily. When a reverse proxy is the public entry point, binding n8n to localhost reduces unnecessary direct exposure.

How should I back up n8n?

Back up the database, n8n persistent data and deployment configuration, and test restoration.

When should I use queue mode?

When real execution concurrency or scaling requirements justify separate workers. It adds complexity and should not be enabled automatically.

What n8n version does this guide use?

This update is pinned to the stable 2.40.6 release verified on September 26, 2026. Check the current stable release and breaking changes before upgrading; do not assume a higher pre-release version is appropriate for production.

Which PostgreSQL versions does current n8n support?

At this verification date, n8n supports PostgreSQL 17 and 18 as the latest actively maintained majors plus 16 for compatibility, and recommends the latest minor release inside the selected major.

What is the difference between /healthz and /healthz/readiness?

/healthz proves that the n8n process is reachable. /healthz/readiness also verifies that the database is connected and migrated, so it is a better baseline for checking whether the instance is ready to accept traffic.

Is a PostgreSQL dump enough to back up n8n?

Not by itself. A complete recovery plan should also preserve the n8n data directory/volume, the exact encryption key, deployment configuration, and any filesystem-stored execution or binary data your configuration relies on.

How do I verify webhooks after deployment?

Create a harmless temporary Webhook + response workflow, publish it, confirm n8n generates the expected public HTTPS production URL, call that URL from outside the VPS, verify the response and confirm the execution appears in n8n.

Abdul Shakoor
Written by

Abdul Shakoor

Founder of Digital Bhatti, an independent technical publication focused on web hosting and infrastructure, WordPress, technical SEO, web performance and automation.

This guide is documentation-led. It does not claim an original n8n throughput benchmark, tested production capacity number, webhook trace or restore drill unless that evidence is explicitly published.