Self-hosting n8n on Ubuntu 24.04 gives you control over workflow data, credentials, updates, and infrastructure. A practical single-server deployment uses Docker Compose, PostgreSQL, an Nginx reverse proxy, and HTTPS, while keeping the n8n application port unavailable from the public internet.
In this tutorial, you will deploy n8n with PostgreSQL, configure the public webhook and editor URLs, place Nginx in front of the container, enable TLS, run n8n's security audit, control execution retention, and create a backup and update process.
Step 1 — Prepare Ubuntu, DNS, and Docker
Create a DNS A record such as n8n.example.com that points to your Raff Linux VM's public IPv4 address. Publish an AAAA record only when IPv6 reaches the same server and is protected correctly.
Update Ubuntu:
sudo apt update sudo apt upgrade -y
Install Docker Engine and the Docker Compose plugin by following How to Install Docker on Ubuntu 24.04.
Verify both commands work:
docker --version docker compose version
For a small personal instance, 2 GB RAM may be enough. When n8n and PostgreSQL share one VM, 4 GB RAM provides safer headroom for workflow concurrency, updates, and binary data.
Step 2 — Create the n8n Project Directory
Create a dedicated directory owned by your administrative user:
sudo mkdir -p /opt/n8n/backups sudo chown -R "$USER":"$USER" /opt/n8n cd /opt/n8n
The deployment will use named Docker volumes for PostgreSQL and n8n application data. Named volumes avoid host-directory ownership problems while keeping data persistent across container replacement.
Step 3 — Create the Environment File
Generate a database password and an n8n encryption key:
openssl rand -base64 36 openssl rand -hex 32
Store both values in a password manager. Create the environment file:
nano /opt/n8n/.env
Add the following values and replace every placeholder:
N8N_IMAGE=docker.n8n.io/n8nio/n8n:latest N8N_DOMAIN=n8n.example.com GENERIC_TIMEZONE=UTC POSTGRES_DB=n8n POSTGRES_USER=n8n POSTGRES_PASSWORD=replace_with_a_long_database_password N8N_ENCRYPTION_KEY=replace_with_the_generated_64_character_hex_value
Protect the file:
chmod 600 /opt/n8n/.env
N8N_ENCRYPTION_KEY protects credentials stored in the database. Losing it can make restored credentials unusable, so keep an off-server copy with the database backup.
The latest image tag is convenient for the initial deployment but should not be your long-term production update policy. After validating the installation, replace it with the exact n8n image version you have tested.
Step 4 — Create the Docker Compose File
Create the Compose configuration:
nano /opt/n8n/compose.yaml
Add:
name: n8n services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 10 n8n: image: ${N8N_IMAGE} 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_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY} N8N_HOST: ${N8N_DOMAIN} N8N_PORT: 5678 N8N_PROTOCOL: https N8N_EDITOR_BASE_URL: https://${N8N_DOMAIN}/ WEBHOOK_URL: https://${N8N_DOMAIN}/ N8N_PROXY_HOPS: 1 GENERIC_TIMEZONE: ${GENERIC_TIMEZONE} TZ: ${GENERIC_TIMEZONE} EXECUTIONS_DATA_PRUNE: "true" EXECUTIONS_DATA_MAX_AGE: 168 N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true" volumes: - n8n_data:/home/node/.n8n volumes: postgres_data: name: n8n_postgres_data n8n_data: name: n8n_data
Important details:
- PostgreSQL is the supported production database for current n8n deployments.
- Port
5678is bound to127.0.0.1, so it is reachable by Nginx on the VM but not directly from the internet. WEBHOOK_URLcontrols public callback URLs generated for webhook-based workflows.N8N_EDITOR_BASE_URLdefines the public editor URL.N8N_PROXY_HOPS=1tells n8n that one trusted reverse proxy sits in front of it.- Execution pruning prevents successful and failed execution records from growing without limit. Adjust the seven-day value to your operational and compliance requirements.
This single-instance configuration does not use Redis or queue-mode workers. Add those only when measured concurrency and availability requirements justify a multi-process architecture.
Step 5 — Start n8n and PostgreSQL
Validate the rendered Compose configuration:
cd /opt/n8n docker compose config
Start the services:
docker compose up -d
Check their state:
docker compose ps
Review startup logs:
docker compose logs --tail=100 postgres docker compose logs --tail=100 n8n
Test n8n locally:
curl -i http://127.0.0.1:5678/healthz
A successful health response confirms the container is accepting requests. Do not open port 5678 through UFW; Nginx will provide the public endpoint.
Step 6 — Configure Nginx as the Reverse Proxy
Install Nginx:
sudo apt install -y nginx
Create a server block:
sudo nano /etc/nginx/sites-available/n8n.example.com
Add:
server { listen 80; listen [::]:80; server_name n8n.example.com; client_max_body_size 50M; 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"; proxy_buffering off; proxy_read_timeout 300s; } }
Replace the example hostname with your real domain, then enable the site:
sudo ln -s /etc/nginx/sites-available/n8n.example.com \ /etc/nginx/sites-enabled/n8n.example.com sudo nginx -t sudo systemctl reload nginx
Test the HTTP endpoint:
curl -I http://n8n.example.com
If UFW is active, allow SSH and web traffic:
sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw status
Step 7 — Enable HTTPS
Install Certbot and its Nginx plugin:
sudo apt install -y certbot python3-certbot-nginx
Request the certificate and redirect HTTP to HTTPS:
sudo certbot --nginx --redirect -d n8n.example.com
Verify both the HTTPS response and renewal path:
curl -I https://n8n.example.com sudo certbot renew --dry-run
For DNS, firewall, renewal, and HSTS troubleshooting, use How to Secure Nginx with Let's Encrypt on Ubuntu 24.04.