Self-Hosting n8n on Railway: My $5/Month Automation Stack
How I moved off n8n Cloud, cut my bill by 80%, and got full workflow control — with the exact Railway config I use.
About eight months ago I was paying $20/month for n8n Cloud to run maybe a dozen workflows — a Slack notifier here, a GitHub webhook there, some client reporting automation. It worked fine, but $20/month for something I was barely using felt wrong. I'm also the kind of developer who gets itchy when I don't control my own infrastructure.
So I self-hosted n8n on Railway. It took about 45 minutes to set up properly, and I've been running it for eight months without touching it. My bill averages $4.80/month.
Here's the exact setup.
Why n8n Over Zapier or Make
Before getting into the how, a quick word on why n8n specifically.
Zapier is expensive the moment you need anything beyond simple triggers. Make (formerly Integromat) is better on pricing but the visual interface starts feeling limiting when you're building multi-step conditional logic. n8n is open-source, the self-hosted version has no task limits, and the node-based editor actually mirrors how I think about workflows as a developer — it's closer to writing code than dragging boxes around.
The JavaScript expression engine is particularly useful. You can do things like:
{{ $json.items.filter(item => item.status === 'active').length }}
Right inline in the workflow, no custom function node needed for simple transformations.
The Stack
Here's what I'm running:
- n8n — the automation engine
- Railway — hosting the n8n container + PostgreSQL
- Supabase — for any workflows that need a proper database layer beyond n8n's own storage
- Custom domain via Cloudflare (free)
Total monthly cost: Railway ~$4.80, Supabase free tier (I'm not hitting limits), Cloudflare free.
Setting Up n8n on Railway
Railway makes this genuinely easy. They have a one-click n8n template, but I prefer setting it up manually so I understand what's happening.
Step 1: Create the project
In Railway, create a new project and add two services:
- A PostgreSQL database service
- An n8n service from a Docker image
For the n8n service, use the official image: n8nio/n8n:latest
If you want version pinning (recommended for production — you don't want n8n auto-updating to a breaking version):
n8nio/n8n:1.74.0
Check the n8n releases page for the latest stable tag.
Step 2: Environment variables
This is where most people get tripped up. Here are the env vars you actually need:
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=${{Postgres.PGHOST}}
DB_POSTGRESDB_PORT=${{Postgres.PGPORT}}
DB_POSTGRESDB_DATABASE=${{Postgres.PGDATABASE}}
DB_POSTGRESDB_USER=${{Postgres.PGUSER}}
DB_POSTGRESDB_PASSWORD=${{Postgres.PGPASSWORD}}
# n8n core config
N8N_HOST=your-subdomain.yourdomain.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://your-subdomain.yourdomain.com/
# Security — generate a proper secret
N8N_ENCRYPTION_KEY=your-random-32-char-string-here
# Execution settings
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_PRUNE_MAX_COUNT=5000
# Timezone
GENERIC_TIMEZONE=Europe/Warsaw
The ${{Postgres.PGHOST}} syntax is Railway's variable referencing — it automatically pulls from your PostgreSQL service. No copying connection strings manually.
N8N_ENCRYPTION_KEY is critical. This encrypts your stored credentials. Generate it once, store it somewhere safe (a password manager), and never change it or you'll lose access to all saved credentials.
EXECUTIONS_DATA_PRUNE keeps your database from bloating. I set max age to 168 hours (7 days) and a max count of 5,000 executions. Adjust based on how much execution history you actually review.
Step 3: Persistent storage for n8n data
Railway services are stateless by default. You need a volume for n8n's local file storage (used for binary data in workflows, custom nodes, etc.):
In your n8n service settings, add a volume mounted at /home/node/.n8n
Set size to 1GB — this is plenty for most setups and costs almost nothing on Railway's pricing.
Step 4: Custom domain
In Railway, go to your n8n service → Settings → Networking → Generate Domain, or add a custom domain if you have one.
I use a subdomain like automation.mydomain.com. In Cloudflare, I add a CNAME record pointing to Railway's generated domain, with the proxy toggled on. This gives you free DDoS protection and hides the Railway URL from clients.
Update N8N_HOST and WEBHOOK_URL in your env vars to match your actual domain.
Step 5: First login and basic security
On first boot, n8n will prompt you to create an owner account. Do this immediately — an unsecured n8n instance with a public URL is bad news.
Under Settings → Users, I keep it to just myself. For workflows that multiple clients need to trigger, I use webhook authentication (more on that below) rather than giving anyone else a login.
Configuring Webhook Security
Since n8n is publicly accessible, any workflow that starts with a webhook needs some form of authentication. n8n supports a few options:
Header auth — simple and works for most cases:
Header Name: X-Webhook-Secret
Header Value: your-secret-here
Basic auth — if you're connecting something that only supports Basic auth:
Authentication: Basic Auth
Username: webhookuser
Password: your-password
For anything client-facing, I generate unique secrets per webhook URL and store them in a simple Supabase table. This way I can revoke individual client access without touching the workflow config.
Workflows I Actually Run
Here's what's running on this $5/month instance:
GitHub → Slack notifications. When a PR is opened on any of my active client repos, I get a Slack DM with the title, author, and a link. Took 10 minutes to set up, saves me from constantly checking GitHub.
Weekly client report emails. Every Monday at 8am, a workflow pulls deployment metrics from Vercel's API, formats them into a simple HTML email, and sends via Resend. Clients see a summary of last week's activity without me lifting a finger.
New lead → CRM + Slack. When someone fills out a contact form (Webhook trigger), the workflow adds them to my Notion CRM database and sends a Slack message. I respond to leads within minutes because I'm always notified.
Invoice overdue reminders. I send invoices via a simple system that stores due dates in Supabase. A daily n8n workflow checks for overdue invoices and sends a polite reminder email. Has improved my collection rate noticeably.
Content publishing pipeline. When a new MDX file is pushed to certain repos, a workflow triggers a Vercel deploy, waits for it to complete, then posts to my social accounts. (Yes, this very blog post was deployed this way.)
None of these workflows are technically complex. The value is in them running reliably, automatically, without me remembering to do anything.
Dealing With the Inevitable Failures
Workflows fail. Webhooks get missed. APIs go down. Here's my approach to reliability:
Enable error workflows. In n8n Settings → Workflows, you can set a global error workflow that gets triggered whenever any workflow fails. Mine sends me a Slack message with the workflow name and the error. Five minutes to set up, invaluable for catching silent failures.
Use the retry-on-fail option on HTTP request nodes when calling flaky external APIs. Three retries with exponential backoff handles most transient failures.
Execution logs are your friend. When something breaks, the execution log shows you exactly which node failed and what the input/output data looked like. I've debugged issues in minutes that would have taken hours without it.
Back up your workflows. Export all workflows as JSON periodically (Settings → Import/Export) and commit them to a private git repo. Railway's PostgreSQL is reliable but having your workflow definitions in version control is just good practice.
Railway Cost Breakdown
After eight months I have real numbers:
| Service | Average Monthly Cost | |---------|---------------------| | n8n service (0.5 vCPU, 512MB RAM) | $3.40 | | PostgreSQL (1GB storage) | $0.98 | | Networking/egress | $0.42 | | Total | $4.80 |
Railway bills based on actual resource consumption, not reserved capacity. During off-hours when no workflows are running, the n8n service is essentially idle and costs almost nothing.
If you're running heavier workloads (lots of concurrent executions, large data transformations), bump to 1 vCPU and 1GB RAM — you're still looking at ~$8-10/month, which is half of n8n Cloud's cheapest plan.
When Self-Hosting Doesn't Make Sense
To be straight: self-hosting n8n is a maintenance responsibility. You're in charge of updates, you need to think about backups, and when something breaks at 2am it's your problem.
If you're running a larger team, n8n Cloud's team features (SSO, role-based access, audit logs) might be worth the premium. If you're non-technical or want zero ops burden, Cloud makes sense.
But if you're a solo developer or small freelance operation comfortable with a bit of DevOps, the self-hosted path on Railway is genuinely the best value. You get the same n8n, unlimited executions, and you keep full control of your workflow data.
Getting Started
The fastest path: create a Railway account, add a new project, and search their template library for "n8n". The community template gets you 80% of the way there. Then come back and apply the env var configuration I described above — the template defaults are missing a few things.
For persistent storage and custom domain setup, you'll want to do those manually following the steps above. Takes maybe an hour total if you're moving carefully.
After that, explore n8n's template library — there are 1,000+ community workflows covering almost every integration you'd want to build. I treat it like a starting point, then customize to fit my actual needs.
Eight months in, this has been one of the best infrastructure decisions I've made for my freelance practice. Reliable automation, full control, cost that I barely notice.
This post contains affiliate links. If you sign up for Railway or Supabase through my links, I may earn a small commission at no extra cost to you. I only recommend tools I actually use.