When Is n8n Overkill?

Sam Rivera

Sam Rivera

September 27, 2026

When Is n8n Overkill?

Last spring I had five workflows running in a self-hosted n8n container on a small VPS. A nightly Stripe export to a CSV in object storage. A weekly “who hasn’t logged in for 30 days” email to me. A daily database dump. An RSS-to-Discord relay. And one webhook that took a form submission, looked up the customer, and opened a support ticket.

Then I moved the box. New VPS, same Compose file, same volume copied over. Everything came up green. Two days later I noticed the Stripe export folder hadn’t grown. The workflow was still “active.” The executions list showed it running every night. Each run failed on the first node with a credentials error, because I had copied the database but not the encryption key that decrypts the stored credentials. n8n generated a fresh key on first boot, happily, and every saved API token became noise.

That was entirely my mistake. But fixing it forced me to look at those five workflows one at a time, and I realized four of them were a single HTTP call and a file write wearing a visual editor as a costume. I moved those four to cron and short scripts. n8n kept one job. This is the reasoning I used, because “when is n8n overkill” has a more specific answer than “when your workflow is simple.”

What n8n actually gives you

It’s worth being fair first. n8n is not a heavy tool by enterprise standards. It’s a Node process, a database (SQLite by default, Postgres if you configure it), and a web editor. On my box it sat around 250 MB of RAM idle, which is nothing on a 2 GB VPS.

What you get for that:

  • Triggers you don’t have to build. Webhooks with a URL already listening, polling triggers for dozens of SaaS apps, schedule triggers.
  • Credential storage. OAuth dances handled for you, tokens refreshed without you writing a refresh loop.
  • An execution log with inputs and outputs per node. This is the underrated one. When a run fails you can click the node and see the exact JSON it received.
  • Retries and error workflows without writing your own retry wrapper.
  • A canvas other people can read. A non-developer can open it and roughly follow what happens.

Every one of those is real value. The question is whether your job uses any of them. My four simple workflows used exactly one: the schedule trigger. Cron has done that since before I was born.

A whiteboard covered in a tangled hand-drawn flowchart with a marker and coffee mug

The test I use now: count the features you’d miss

For each workflow, I ask what I would have to write myself if n8n disappeared tomorrow. Not “could I rewrite it,” because the answer is always yes. What specifically would I have to build?

Here’s how that went for the Stripe export:

  • Trigger: once a night. Cron line. Zero work.
  • Credentials: one Stripe restricted key. An environment file readable only by the user running the job. Five minutes.
  • Logic: list charges since yesterday, write CSV, upload. About 40 lines of Python with the official client.
  • Retries: Stripe’s client already retries on network errors. If the whole run fails, tomorrow’s run covers two days, because I query by timestamp, not “yesterday.”
  • Execution log: this is the one I’d genuinely miss. More on that below.

Four of five features were free or trivial outside n8n. That’s the signal. When the replacement is a cron line and a script you could explain to yourself in a month, n8n is overkill for that job.

Compare the support-ticket webhook, the one I kept. It needed a public URL listening at all times, a customer lookup in one API, a conditional branch on plan type, ticket creation in another API with an OAuth token that expires, and a retry if the ticketing API returned a 429. Rebuilding that means a small web service, a token refresh routine, a queue or at least a retry loop, and somewhere to see failures. That’s a real project. n8n earns its container for that one.

Signs n8n is overkill

The only trigger is a clock

If the workflow starts with a Schedule node and nothing else ever starts it, you’re paying for an always-on Node process and a web UI to do what crontab -e does. A systemd timer is even better if you want logging and “run missed jobs after reboot” behavior with Persistent=true.

Every node is an HTTP Request node

n8n’s value is its prebuilt integrations. If you ended up using the generic HTTP Request node for every step because the built-in node didn’t expose the endpoint you needed, you’re writing API calls anyway, just in a form field instead of a file. At that point a script is shorter and diffable.

You’re writing JavaScript in Code nodes to get around the canvas

One Code node to reshape a payload is fine. Three Code nodes and a Merge node to emulate a loop with state is a program that happens to be stored in a database row. It doesn’t get linted, it doesn’t get tests, and your editor’s search can’t find it.

Nobody else will ever open the editor

The canvas is a communication tool. If you are the only person who will ever read the workflow, that benefit is zero. A script with a docstring at the top communicates just as well to future you.

The data is yours, not a SaaS app’s

Dumping your own Postgres, rotating your own logs, pruning your own backups: these touch your own server. There’s no OAuth, no third-party rate limit, no integration to maintain. n8n adds an extra layer between you and pg_dump for no benefit. Worse, if n8n runs on the same box it’s backing up, its own database is one of the things you’re backing up, which gets circular fast.

Signs n8n is not overkill

I want to be clear that I didn’t rip n8n out. It’s the right tool when:

  • Something outside starts the job. A webhook from Stripe, a form, a GitHub event. Keeping a listener up, authenticated, and logged is exactly what n8n does well.
  • You need OAuth to a SaaS API. Google, Microsoft, HubSpot. Writing and maintaining token refresh for one integration is annoying. For three it’s a part-time job.
  • A non-developer needs to change it. If someone on the team adjusts a filter or a message template, a canvas beats asking them to edit Python.
  • There’s a human step in the middle. An approval, a form, a “wait for reply.” Building that yourself means building state, and state is where weekend scripts turn into products. If that’s the shape of your problem, the real comparison is when n8n beats a weekend of custom CRUD, not n8n versus cron.
  • You need to see each run’s data. Debugging a webhook that sometimes gets a malformed payload is much easier when you can click the failed execution and read the exact body.

A small closet shelf holding one fanless computer and tangled ethernet cables in dim blue light

What I lost when I moved to cron, and how I patched it

The honest cost of cron is observability. n8n shows you a red execution. Cron shows you nothing. If a script exits with an error at 3 a.m., cron will try to email the output to the local user, which on a VPS usually means a mailbox nobody reads.

Here’s what replaced the execution log for me:

  1. Every script exits non-zero on failure. set -euo pipefail in shell, and no bare except: pass in Python. A script that swallows its own errors is worse than n8n’s silent-credentials failure, because at least n8n recorded it.
  2. Output goes to a log file with a date. >> /var/log/jobs/stripe-export.log 2>&1 in the cron line. I rotate with logrotate.
  3. Each job pings a heartbeat URL at the end, only on success. If the ping doesn’t arrive by a deadline, I get a notification. This catches the failure mode that bit me in n8n: a job that “runs” but doesn’t do the work. The choice between Healthchecks.io cron checks and Uptime Kuma push monitors matters more than it sounds, because both can stay green if you ping at the start of the job instead of the end.
  4. The script checks its own output. The Stripe export verifies the uploaded file exists and is non-empty before it pings. A heartbeat that only proves the script reached its last line isn’t enough.

That’s maybe an hour of setup across four jobs. In exchange I got rid of a database I had to back up, an encryption key I had to not lose, a web UI I had to keep patched and behind auth, and upgrade notes I had to read before pulling a new image.

The maintenance math nobody puts on the pricing page

Self-hosted n8n is free in license terms. It’s not free in attention. Over a year, running it means:

  • Pulling new images and reading release notes for breaking changes.
  • Keeping the editor behind authentication and ideally not exposed to the public internet at all, since it holds credentials to everything it touches.
  • Backing up its database and its encryption key, and testing that the restore actually decrypts credentials. I learned that part the hard way.
  • Pruning execution data so the database doesn’t grow without limit. There are environment variables for this; you have to know to set them.

For one webhook with OAuth and branching, that’s a fair trade. For four cron jobs, it isn’t. A crontab has no upgrade path, no login page, and no encryption key. It’s six lines in a text file I can read over SSH from my phone.

A quick decision guide

If you’re staring at a workflow and wondering, run through this:

  1. What starts it? Only a clock: lean toward cron. An external event: lean toward n8n or a small service.
  2. How many third-party APIs, and do any need OAuth? Zero or one with a static key: script. Several, or OAuth: n8n earns its keep.
  3. Is there branching or a human in the loop? Linear steps: script. Conditional paths, waits, approvals: n8n.
  4. Who else reads it? Just you: script. Anyone non-technical: canvas.
  5. Are you already running n8n for something else? This one matters. If the container is already up, backed up, and maintained for a legitimate reason, adding a simple scheduled job to it costs almost nothing. The overkill is standing up n8n for the simple job.

That last point is where I landed. I still run n8n, for the support webhook. When I have a new idea for an automation, I don’t default to opening the editor anymore. I ask whether it’s a clock and a single API call. If it is, it goes in a script with a heartbeat, and the canvas stays for the work that actually needs one.

More articles for you