Every few months a team decides that the shell script on the cron box has outgrown itself, and the shortlist that follows is almost always the same three: n8n, Kestra, Apache Airflow. They all run steps in a dependency order, retry the ones that fail, and show you a graph afterwards. On a feature comparison they look close enough that the argument becomes about taste.
They are not close. Each was built for a different person doing a different job, and the question that actually settles it is one you can answer before evaluating anything: who is going to author these workflows, and what is a step allowed to be?
Three machines, briefly
Airflow is a Python program that produces a graph. A DAG file is imported by the scheduler, and dependencies exist because you called one task with another task’s return value. It is fifteen-odd years of data-engineering convention compressed into a scheduler: dated runs, catch-up, backfills, and a provider package for every warehouse and cloud service you have ever billed. Airflow 3 rebuilt a good deal of that — a separate task execution API so workers no longer talk directly to the metadata database, DAG versioning, asset-driven scheduling — but the shape is unchanged. You write Python, and the platform runs it.
Kestra is a declarative engine that executes YAML. A flow is a document with an id,
a namespace, a list of tasks, and optional triggers and errors blocks. Each task
names a plugin type — io.kestra.plugin.aws.s3.Upload — and the engine validates the
document against that plugin’s schema before running anything. Steps are language-agnostic
by design: a script or a container is a first-class task rather than an escape hatch, and
data moves between steps as a reference into internal storage instead of as a value in
memory. It runs as a single JVM process against Postgres.
n8n is a node-based automation tool with a visual canvas and several hundred prebuilt integrations. A workflow is a JSON graph of nodes wired together, and it exists to do the thing that is tedious in the other two: authenticate against a SaaS API, paginate its results, and reshape the payload for another SaaS API. It is also where the AI-agent work has landed — LLM nodes, tool calling, and human-in-the-loop steps are native rather than bolted on.
The axes that actually decide it
| Question | n8n | Kestra | Airflow |
|---|---|---|---|
| A non-engineer can author a workflow | yesdrag nodes on a canvas | with caveatsYAML in a browser editor | noit is a Python program |
| Workflow definition lives in Git as the source of truth | with caveatsJSON export, or paid Git sync | yesYAML files, synced from a repo | yesPython files in a dags folder |
| Steps can be written in any language | with caveatsJavaScript or Python in a code node | yesscripts and containers as first-class tasks | with caveatsPython, or a task that shells out |
| Built for data volume passing between steps | noitems are held in memory per execution | yesinternal storage, files passed by reference | with caveatsXComs are small; real data goes elsewhere |
| Hundreds of prebuilt SaaS connectors | yesthe entire point of the product | with caveatsplugins skew to data and infrastructure | with caveatsproviders skew to warehouses and clouds |
| Backfill and rerun a date range as a first-class idea | noyou rerun executions by hand | yesbackfill from the UI or the API | yesthe model the scheduler is built around |
| Event-driven and sub-minute triggering | yeswebhooks are native | yeswebhook, queue and file triggers | with caveatsassets and watchers, on a scheduler loop |
| Runs as one process on one box | yesNode process plus a database | yesone JVM binary plus Postgres | noAPI server, scheduler, workers, triggerer, DB |
| License lets you resell it inside your product | noSustainable Use License, not OSI open source | yesApache 2.0 core, paid enterprise tier | yesApache 2.0, vendor-neutral foundation |
Two rows in that table end most arguments. The first, because a tool a non-engineer can author in is either the requirement or the disqualifier and rarely anything in between. The last, because it is a legal constraint discovered after the proof of concept.
The definition is the product
Everything downstream — how a workflow is reviewed, who is allowed to change it, what a pull request looks like — follows from what the definition physically is. So look at the same trivial job in all three.
The same nightly job, three ways
- What the definition is
- —
- What it costs you
- —
Airflow’s version is a program, which is both the strength and the trap. You get loops, conditionals, shared libraries, and your own testing tools; you also get the ability to make an HTTP call at module scope, which the DAG processor will then execute on every parse cycle forever. Nothing in the YAML or the JSON versions can do that, because neither of them can do anything.
Kestra’s version is data. It is schema-validated before execution, it diffs cleanly, and
the outputs.extract.uri expression passes a pointer rather than the bytes — which is
why a step producing a 4 GB file is unremarkable. The ceiling is real, though: when a step
becomes genuinely bespoke, it becomes a script or container task, and at that point you
are maintaining code again with YAML wrapped around it.
n8n’s version is a JSON graph with pixel coordinates in it, and that detail is worth sitting with. The canvas layout lives in the same document as the logic, credentials are referenced by database id rather than embedded, and the natural editing surface is the UI rather than the file. This is not an accident or an oversight — it is what makes the tool usable by people who will never open a terminal. It is also why Git-based version control is a paid feature rather than the default, and why code review of an n8n workflow tends to mean “watch someone demo it.”
What you actually have to run
The operational cost differs by roughly an order of magnitude, and it does not track the feature sets.
n8n is a Node process and a database. Past one box you switch it to queue mode —
EXECUTIONS_MODE=queue, Redis as the broker, Postgres instead of SQLite — and run worker
containers alongside the main instance. Multi-main high availability is an enterprise
feature.
Kestra is one JVM binary plus Postgres, and stays that way for most deployments. The distributed and high-availability topologies exist for when you outgrow it, but the single-node path is genuinely a single node rather than a demo mode.
Airflow is an API server, a scheduler, a DAG processor, a triggerer, workers, and a metadata database — before it has run one task. Airflow 3 made the DAG processor a separate component by default and put workers behind an execution API rather than giving them database credentials, which is better architecture and more moving parts. This is the strongest argument for buying it managed: the software is free, the operator is not.
The licence is a technical decision
Airflow is Apache 2.0 under the Apache Software Foundation, which is as close to governance-neutral as this category gets. Kestra’s core is Apache 2.0 with a paid enterprise edition on top — the usual open-core split, with SSO, audit logs, tenancy and worker isolation on the commercial side.
n8n is fair-code under the Sustainable Use License, which is not an OSI open source licence. Internal use and modification are fine. Reselling it, or embedding it in a product you charge for, is not, without an agreement. If your plan involves shipping workflows as part of a commercial offering, read that licence during the evaluation rather than after, because it is the most common way a promising spike ends up in the bin.
Where each one actually falls over
n8n fails on data volume. Items flow between nodes as JSON held in memory for the execution. That is invisible at 500 records and fatal at 500,000, and the failure arrives as a memory error rather than as gradual slowness. It also has no real backfill story: rerunning a month of a daily job means rerunning executions by hand.
Airflow fails on latency and on non-Python teams. A scheduler built for dated batch runs is the wrong instrument for reacting to a webhook in two seconds, and no amount of sensor tuning changes that. And the entire authoring model assumes Python is the house language — if the people on call write Go and SQL, every DAG becomes a translation layer maintained by whoever is least unhappy about it.
Kestra fails on ecosystem depth. The plugin catalogue is large and skews usefully toward data and infrastructure, but the community is younger than Airflow’s, the answer to an obscure question is more often “read the source”, and writing your own plugin means writing Java. Check that the three integrations you actually need exist before you commit; most teams find they do.
So which one?
Pick a job, get an answer
—
- What decides it
- —
- Why
- —
- What it will cost you later
- —
The decision reduces to two questions asked in order.
Who authors this? If the answer includes someone who is not an engineer — an ops person, a marketer, a support lead — the answer is n8n and the rest of the comparison is academic. Nobody in that group is going to open a pull request against a Python module, and pretending otherwise produces a queue of change requests routed through the data team.
What is a step? If steps are Python and the work is dated batch data, use Airflow: the backfill semantics, the provider ecosystem and the industry-standard-ness are worth its operational weight, especially bought managed. If steps are containers and scripts in several languages, or the people who own the pipelines are not Python developers, use Kestra: you get declarative definitions that diff cleanly, real backfills, event triggers and file passing by reference, for one process and a Postgres database.
And keep the fourth answer available. A single script on a timer has no graph, no fan-out, and nothing to backfill; it does not need a control plane, it needs cron and an alert when it exits non-zero. The right moment to revisit that is the day a second script has to run after the first, or the day a failure goes unnoticed for three days.
The short version
Airflow is a Python scheduler for dated batch data pipelines, with the deepest ecosystem and the heaviest operational footprint. Kestra is a declarative YAML engine for polyglot pipelines, with most of Airflow’s scheduling semantics, one process to run, and a smaller community. n8n is a visual integration tool with hundreds of connectors, unmatched for gluing SaaS APIs together, unsuitable for large data volumes, and licensed in a way that forbids reselling.
Pick by author and by step. Non-engineers authoring, SaaS APIs as steps: n8n. Python engineers, dated data pipelines: Airflow. Anything polyglot, or a team that wants declarative workflows without operating five processes: Kestra. And whichever you pick, the thing that actually determines whether you are still happy in a year is not the tool — it is whether the workflow definition lives in Git and whether a failure pages someone.