A client was paying $840/month for a Zapier Business plan. They had 47 active Zaps across their sales, operations, and customer support teams. After the migration to self-hosted n8n, their monthly automation cost dropped to $8/month — the EC2 instance running n8n.
Here's the exact process I used over 3 weeks, including the 3 edge cases that almost broke production.
Phase 1: Audit (Week 1, Days 1–3)
Before touching anything, I audited every Zap. I created a spreadsheet with:
- Zap name
- Trigger app + event
- Action apps + events
- Monthly task count (from Zapier dashboard)
- Business criticality (high / medium / low)
- Known quirks or notes
The 47 Zaps broke down like this:
| Category | Count | Notes |
|---|---|---|
| CRM automation (HubSpot/Close) | 18 | High criticality |
| Slack notifications | 9 | Easy to migrate |
| Google Sheets sync | 7 | Medium complexity |
| Email parsing/routing | 6 | Complex — needed code nodes |
| Payment/Stripe webhooks | 4 | High criticality |
| Misc (3rd party apps) | 3 | Needed custom HTTP nodes |
Phase 2: n8n Setup (Week 1, Days 4–5)
Set up n8n on AWS EC2 (t3.small, Ubuntu 22.04) with Docker Compose, Nginx, SSL via Certbot. Full setup takes about 2 hours. (See my guide: How to Self-Host n8n on AWS EC2)
Phase 3: Migration by Priority (Week 2)
I migrated lowest-criticality Zaps first to build confidence, then moved to high-criticality CRM flows.
The Zapier-to-n8n Equivalents
| Zapier | n8n Equivalent |
|---|---|
| Webhook trigger | Webhook node |
| Filter step | IF node |
| Paths (branching) | Switch node |
| Formatter (text) | Set node + JS expression |
| Delay step | Wait node |
| Looping (not native) | SplitInBatches node |
| Code step | Code node (JS or Python) |
| HTTP request | HTTP Request node |
The 3 Edge Cases That Nearly Broke Things
Edge Case 1: Zapier Formatter date parsing
Several Zaps used Zapier's Formatter to parse dates like "Dec 15, 2024" into ISO format. n8n doesn't have a direct equivalent — I used a JS Code node:
const raw = $json.date_string; // "Dec 15, 2024"
const parsed = new Date(raw);
return [{ json: { date_iso: parsed.toISOString() } }];
Edge Case 2: Multi-step looping
One Zap used Zapier's native looping (beta feature) to iterate over a list of contacts. n8n handles this differently — you split the input array, process each item, then merge results. The SplitInBatches node is the key.
Edge Case 3: HubSpot pagination
A Zap that fetched all contacts from HubSpot hit the 100-contact API limit on the Zapier node. In n8n, the HubSpot node handles pagination automatically with the "Return All" toggle — actually better than Zapier in this case.
Parallel running period: For 5 days, I ran both Zapier and n8n simultaneously for the 12 highest-criticality Zaps. I compared outputs in a Google Sheet. This caught 2 discrepancies (both were the date parsing edge case above) before I turned off Zapier.
Phase 4: Monitoring Setup (Week 3)
After full cutover, I set up a monitoring layer:
- n8n error workflow: A dedicated workflow that catches all execution errors and sends a Slack DM with the workflow name, error message, and timestamp
- Health check cron: Every 5 minutes, a cron job pings
n8n.client.com/healthz - Weekly execution report: Sunday morning Slack message with execution counts, error rates, and slowest workflows
# n8n error catch workflow
# Set as "Error Workflow" in n8n settings (Settings → Error Workflow)
# Triggers automatically on any failed execution
Trigger: Error Trigger
→ Send Slack DM: "❌ n8n Error in: {{$json.workflow.name}}\nError: {{$json.execution.error.message}}"
The Numbers After 60 Days
- Zapier cost: $840/month → $0
- EC2 cost: $8/month
- Annual savings: $9,984
- Execution reliability: 99.7% (2 failures in 60 days, both caught by monitoring)
- Average execution time: 15% faster than Zapier (no cloud queue delay)
Zapier migrations are one of my core services. If you're on a $200+ Zapier plan, the math almost always works in favour of migrating. Get a free migration assessment.