...

How to Install n8n on a VPS: Docker Compose, Domain, SSL, and PostgreSQL

Martin Klein

Reading time 1 minute

It is better to deploy n8n on a VPS not as a single ad hoc container, but as a small self-hosted architecture: VPS, Docker Compose, n8n, PostgreSQL, a reverse proxy, a domain, SSL, persistent volumes, environment variables, access protection, and backups.

The minimal setup looks like this: User → domain → HTTPS → reverse proxy → n8n container → PostgreSQL

A small self-hosted n8n deployment typically requires:

  • A VPS with Docker and Docker Compose;
  • A domain or subdomain, for example n8n.example.com;
  • A reverse proxy: Nginx, Traefik, or Caddy;
  • An SSL certificate for HTTPS;
  • An n8n container;
  • PostgreSQL for operational data;
  • A volume for n8n data;
  • A volume for PostgreSQL;
  • Environment variables for the public URL, time zone, database, and security;
  • Restricted access to the editor;
  • Backups of the database and volumes;
  • A container update plan.

The main rule is not to expose n8n to the public internet without HTTPS and editor protection. n8n can store credentials, tokens, webhook URLs, workflows, and data from CRMs, spreadsheets, email, messengers, and other services.

For personal automation, n8n can serve as a host for small automation scenarios: notifications, Telegram bots, Google Sheets, email, webhooks, parsing, and reminders. Even in this setup, HTTPS, a volume, and a PostgreSQL backup are required.

For a team, stability and access control are more important. If several people create workflows, n8n becomes a working tool rather than an experiment on a VPS.

For a small business, n8n should be treated as part of the infrastructure. Requests, leads, notifications, payments, CRM events, documents, and internal processes may pass through it.

A basic Docker Compose installation usually includes two services:

  • n8n — the application itself;
  • postgres — the database for workflows, credentials, and settings.

The reverse proxy accepts HTTPS requests for the domain and forwards them to the n8n container. This service can also be run in a container and added to Compose, but there are more details to consider depending on the proxy you choose, so we will use a simpler option.

A proxy is required because without a correctly configured public URL, webhooks, OAuth callbacks, external integrations, and links within the interface may not work correctly.

After installation, you need to check:

  • Whether n8n opens over HTTPS;
  • Whether login to the editor works;
  • Whether workflows are saved after the container is restarted;
  • Whether webhooks receive external requests;
  • Whether n8n connects to PostgreSQL;
  • Whether the containers start after a reboot;
  • Whether a database backup is created;
  • Whether data can be restored from the backup.

The most common mistakes are running n8n without HTTPS, storing data in an ephemeral container, failing to back up PostgreSQL, exposing the editor to the entire internet, not restricting webhooks, forgetting about container updates, and not testing disaster recovery.

The right approach is to first design the minimal architecture, then start the Docker Compose stack, configure the domain and SSL, set environment variables, restrict access to the editor, test webhooks, configure backups, and only then use n8n for production automations.

n8n Architecture on a VPS

A self-hosted n8n deployment on a VPS is best built from the start as a small operational system, rather than as a single container just to try it out. Even if the automations are personal, n8n quickly accumulates credentials, webhook URLs, tokens, and data from CRMs, spreadsheets, email, and messaging platforms.

A minimal architecture is typically built around five components: the server, containers, the database, HTTPS access, and persistent data storage.

Domain → reverse proxy → n8n → PostgreSQL

VPS, Docker Compose, and a domain

A VPS is the foundation on which Docker, n8n, PostgreSQL, and the reverse proxy will run. For personal automation, a small server is usually enough, but it is important to leave headroom for memory and disk space: workflows, execution logs, the database, and Docker images grow over time.

Docker Compose is used to define the entire setup in a single file: the n8n service, the PostgreSQL service, volumes, the network, and environment variables. This is more convenient than starting containers manually with long commands.

A domain or subdomain is required for external integrations to work properly. For example: n8n.example.com

Without a domain and a stable HTTPS address, you may run into issues with webhooks, OAuth callbacks, external services, and the links that n8n generates within the interface.

At this stage, it is important to decide in advance whether n8n will be a personal tool, a team service, or part of the business infrastructure. This determines how strictly you need to restrict access, retain logs, create backups, and update containers.

After the server and domain, the next key component is the database. This is where n8n will store its operational state.

n8n and PostgreSQL

n8n is an application where workflows, credentials, webhooks, integrations, and automations are created. However, the n8n container itself should not be the only place where data is stored.

For a proper self-hosted installation, it is better to use a separate PostgreSQL database. It stores important n8n data: workflows, settings, executions, credential metadata, and other application state.

In Docker Compose, this usually means two separate services:

  • n8n
  • postgres

This setup is easier to maintain: the application can be updated separately, while the database remains in its own volume. If the n8n container is deleted and recreated, the data should not disappear.

PostgreSQL is especially important if n8n is used by a team or a business. Losing the database can mean losing workflows, execution history, settings, and part of the operational infrastructure.

However, the database alone does not make the installation publicly accessible. To make n8n work correctly on a domain, you need a reverse proxy and SSL.

Reverse proxy and SSL

A reverse proxy receives requests from the internet and forwards them to the n8n container. Nginx, Caddy, Traefik, or another proxy is typically used for this.

From the outside, the user opens: https://n8n.example.com

Inside the VPS, the reverse proxy forwards the request to the n8n container over an internal port.

SSL is mandatory. n8n cannot be used properly and securely over plain HTTP, especially when credentials, OAuth, cookies, webhook requests, and data from external services pass through it.

HTTPS is needed not only to protect traffic. It is also important for integrations: many services do not accept insecure callback URLs or webhook endpoints.

In this part of the architecture, you need to check three things:

  • the domain points to the VPS;
  • the reverse proxy forwards requests to n8n;
  • the SSL certificate has been issued and HTTPS works without errors.

Once public access is configured, it is important to make sure that data will not be lost after containers are restarted or updated.

Volumes, env, and backups

Volumes are required for persistent data storage. If data is stored only inside the container, it can be lost when the container is recreated, updated, or accidentally cleaned up in Docker.

At minimum, two persistent storage locations are required:

  • a volume for PostgreSQL;
  • a volume for n8n data.

The PostgreSQL volume stores the database. The n8n volume can store local files, settings, and data that n8n saves outside the database.

Environment variables define key installation parameters: the public URL, time zone, PostgreSQL connection, operating mode, webhook address, and security settings.

Examples of values that are typically put in env:

N8N_HOST=n8n.example.com

N8N_PROTOCOL=https

WEBHOOK_URL=https://n8n.example.com/

GENERIC_TIMEZONE=Europe/Berlin

The values depend on the specific deployment architecture, but the principle is the same: n8n must know the public address where it is available and how to generate webhook links.

Backups are required for more than just files. For n8n, backing up PostgreSQL is especially important because it contains the service’s primary state.

If the database is not backed up, a container update, administrator error, or volume corruption can result in the loss of workflows and settings.

With storage and backups covered, one final required layer remains: access to the n8n editor.

Authentication and editor security

The n8n editor must not be left open to the entire internet. It is not a regular public page, but a control panel for automations and integrations.

The following may be accessible through n8n:

  • API keys;
  • OAuth tokens;
  • credentials for external services;
  • webhook endpoints;
  • request-processing workflows;
  • integrations with CRM systems, email, spreadsheets, and messengers.

For this reason, access to the editor must be restricted. For personal use, n8n’s built-in authentication plus additional Basic Auth, access via a VPN, or IP-based restrictions at the reverse proxy level may be sufficient.

For a team, a sound user model, roles, access rules, and a clear account provisioning process are more important. For business use, it is also advisable to define who can modify workflows, who can view credentials, and who is responsible for updates.

Webhooks should be handled separately. They often need to be public; otherwise, external services cannot send events to n8n. But a public webhook is not the same as an exposed editor.

The correct setup is as follows: the editor is protected, HTTPS is enabled, credentials are not stored in arbitrary files, webhooks are constrained by the workflow logic, and only administrators have access to the server and Docker.

As a result, the n8n architecture on a VPS must cover not only starting the container, but also day-to-day operations: domain, SSL, PostgreSQL, volumes, env, authentication, webhooks, backups, and updates.

Use Cases

n8n can be deployed on a VPS for a wide range of tasks, from personal notifications to business workflows. The technical foundation may be similar—Docker Compose, PostgreSQL, a domain, and SSL—but the requirements for security, backups, and access will vary.

Before installation, it is therefore useful to understand what role n8n will play: a personal tool, a team environment, or part of the business infrastructure.

Personal Automation

For personal automation, n8n is typically used as a general-purpose script server. It can receive webhooks, send notifications, update spreadsheets, run Telegram bots, collect data from APIs, and connect different services.

Examples of personal scenarios:

  • Telegram notifications;
  • Automatically writing data to Google Sheets;
  • Reminders;
  • Processing webhook events;
  • Simple email integrations;
  • Collecting data from public APIs;
  • Home or personal work-related automations.

In this scenario, the workload is usually light, so the architecture can be simple: a single VPS, Docker Compose, n8n, PostgreSQL, a reverse proxy, and SSL.

However, even for personal use, n8n should not be treated as a disposable container. Credentials, tokens, workflows, and execution history will quickly accumulate in the instance. This is why you need volumes, HTTPS, restricted editor access, and PostgreSQL backups.

If n8n is used by only one person, access can be restricted more simply: a strong password, HTTPS, Basic Auth on the reverse proxy, IP restrictions, or a VPN. The main point is not to leave the editor exposed to the entire internet.

For a team, the requirements become stricter: there are multiple users, more workflows, and a higher risk of accidental changes.

Team

In a team setup, n8n becomes a shared automation environment. Multiple people can create workflows, connect services, modify integrations, and work with the same data.

At this point, it is important to think not only about installation, but also about operating rules.

You should define in advance:

  • Who has access to the editor;
  • Who can create and modify workflows;
  • Who is responsible for credentials;
  • Who updates the containers;
  • Who checks workflow errors;
  • Who monitors backups;
  • How changes to critical workflows are recorded.

For a team, a domain, HTTPS, and clear authorization are especially important. If n8n is accessible through a random IP address and open to everyone, it quickly becomes a risk: anyone who discovers the access point may be able to modify automations and interact with connected services.

It is also worth separating test and production workflows. If several people modify workflows at the same time, it is easy to accidentally break request processing, notifications, or a CRM integration.

In a team deployment, PostgreSQL backups can no longer be treated as a “nice-to-have.” Losing the database may mean losing the workflows, credentials, execution history, and settings used by the entire team.

If n8n starts affecting real requests, sales, or support operations, this is already the next level: a small business.

Small business

For small businesses, n8n can handle many day-to-day tasks: website inquiries, manager notifications, CRM integrations, form processing, task creation, spreadsheet synchronization, email sending, document handling, and data transfer between services.

At this level, n8n is no longer just a convenient tool; it becomes part of the operational infrastructure. If it goes down, leads, notifications, reports, orders, or internal tasks may stop coming through.

For this reason, the minimum setup for a business should include:

  • PostgreSQL with regular backups;
  • Persistent volumes;
  • HTTPS;
  • Restricted access to the editor;
  • A clear domain name;
  • Container monitoring;
  • An update plan;
  • Recovery testing;
  • Control over public webhooks.

Special attention should be paid to webhooks. Many workflows need to receive external requests: from a website, CRM, payment system, form, or another service. However, a public webhook must not become an unrestricted entry point.

For important processes, you should validate tokens, secrets, request signatures, the request method, the data source, and the processing logic. Otherwise, the webhook may start accepting junk or malicious requests.

Businesses also need to understand the responsibilities that come with the self-hosted model. If n8n is installed on your own VPS, updates, backups, security, monitoring, and disaster recovery remain the responsibility of the server owner.

Ultimately, the use case determines the depth of configuration required. For personal tasks, a simple but secure installation is enough. For a team, access and maintenance rules are needed. For a business, n8n should be deployed as a full-fledged service: with HTTPS, PostgreSQL, backups, access restrictions, updates, and recovery testing.

Installing with Docker Compose

Docker Compose is convenient because the entire n8n installation is described in a single project: the application container, PostgreSQL, volumes, network, and environment variables.

On a VPS, it is better not to run n8n with a single docker run command. This setup is harder to reproduce, harder to update, and makes it easier to accidentally leave the application without persistent data storage.

Server Preparation

Before installation, you need a VPS with an up-to-date system, Docker, Docker Compose, and a domain that points to the server’s IP address.

First, create a separate project directory:

mkdir -p /opt/n8n

cd /opt/n8n

This directory will contain:

  • docker-compose.yml;
  • .env;
  • volume data;
  • additional backup scripts, if they are needed later.

You can check Docker like this:

docker –version

docker compose version

If a firewall is enabled on the server, only ports 80 and 443 are usually opened externally for the reverse proxy. It is better not to publish the n8n port 5678 directly to the internet.

n8n should be accessed through a domain and HTTPS, for example: https://n8n.example.com

Once the server is ready, you can define the services in docker-compose.yml.

docker-compose.yml

A minimal docker-compose.yml for self-hosted n8n with PostgreSQL might look like this:

services:

postgres:

image: postgres:16

container_name: n8n-postgres

restart: unless-stopped

environment:

POSTGRES_USER: ${POSTGRES_USER}

POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

POSTGRES_DB: ${POSTGRES_DB}

volumes:

– postgres_data:/var/lib/postgresql/data

n8n:

image: n8nio/n8n:latest

container_name: n8n

restart: unless-stopped

depends_on:

– postgres

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: https

WEBHOOK_URL: ${WEBHOOK_URL}

GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}

TZ: ${GENERIC_TIMEZONE}

N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}

volumes:

– n8n_data:/home/node/.n8n

ports:

– "127.0.0.1:5678:5678"

volumes:

postgres_data:

n8n_data:

In this setup, PostgreSQL is available only within the Docker network, while n8n is bound to 127.0.0.1:5678. This means the port is not exposed directly to the internet, and external access must go through a reverse proxy.

The line:

ports:

– "127.0.0.1:5678:5678"

intentionally restricts the port to the local interface. This allows Nginx, Caddy, or Traefik on the same VPS to proxy requests to n8n, while a random user will not be able to open the editor at http://server-ip:5678.

After defining the Compose file, move sensitive values into .env.

Environment variables

The .env file must be located next to docker-compose.yml. It specifies the domain, database credentials, time zone, and encryption key.

Example:

POSTGRES_USER=n8n

POSTGRES_PASSWORD=change_this_strong_password

POSTGRES_DB=n8n

N8N_HOST=n8n.example.com

WEBHOOK_URL=https://n8n.example.com/

GENERIC_TIMEZONE=Europe/Berlin

N8N_ENCRYPTION_KEY=change_this_to_long_random_string

Replace the passwords and N8N_ENCRYPTION_KEY with your own values. The encryption key is especially important: it is used to protect sensitive n8n data. If you lose this key, you may have trouble accessing encrypted credentials after restoring from a backup.

You can generate a random string as follows: openssl rand -hex 32

In N8N_HOST, specify the domain without https://. In WEBHOOK_URL, specify the full public address that external services will use to access the webhooks.

If WEBHOOK_URL is not configured correctly, n8n may generate webhook links with the wrong domain, protocol, or local address.

After preparing the .env file, you can start the containers.

Starting the containers

Start them from the project directory:

cd /opt/n8n

docker compose up -d

Check the container status: docker compose ps

View the n8n logs: docker compose logs n8n –tail=100

View the PostgreSQL logs: docker compose logs postgres –tail=100

If the n8n container does not start, the most common causes are environment variables, the connection to PostgreSQL, an incorrect .env file, or an error in docker-compose.yml.

If PostgreSQL does not start, check the password, database name, volume, and container logs.

After the first startup, n8n should not be considered ready for use immediately. You need to verify access, HTTPS, data persistence, and webhooks.

Verifying n8n

At this stage, n8n should respond locally on the VPS: curl -I http://127.0.0.1:5678

If you get a response, the n8n container is running and listening on the local port. Access should then go through the reverse proxy and the domain: https://n8n.example.com

After configuring the proxy and SSL, check the following:

  • Whether the editor opens over HTTPS;
  • Whether the first user can be created;
  • Whether the workflow can be saved;
  • Whether data persists after the container is restarted;
  • Whether the PostgreSQL connection works;
  • Whether the webhook URL is generated correctly;
  • Whether n8n detects the external HTTPS address;
  • Whether there are any errors in docker compose logs n8n.

Restart to verify: docker compose restart

After the restart, the workflow and settings should remain in place. If the data has disappeared, there is an issue with the volumes, or n8n was started without persistent storage.

You should also check automatic startup after a VPS reboot: sudo reboot

After the server reboots, the containers should start automatically thanks to:

restart: unless-stopped

If n8n opens only locally but not via the domain, the problem is no longer with Docker Compose, but with DNS, the reverse proxy, SSL, or the firewall. Therefore, after starting the containers, the next step is to configure the domain, HTTPS, and request proxying.

Domain, SSL, and reverse proxy

After the containers are started, n8n should not be accessible directly by IP address and port 5678. A proper installation requires a domain, HTTPS, and a reverse proxy that accepts external requests and forwards them to the n8n container.

The working setup looks like this: https://n8n.example.com → Nginx → http://127.0.0.1:5678 → n8n

This keeps n8n behind the proxy, with only the standard HTTP/HTTPS ports exposed externally.

DNS record

First, you need to create a DNS record for the domain or subdomain where n8n will run.

For example: n8n.example.com  A  203.0.113.10

Here, 203.0.113.10 is the VPS IP address.

If IPv6 is used, be sure to check the AAAA record as well.

You can check where the domain points like this: dig A n8n.example.com +short

Or with nslookup: nslookup n8n.example.com

DNS must point to the exact VPS where Docker Compose, n8n, and the reverse proxy are running. If the domain still points to the old IP address or does not resolve, the SSL certificate will not be issued, and webhooks will not work from outside.

After configuring DNS, make sure ports 80 and 443 are open on the VPS. Port 80 is required for HTTP requests and certificate issuance, while port 443 is used for HTTPS.

If you use UFW:

sudo ufw allow 80/tcp

sudo ufw allow 443/tcp

sudo ufw status

Once the domain resolves and the ports are accessible, you can configure Nginx as a reverse proxy.

Nginx proxy

Nginx will receive requests for n8n.example.com and forward them to the n8n container, which listens on the local address 127.0.0.1:5678.

Example of a basic configuration:

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-Proto $scheme;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection "upgrade";

}

}

For example, you can place the file here: /etc/nginx/sites-available/n8n.example.com

Then enable the site:

sudo ln -s /etc/nginx/sites-available/n8n.example.com /etc/nginx/sites-enabled/

sudo nginx -t

sudo systemctl reload nginx

If docker-compose.yml publishes n8n only on 127.0.0.1:5678, Nginx on the same VPS will be able to reach n8n, while direct external access to port 5678 will be blocked.

This is a standard setup: the user accesses https://n8n.example.com, while the container’s internal port is not exposed to the outside.

After checking the proxy, you can issue an SSL certificate.

Let’s Encrypt

HTTPS is typically set up using Let’s Encrypt and Certbot. If Nginx is already configured and the domain points to the VPS, you can issue a certificate like this: sudo certbot –nginx -d n8n.example.com

Certbot will add the SSL configuration and can also configure a redirect from HTTP to HTTPS.

After issuing the certificate, check that the site loads at: https://n8n.example.com

It is also useful to check the redirect:

curl -I http://n8n.example.com

curl -I https://n8n.example.com

In a normal setup, HTTP should redirect to HTTPS, and HTTPS should return the n8n page without SSL errors.

If the certificate is not issued, the cause is most often one of the following:

  • DNS does not yet point to the VPS;
  • Port 80 is blocked by the firewall;
  • Nginx is not running;
  • server_name does not match the domain;
  • There is a CDN in front of the server with an incorrect SSL mode;
  • The domain is not being served from this VPS.

Once HTTPS is working, you need to check one more important setting: the public URL for webhooks.

Webhook URL

n8n needs to know its external address. It is used in webhook links, OAuth callbacks, and integrations that access n8n from outside.

In .env, specify the public HTTPS address:

N8N_HOST=n8n.example.com

N8N_PROTOCOL=https

WEBHOOK_URL=https://n8n.example.com/

If WEBHOOK_URL is set incorrectly, n8n may display webhook links with a local address, HTTP instead of HTTPS, the server IP address, or the wrong domain.

After changing .env, restart the containers:

cd /opt/n8n

docker compose up -d

It is best to test the webhook not only in the UI. Create a simple test workflow with a webhook trigger and send an external request: curl -X POST https://n8n.example.com/webhook/test

The exact path will depend on the URL that n8n provides for the specific workflow.

If the webhook does not trigger, check the following:

  • Whether the workflow is activated;
  • Whether the webhook URL is correct;
  • Whether HTTPS is working;
  • Whether the request reaches Nginx;
  • Whether Nginx forwards the request to n8n;
  • Whether there are any errors in docker compose logs n8n;
  • Whether a firewall or CDN is blocking the required request.

Ultimately, a domain, SSL, and a reverse proxy are needed for more than just a clean-looking address. Without them, n8n may work locally, but external webhooks, OAuth integrations, and secure access to the editor will be configured incorrectly.

Security and Maintenance

After starting n8n, it is important not to leave the installation in a “the container is running, so it is done” state. A self-hosted n8n instance quickly becomes part of your infrastructure: it stores credentials, receives webhooks, connects to external services, and can perform actions on behalf of a user or team.

For this reason, security and maintenance should be considered from the start. The minimum baseline includes HTTPS, restricted access to the editor, careful handling of webhooks, regular PostgreSQL backups, and container updates.

HTTPS is required

n8n should not be exposed to the public Internet over plain HTTP. Tokens, cookies, credentials, OAuth data, webhook requests, and service information can pass through the interface and workflows.

If traffic is transmitted without HTTPS, this data is less protected in transit. In addition, many external services do not accept insecure callback URLs or webhook endpoints.

The correct setup is: https://n8n.example.com → reverse proxy → http://127.0.0.1:5678

Externally, users and external services interact only over HTTPS. Inside the VPS, the reverse proxy forwards requests to the n8n container at the local address.

After issuing the SSL certificate, check the following:

  • n8n opens over HTTPS;
  • HTTP is redirected to HTTPS;
  • There are no certificate errors;
  • WEBHOOK_URL uses https://;
  • The OAuth callback is also generated with HTTPS.

Once n8n is running over HTTPS, the next layer of protection is the editor itself. It must not be left accessible to everyone.

Access to the Editor

The n8n editor is the control panel for workflows and integrations. It lets you modify workflows, connect services, run automations, and manage credentials.

Therefore, the editor must be protected from random visitors and bots. It must not be left simply accessible at: https://n8n.example.com without proper authorization and restrictions.

For a personal installation, you can use several layers of protection:

  • Built-in n8n authentication;
  • A strong password;
  • Basic Auth on the reverse proxy;
  • IP-based access restrictions;
  • A VPN;
  • A separate private subdomain.

For a team, it is important to decide in advance who can create workflows, who manages credentials, and who is responsible for changes to production workflows.

If n8n is used for business, access to the editor should be granted only to those who genuinely need it. The fewer people who can modify workflows and credentials, the lower the risk of accidental or dangerous changes.

The editor should be locked down, but webhooks often remain public. They need to be protected at the logic and routing level.

Webhooks

Webhooks allow external services to send events to n8n. For example, a website can submit an inquiry, a CRM can report a deal update, a payment system can report payment status, and a form can send new data.

A public webhook is fine. However, a public webhook should not be an unrestricted entry point.

For important workflows, you should validate:

  • The request method: GET, POST, or another method;
  • The presence of a secret token;
  • The request signature, if the service supports it;
  • The structure of incoming data;
  • The event source;
  • The request rate;
  • Handling of invalid payloads;
  • Protection against replaying the same event.

A simple approach is to include a secret in the URL or a header and validate it in the workflow. A more reliable approach is to use request signatures if the external service supports this type of verification.

You should also avoid making a webhook too generic. It is better to have several clear endpoints for specific tasks than one general-purpose webhook that accepts everything.

If a webhook starts receiving junk requests, you should review the logs, limit processing, add validation checks, and change the URL if necessary.

Webhooks handle incoming events, but the safety of workflows, credentials, and history depends on the database. Therefore, the next required item is PostgreSQL backups.

PostgreSQL Backups

PostgreSQL stores the core state of n8n: workflows, credentials metadata, settings, executions, and other operational data. If the database is lost, the n8n container alone will not restore the automations.

A minimal PostgreSQL backup can be created using pg_dump inside the container: docker exec n8n-postgres pg_dump -U n8n n8n > /opt/n8n/backups/n8n-db-$(date +%F).sql

If the database is large, it is better to compress the dump: docker exec n8n-postgres pg_dump -U n8n n8n | gzip > /opt/n8n/backups/n8n-db-$(date +%F).sql.gz

However, a local file on the same VPS is not enough. The copy should be sent to remote storage: object storage, a backup server, or another independent location.

You also need to save not only the database dump, but also important installation files:

  • docker-compose.yml;
  • .env;
  • the n8n volume, if it contains important data;
  • restore instructions;
  • N8N_ENCRYPTION_KEY.

The encryption key is especially important. If you restore the database but lose N8N_ENCRYPTION_KEY, you may run into problems decrypting credentials.

A backup should be considered functional only after a restore test. At minimum, periodically bring up a copy in a test environment and verify that workflows, credentials, and webhooks are available.

Once backups are configured, maintenance remains: containers need to be updated, but this must be done carefully.

Container Updates

n8n and PostgreSQL should not be left without updates indefinitely. New versions may include security and bug fixes, dependency changes, and improvements to workflow execution.

However, updating containers “blindly” is also risky. Before updating, back up PostgreSQL and record the current version.

You can view the current images with: docker compose images

An update usually looks like this: cd /opt/n8n

docker compose pull

docker compose up -d

After the update, check the following:

  • Whether the editor opens;
  • Whether the main workflows run;
  • Whether webhooks work;
  • Whether any errors have appeared in the logs;
  • Whether PostgreSQL connects;
  • Whether credentials have been preserved;
  • Whether OAuth works correctly;
  • Whether any important settings have changed.

n8n logs after the update: docker compose logs n8n –tail=100

For a production installation, it is better not to use completely unattended automatic updates. If a container updates itself and breaks an important workflow, the issue may only become apparent with live requests or integrations.

The best approach is to update regularly, but with a backup, a changelog review, testing of key workflows, and the option to roll back.

Ultimately, n8n security on a VPS comes down to a few simple rules: HTTPS is enabled, the editor is locked down, webhooks validate incoming requests, PostgreSQL is backed up regularly, and containers are updated through a clear process rather than chaotically.

Common Mistakes

n8n is often installed as a simple container for experimentation, then gradually starts being used for real tasks. This is where the risk arises: a temporary setup remains in production, and requests, tokens, webhooks, and data from external services begin passing through it.

Most problems are not caused by n8n itself, but by poor operational practices: there is no HTTPS, no volumes, no backups, the editor is open to everyone, webhooks accept any requests, and containers are left unupdated for months.

n8n without HTTPS

Running n8n without HTTPS is one of the most dangerous mistakes. Cookies, credentials, OAuth data, webhook payloads, API keys, and other sensitive information may pass through the interface and workflows.

A bad setup looks like this: http://n8n.example.com

Or like this: http://server-ip:5678

This option may be suitable only for local testing, not for a public server.

A production installation requires HTTPS: https://n8n.example.com

If n8n is behind a reverse proxy, only the HTTPS domain should be exposed externally, while the container itself can listen on a local port: https://n8n.example.com → Nginx → http://127.0.0.1:5678

Without HTTPS, OAuth integrations and external webhooks may also break because many services do not accept insecure callback URLs.

Data without a volume

Another common mistake is storing data inside the container without mounting a persistent volume. The container may appear to work until it is recreated, updated, or deleted.

The problem is that a container is not a reliable place to store state. It can be recreated with a single command, and the data inside it will disappear.

It is better to use volumes from the start:

volumes:

– n8n_data:/home/node/.n8n

And a separate volume for PostgreSQL:

volumes:

– postgres_data:/var/lib/postgresql/data

If n8n is used with PostgreSQL, the primary state is stored in the database. However, the n8n volume is still important for local data and for the deployment to work correctly.

After startup, check that workflows and settings do not disappear after a restart: docker compose restart

If n8n looks like a fresh installation after the restart, there is an issue with the volumes or the database is connected incorrectly.

PostgreSQL without backups

PostgreSQL can run reliably for months, but that does not remove the need for backups. If the database becomes corrupted, the volume is deleted, the VPS fails, or an administrator runs the wrong command, workflows and settings can be lost.

A bad approach is to treat a volume as a backup. A volume stores data, but it does not protect against deletion, corruption, update failures, or loss of the server.

A minimal dump can be created as follows: docker exec n8n-postgres pg_dump -U n8n n8n | gzip > /opt/n8n/backups/n8n-db-$(date +%F).sql.gz

However, this file must not be kept only on the same VPS. If the server is lost, the local backup will be lost along with it.

A proper setup:

  • Regular PostgreSQL dumps;
  • Storing a copy outside the VPS;
  • Saving docker-compose.yml and .env;
  • Storing N8N_ENCRYPTION_KEY separately;
  • Periodic restore testing.

A backup without restore testing is only a hope, not a guarantee.

The editor is publicly accessible

The n8n editor must not be left accessible to the entire internet without restrictions. It is not a public page, but a control panel for automations.

The editor allows users to modify workflows, connect services, view settings, and manage credentials. If an unauthorized person gains access to it, they can interfere with real processes.

Unsafe setup: https://n8n.example.com is open to anyone who knows the address

It is better to use several layers of protection:

  • n8n’s built-in authentication;
  • A strong password;
  • Basic Auth on the reverse proxy;
  • IP-based access restrictions;
  • VPN;
  • Separate user accounts for the team;
  • Minimum access, only for those who need it.

For a personal installation, a strong password and Basic Auth may be sufficient. For a team or business, it is better to define in advance who has access to the editor, who can modify workflows, and who is responsible for credentials.

Using a weak or reused password is especially dangerous. If n8n is connected to a CRM, email, spreadsheets, messaging apps, or payment services, a compromised editor can affect more than just the VPS itself.

Webhooks without restrictions

Webhooks often need to be public; otherwise, external services cannot send events to n8n. However, a public webhook should not accept arbitrary requests without validation.

A bad scenario is a webhook that accepts POST requests from the internet and immediately creates a request, sends an email, writes to a CRM, or triggers another critical workflow.

For production webhooks, you need to validate incoming data:

  • That a secret token is present;
  • That the request signature matches;
  • That the request method is correct;
  • That the payload structure is expected;
  • That the same event has not been sent more than once;
  • That the request rate limit has not been exceeded;
  • That invalid data can be handled safely.

A simple option is to add a secret to a header or parameter and validate it in the workflow. If the external service supports request signatures, it is better to use them.

You also should not use a single webhook “for everything.” It is better to separate endpoints by purpose: requests separately, payments separately, notifications separately, and technical events separately.

If a webhook starts receiving junk requests, you can change its URL and strengthen validation in the workflow. Critical actions should not be triggered simply because someone sent a request to a public endpoint.

No updates

n8n, PostgreSQL, Docker images, and the VPS itself all need to be updated. If you do not update them, the installation gradually becomes outdated: incompatibilities appear, old bugs remain, fixed vulnerabilities stay exposed, and dependency issues emerge.

However, you should not update containers without preparation either. Before updating, you need a database backup and a clear rollback plan.

Basic procedure:

cd /opt/n8n

docker compose pull

docker compose up -d

Before that, it is worth creating a PostgreSQL dump:

docker exec n8n-postgres pg_dump -U n8n n8n | gzip > /opt/n8n/backups/n8n-db-before-update-$(date +%F).sql.gz

After the update, check:

  • Whether the editor opens;
  • Whether key workflows run;
  • Whether webhooks work;
  • Whether any errors have appeared in the logs;
  • Whether PostgreSQL connects successfully;
  • Whether OAuth integrations are still working;
  • Whether credentials have been preserved.

You can view the logs as follows: docker compose logs n8n –tail=100

Uncontrolled automatic updates can be convenient for a test installation, but they are risky for production workflows. A container may update at the wrong time, and an issue may only surface in live requests or integrations.

Ultimately, the common mistakes come down to one thing: n8n must not be left in a “temporary” state once real processes are already running through it. HTTPS, volumes, PostgreSQL backups, a locked-down editor, protected webhooks, and regular updates are not nice-to-have additions; they are the baseline for operating self-hosted n8n.

Conclusion

n8n on a VPS is best deployed as a small operational system rather than a single temporary container. A minimal secure setup includes Docker Compose, n8n, PostgreSQL, a reverse proxy, a domain, HTTPS, volumes, environment variables, restricted access to the editor, and regular backups.

For personal automation, this setup provides control and flexibility. For a team, it provides a shared workflow environment. For a small business, it makes it possible to connect requests, CRM, forms, notifications, documents, and other services without developing a separate integration for each one.

The key point is not to leave a self-hosted n8n instance in a “test” state once real processes are already running through it. HTTPS, PostgreSQL backups, a secured editor, verified webhooks, and container updates should be part of basic operations.

FAQ

Can n8n be installed on a VPS without PostgreSQL?

Yes, but PostgreSQL is recommended for a production self-hosted installation. If you only need n8n for testing, a simpler setup may be acceptable. However, for regular workflows, a team, or a business, a separate database is more reliable and easier to back up.

PostgreSQL stores important n8n state, including workflows, settings, executions, and other data. For this reason, it should be configured through Docker Compose and backed up regularly.

Why does n8n need a domain?

A domain is needed not only for convenient access to the interface. It is also important for webhooks, OAuth callbacks, and external integrations.

For example, instead of a temporary address such as: http://server-ip:5678

It is better to use a proper HTTPS address: https://n8n.example.com

This allows external services to send requests to n8n reliably, while n8n itself can correctly generate public webhook URLs.

Can n8n be accessed directly by IP address and port 5678?

For testing on a local network, yes. For a production deployment, it is not recommended. Port 5678 should not be exposed directly to the internet.

A better approach is to keep n8n bound to the local address: 127.0.0.1:5678

Then provide external access through a reverse proxy and HTTPS: https://n8n.example.com → Nginx → 127.0.0.1:5678

This makes it easier to manage SSL, headers, access control, and additional restrictions.

Why shouldn’t you run n8n without HTTPS?

Credentials, OAuth tokens, cookies, webhook payloads, CRM data, email, spreadsheet data, and data from other services may pass through n8n. Without HTTPS, traffic is less secure in transit.

In addition, some external services do not accept insecure callback URLs or webhook endpoints. For this reason, HTTPS should be mandatory for any public n8n instance.

What must be backed up?

At a minimum, back up:

  • the PostgreSQL dump;
  • docker-compose.yml;
  • .env;
  • N8N_ENCRYPTION_KEY;
  • the n8n volume, if it contains important data;
  • the recovery instructions.

It is especially important not to lose N8N_ENCRYPTION_KEY. If you restore the database without it, you may have problems accessing encrypted credentials.

Do webhooks need to be secured?

Yes. A webhook can be public, but that does not mean it should accept every request without validation.

For important workflows, you should validate the secret token, request signature, method, payload structure, and repeated deliveries. Otherwise, the external endpoint can become a target for junk or malicious requests.

Sources

1. n8n Docs — Docker Compose installation

2. n8n Docs — Environment variables

3. n8n Docs — Securing n8n

4. PostgreSQL Documentation — pg_dump

Subscribe to our newsletter and receive articles and news

    Check out our other materials