--- title: "How to Self-Host n8n on Ubuntu 24.04 with Docker Compose" description: "Self-host the n8n workflow automation tool on Ubuntu 24.04 with Docker Compose and PostgreSQL, publish it over HTTPS with Nginx, and test a webhook workflow." category: "Web Applications Hosting" language: en source: https://cubepath.com/docs/web-applications-hosting/n8n-workflow-automation-installation updated: 2026-09-25 --- # How to Self-Host n8n on Ubuntu 24.04 with Docker Compose n8n is a workflow automation tool, similar to Zapier or Make, that connects APIs and services through a visual editor and can also run custom JavaScript or Python code. In this tutorial you will self-host n8n on Ubuntu 24.04 with Docker Compose, using PostgreSQL as the database, and publish it through Nginx with a Let's Encrypt certificate. At the end you will build a small webhook workflow and call it with `curl` to confirm everything works end to end. ## Prerequisites To follow this tutorial you need: - A server running Ubuntu 24.04 LTS, for example an [n8n VPS](/vps/vps-n8n) from CubePath, with at least 2 GB of RAM and 20 GB of free disk space. - A non-root user with `sudo` privileges. - Docker Engine and the Docker Compose plugin installed from Docker's official repository. `docker compose version` must print a v2 version. - A domain or subdomain (this guide uses `n8n.your_domain`) with a DNS `A` record pointing to `your_server_ip`. ## Step 1 - Creating the project directory and secrets Keep the whole deployment in one directory so it is easy to back up and move: ```bash sudo mkdir -p /opt/n8n sudo chown $USER:$USER /opt/n8n cd /opt/n8n ``` n8n encrypts the credentials you store (API keys, OAuth tokens) with an encryption key. If you lose that key, stored credentials become unreadable, so generate it yourself and keep it in an `.env` file together with the database password: ```bash cat > .env <5678/tcp n8n-postgres-1 postgres:16 postgres Up 31 seconds (healthy) 5432/tcp ``` Follow the n8n log until it reports that the editor is available, then press `Ctrl+C`: ```bash docker compose logs -f n8n ``` ```output n8n-1 | Editor is now accessible via: n8n-1 | https://n8n.your_domain ``` Finally, confirm that it answers locally: ```bash curl -s http://127.0.0.1:5678/healthz ``` ```output {"status":"ok"} ``` ## Step 4 - Configuring Nginx and HTTPS Install Nginx and Certbot and open the web ports: ```bash sudo apt install nginx certbot python3-certbot-nginx sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable ``` The n8n editor keeps a persistent WebSocket connection to receive execution updates, so the proxy must pass the `Upgrade` headers. Create the server block: ```bash sudo nano /etc/nginx/sites-available/n8n ``` ```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 80; listen [::]:80; server_name n8n.your_domain; client_max_body_size 50M; location / { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; 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_buffering off; proxy_read_timeout 3600s; } } ``` `proxy_buffering off` lets streamed responses reach the browser immediately, and the long read timeout keeps long-running webhook calls from being cut. Enable the site and reload Nginx: ```bash sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` Request the certificate. Certbot adds the TLS settings and an HTTP to HTTPS redirect to this server block: ```bash sudo certbot --nginx -d n8n.your_domain ``` Check the result from outside the proxy: ```bash curl -s https://n8n.your_domain/healthz ``` ```output {"status":"ok"} ``` Renewal is handled by the `certbot.timer` systemd timer; `sudo certbot renew --dry-run` confirms it works. ## Step 5 - Creating the owner account Open `https://n8n.your_domain`. On first launch n8n asks you to set up the **owner account**: enter your email, name and a strong password. This account manages users and instance settings. Signing up for the free community features that n8n offers afterwards is optional. You now see the workflow overview, which is empty. ## Step 6 - Building and testing a webhook workflow To verify that public webhooks work through the proxy, build a two-node workflow: 1. Click **Create Workflow**. 2. Click **Add first step**, search for **Webhook** and add it. Set **HTTP Method** to `POST`, set **Path** to `hello`, and set **Respond** to **Using 'Respond to Webhook' Node**. 3. Click the **+** on the right of the Webhook node, add **Respond to Webhook**, set **Respond With** to **JSON**, and enter this as the response body: ```json { "received": "{{ $json.body.name }}" } ``` 4. Save the workflow and switch it to **Active** with the toggle at the top. The Webhook node shows two URLs: a **Test URL** under `/webhook-test/` that only works while you click **Listen for test event** in the editor, and a **Production URL** under `/webhook/` that works while the workflow is active. Call the production URL: ```bash curl -s -X POST https://n8n.your_domain/webhook/hello \ -H 'Content-Type: application/json' \ -d '{"name":"CubePath"}' ``` ```output {"received":"CubePath"} ``` Open **Executions** in the workflow to see the run with the input and output of each node. From here you can replace the Respond node with real integrations: store credentials under **Credentials** and n8n encrypts them with your `N8N_ENCRYPTION_KEY`. ## Step 7 - Backing up n8n All workflows, credentials and execution history live in PostgreSQL. Dump the database into a dated, compressed file: ```bash sudo install -d -m 700 -o $USER /var/backups/n8n cd /opt/n8n docker compose exec -T postgres pg_dump -U n8n n8n | gzip > /var/backups/n8n/n8n-db-$(date +%F).sql.gz ls -lh /var/backups/n8n ``` Also keep a copy of `/opt/n8n/.env` and `/opt/n8n/compose.yaml`. A database dump restored with a different `N8N_ENCRYPTION_KEY` loses access to every stored credential. For a portable export of workflows as JSON files, which you can import into another n8n instance or keep in Git, use the n8n CLI inside the container: ```bash docker compose exec -u node n8n n8n export:workflow --all --output=/home/node/.n8n/workflows.json docker compose cp n8n:/home/node/.n8n/workflows.json /var/backups/n8n/workflows-$(date +%F).json ``` ## Step 8 - Updating n8n Before updating, take a database dump as in Step 7 and read the release notes for breaking changes. Then pull the new image and recreate the container: ```bash cd /opt/n8n docker compose pull docker compose up -d docker image prune -f ``` n8n runs its database migrations on startup. Watch `docker compose logs -f n8n` until the editor is available again. To stay on a specific release instead of the latest one, pin a version tag in `compose.yaml` (for example `docker.n8n.io/n8nio/n8n:`, using a version number from the n8n release notes). ## Troubleshooting - **Webhook URLs show `http://localhost:5678`**: `WEBHOOK_URL` is missing or wrong in `compose.yaml`. Fix it and run `docker compose up -d`. - **Editor shows "Connection lost"**: WebSockets are not reaching n8n. Check the `map` block and the `Upgrade`/`Connection` headers in the Nginx server block. - **n8n restarts with a "Mismatching encryption keys" error**: the key in `.env` differs from the one saved in the `n8n_data` volume. Restore the original key. - **Permission errors on `/home/node/.n8n`**: happens when a host directory is mounted instead of a named volume. The container runs as UID 1000, so the directory must be owned by `1000:1000`. ## Conclusion You now have n8n running on Ubuntu 24.04 with PostgreSQL, published over HTTPS through Nginx, with working production webhooks and a backup and update routine. As next steps, invite team members from **Settings > Users**, connect your first real integration such as Slack or Google Sheets, and schedule the backup commands with a systemd timer or cron.