Sep 1, 20261,403 words7 min read
Self-hosting n8n on Coolify
How to deploy n8n on Coolify with PostgreSQL, put it behind your own domain or a Cloudflare Tunnel, set the environment variables that make webhooks work and keep it healthy over time.

Introduction
n8n is a workflow automation tool: you connect triggers (a webhook, a cron schedule, a new row in a database) to actions (send a message, call an API, run some JavaScript) by wiring nodes together in a visual editor. Think Zapier or Make, except you can run it on your own server.
Self-hosting it makes sense for a few reasons. The hosted plans charge per execution, while the self-hosted version has no such limit and is free for your own use under its fair-code license. Your data and credentials stay on your machine. And it's a single Docker container with a database next to it, which is exactly the kind of thing Coolify is good at.
In this article I will guide you through deploying n8n on Coolify with a PostgreSQL database, putting it behind your own domain, getting the environment variables right so webhooks work, and taking a first workflow for a spin. It builds on my post on setting up Coolify, and if you followed the one on Cloudflare Tunnels, there is a section for you too.
For this guide, I'll be assuming you already have a Coolify instance running on a Linux server and a domain, or a subdomain, you can point at it.
What you need
- A server with Coolify installed. n8n itself is light, but keep in mind that every workflow execution is stored in the database, so give it some room:
2 GBof RAM is comfortable for personal use. - A domain for the editor, for example
n8n.example.com. Webhooks are the main reason to have n8n online at all, and third-party services will only call a public HTTPS URL. - Ten minutes.
Creating the service
Coolify ships n8n as a one-click service, so there is no compose file to write. Go to Projects, open your project and environment, click + New and type n8n in the search box. You will see two flavors: plain n8n, which stores everything in a SQLite file, and n8n with PostgreSQL. Pick the PostgreSQL one.
SQLite works, but n8n writes a row for every execution and its data, and that file grows quickly and gets slow to vacuum. PostgreSQL handles it without complaint, and it's the database n8n recommends for anything beyond a quick test. Coolify creates both containers for you, wires them into the same private network and generates the database password.
Once the service is created you land on its page. The compose file that defines it is right there under the editor, and you can tweak it later; for now, take a look at the two things that matter:
- The volume mounted at
/home/node/.n8n. That's where n8n keeps its config, and the encryption key that protects every credential you save. It appears in the Persistent Storage tab, and it must survive redeploys. - The magic variables such as
SERVICE_FQDN_N8N. Coolify fills these in from the domain you set on the service, and the template uses them to tell n8n its own public URL.
Setting the domain
In the service page, find the n8n container and set its domain. How you write it depends on how your server is exposed.
With a public server
If your server has a public IP and Coolify handles TLS, as in my first post, set the domain to https://n8n.example.com and create the matching DNS record if it doesn't fall under your wildcard already. Traefik requests the Let's Encrypt certificate on the first deploy:
n8n.example.com A YOUR_SERVER_IPBehind a Cloudflare Tunnel
If your apps go through a tunnel, the rule from the previous post applies: set the domain in Coolify to http://n8n.example.com (plain http, TLS ends at Cloudflare) and add a public hostname in Zero Trust pointing at localhost:80, or coolify-proxy:80 if your connector runs as a container.
Configuring the environment
Open the Environment Variables tab of the service. The template already sets the essentials, but there are a handful of variables worth adding or changing before the first deploy:
# Public URL n8n advertises (needed behind a tunnel, harmless otherwise)
N8N_PROTOCOL=https
N8N_EDITOR_BASE_URL=https://n8n.example.com/
WEBHOOK_URL=https://n8n.example.com/
# Trust the proxy in front of n8n (Traefik, plus Cloudflare if you use it)
N8N_PROXY_HOPS=1
# Your timezone, for schedules and date nodes
GENERIC_TIMEZONE=America/Argentina/Buenos_Aires
TZ=America/Argentina/Buenos_Aires
# Key used to encrypt stored credentials. Generate it once, keep it forever.
N8N_ENCRYPTION_KEY=<A_LONG_RANDOM_STRING>
# Keep the executions table from growing without limit (hours)
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
# Run Code nodes in a separate process, as recommended by n8n
N8N_RUNNERS_ENABLED=trueYou can generate the encryption key on any machine with openssl rand -hex 32. Which brings us to the one thing you really don't want to lose:
Deploying
Hit Deploy and watch the logs. Coolify pulls the two images, starts PostgreSQL first and then n8n, which runs its database migrations on the first boot. When the container reports healthy, open your domain.
The first screen asks you to create the owner account: email, name and a password. There's no separate sign-up after this, so do it right away. Additional users can be invited later from Settings -> Users.
Testing it out
A webhook is the quickest way to prove that the whole chain works, from the internet to the container and back. Create a new workflow, add a Webhook trigger, leave the method on GET and copy the test URL it shows. Click Listen for test event and call it from your computer:
curl -i https://n8n.example.com/webhook-test/YOUR_WEBHOOK_PATHThe node should light up in the editor with the request details, and the terminal should get a 200 back. Two things to check in that output:
- The URL you copied starts with
https://n8n.example.com. If it says http,WEBHOOK_URLis not being picked up. - The response is a
200, not a301or308. A redirect means a caller sending a POST would lose its payload.
Now add a second node, for example Respond to Webhook with a fixed message, save and toggle the workflow to Active. The production URL drops the -test suffix:
curl https://n8n.example.com/webhook/YOUR_WEBHOOK_PATHIf you get your message back, you have a working automation server. Anything that can make an HTTP request can now trigger a workflow.
Keeping it healthy
- Updates: n8n releases often and its minor versions can include database migrations. In the compose editor, pin the image to a version instead of latest, and when you want to upgrade, read the release notes, change the tag and redeploy. Rolling back is then just changing the tag back.
- Backups: the database is the state. Back up the PostgreSQL volume, or the database itself, on a schedule, and keep the encryption key with it. A cheap extra is exporting your workflows as JSON from inside the container with the command below and shipping the files anywhere you like.
- Executions: if the database keeps growing, lower
EXECUTIONS_DATA_MAX_AGEor set the workflow-level option to save only failed executions. Most of the time you don't need the payload of every successful run.
docker exec -it <N8N_CONTAINER> n8n export:workflow --all --output=/home/node/.n8n/workflows-backup.jsonThat's it! You have n8n running on your own server, reachable over HTTPS, with its credentials encrypted with a key you control. From here you can connect it to whatever you already run on Coolify (a database, an API, this blog) and start automating the boring parts. When a single instance stops being enough, n8n's queue mode with Redis and worker containers is the next step, and it also fits nicely in a Coolify compose service.

