Home DevOps Airflow Configuration: One Typo That Took Down Workers
Advanced 5 min · August 1, 2026
Airflow Environment Configuration

Airflow Configuration: One Typo That Took Down Workers

Airflow config typos crash workers silently.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Production
production tested
August 01, 2026
last updated
1,750
articles · all by Naren
Before you start⏱ 15-20 min read
  • Airflow running via docker-compose, Helm, or a pip install you can inspect.
  • Comfort reading airflow.cfg and running CLI commands in the container.
  • A DAG in production you've had to debug once or twice.
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Airflow config loads in layers: built-in defaults, then airflow.cfg, then env vars, then secrets backends — each layer overrides the one before it.
  • Key components: airflow.cfg, AIRFLOW__SECTION__KEY env vars, the airflow config CLI, and the plugins folder.
  • A duplicated [core] section silently reset parallelism to its default of 32 — our workers crashed on the next restart.
  • Env vars override the file without touching it, which makes them the cleanest lever for containers and CI pipelines.
  • Production rule: one source of truth per environment, validated with airflow config list before any rollout.
  • Dotted config sections become underscores in env vars: [providers.some_provider] this_param → AIRFLOW__PROVIDERS_SOME_PROVIDER__THIS_PARAM.
✦ Definition~90s read
What is Airflow Environment Configuration?

Airflow configuration is a four-layer override system — defaults, airflow.cfg, env vars, and secrets backends — where the highest layer defining a setting silently wins and airflow config list reveals the resolved truth.

Think of Airflow's configuration like a set of stacked sticky notes.
Plain-English First

Think of Airflow's configuration like a set of stacked sticky notes. Airflow ships with a printed manual of defaults at the bottom, your airflow.cfg file sits on top of it, and every environment variable is a note you slap on top of that. The highest note always wins. Our team once had two copies of the same page in the file — the second one silently wiped our parallelism setting — and the workers crashed overnight. The fix wasn't a better typo, it's knowing which note layer to trust and checking the stack before you run.

Every Airflow team has the same file, and almost nobody reads it twice. airflow.cfg is 300 lines of settings that look harmless — until one duplicated section, one wrong key name, or one version-gated option takes the whole fleet down at 3 AM. You'll spend more time debugging config than debugging DAGs, so treat config like code.

The config system is a chain of four layers: built-in defaults, airflow.cfg, environment variables, and secrets backends. Each layer silently overrides the one below it. That's powerful — and it's exactly why a typo can hide: your file says one thing, your container env says another, and nobody bothered to ask who actually won.

The painful truth: there's no such thing as "one config." There's dev, staging, prod, and three schedulers that all disagree. The teams that survive manage config from a single source per environment and validate before deploying. The teams that don't learn at 3 AM.

This one's about winning that fight before the phone rings.

1. The Config Hierarchy: Defaults → File → Env → Secrets

Airflow resolves every setting through four layers. Built-in defaults ship with the code. airflow.cfg in AIRFLOW_HOME overrides them. Environment variables override the file. And a secrets backend can override any of it at runtime.

Each layer wins over the one below it, wholesale. If the file defines 40 keys and the env defines 1, that 1 env key wins and the other 39 still come from the file. No merging, no warnings — the winner is just whoever is higher.

The trap: your container image may embed an airflow.cfg, your deployment manifest may inject env vars, and your CI may render yet another file. Three sources, one effective result. If you review only one of them, you're reviewing a lie.

Two env-var variants sit between the plain env var and the file in the official precedence order: AIRFLOW__SECTION__KEY_CMD derives the value by running a command, and AIRFLOW__SECTION__KEY_SECRET fetches it from a secrets backend. Both rank above airflow.cfg but below the plain env var, and both are supported for a short list of keys — sql_alchemy_conn, fernet_key, broker_url, result_backend, and smtp_password among them. The full order of resolution: env var, env _CMD, env _SECRET, file value, file command, file secret, built-in default.

Production teams treat the effective config, not the file, as the artifact that matters.

config_priority.shBASH
1
2
3
4
5
6
7
8
9
10
# The same setting, three layers — env always wins
airflow config get-value core parallelism
# 32  (from built-in default)

# 1. default:           core.parallelism = 32
# 2. airflow.cfg:       [core] parallelism = 16
# 3. env var:           AIRFLOW__CORE__PARALLELISM=8

airflow config get-value core parallelism
# 8   (env var layer won)
Mental Model
Config Is a Stack, Not a File
Think of each layer as a sticky note placed on top of the previous one.
  • Defaults are the printed manual — lowest priority.
  • airflow.cfg overrides defaults; env vars override the file.
  • Secrets backends can override even env vars for secret values.
  • airflow config list shows the stack's final answer, not one layer.
📊 Production Insight
Audit the effective config, not the file.
Layering makes every setting a mystery until you check.
Rule: print airflow config list before every rollout.
🎯 Key Takeaway
Config is four layers of overrides, and the top layer wins silently.
A review of airflow.cfg alone never shows the effective settings.
Punchline: debug the resolved value, not the file.

2. Env-Var Precedence and the AIRFLOW__SECTION__KEY Pattern

The env-var naming scheme is the whole game: AIRFLOW__, then the section in uppercase, then the key, all separated by double underscores. AIRFLOW__CORE__PARALLELISM maps to [core] parallelism. AIRFLOW__CELERY__WORKER_CONCURRENCY maps to [celery] worker_concurrency.

Because env vars never touch the file, they're the standard lever for containers, systemd units, and CI. One image, many environments — the env renders the difference at process start.

Two gotchas. First, env vars are read when the process starts, not per task — changing them on a running scheduler does nothing until restart. Second, they must be set for every component that needs them: a scheduler with the var and a worker without it will disagree all night.

Two more rules from the docs. Dotted section names translate to underscores: [providers.some_provider] this_param becomes AIRFLOW__PROVIDERS_SOME_PROVIDER__THIS_PARAM — the dot is illegal in the env-var form. And the AIRFLOW_CONFIG env var overrides the config file path itself, which is how containers point every component at one rendered file. Sharing rules differ by key: the JWT signing key ([api_auth] jwt_secret) must be identical on every component that generates or validates tokens, while database connection strings and Fernet keys should be scoped only to the components that need them.

Treat the env block as the deployable config artifact. Version it, review it, validate it — exactly like code.

airflow_env.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# The pattern: AIRFLOW__SECTION__KEY
AIRFLOW__CORE__PARALLELISM=32
AIRFLOW__CORE__MAX_ACTIVE_TASKS_PER_DAG=8
AIRFLOW__CORE__DAGBAG_IMPORT_TIMEOUT=60
AIRFLOW__SCHEDULER__PARSING_PROCESSES=4
AIRFLOW__CELERY__WORKER_CONCURRENCY=8
AIRFLOW__WEBSERVER__WEB_SERVER_WORKER_TIMEOUT=120

export AIRFLOW__CORE__PARALLELISM AIRFLOW__CORE__MAX_ACTIVE_TASKS_PER_DAG

# Verify what the process will actually see before you start it:
airflow config get-value core parallelism
# 32
📊 Production Insight
Env vars render environment-specific values without touching files.
Set them at process start for every component.
Rule: version the env block, not the file.
🎯 Key Takeaway
AIRFLOW__SECTION__KEY is the portable, reviewable config lever.
Missing vars mean webserver, scheduler, and workers silently diverge.
Punchline: the env block is the config — version it.
Where Should This Setting Live?
IfThe value is a secret (password, API key, token)
UseSecrets backend (Vault, AWS Secrets Manager) referenced by an env var name — never the file, never the image.
IfThe value differs between dev, staging, and prod
UseEnvironment variable rendered per environment from a single template in your deployment repo.
IfThe value is stable, shared, and rarely tuned
Useairflow.cfg committed to the image or repo — one copy, reviewed like code.
IfYou're tuning a knob on a live incident
UseEnv var on the specific process, logged, and promoted to the real config source afterward.
IfThe setting doesn't exist in this Airflow version
UseDon't set it at all — airflow config list reveals the valid keys for the installed version.
AIRFLOW__SECTION__KEY env vars AIRFLOW__SECTION__KEY Env Vars The env block is the deployable config AIRFLOW__SECTION__KEY double underscores, uppercase AIRFLOW__CORE__PARALLELISM maps to [core] parallelism AIRFLOW__CELERY__WORKER_CONCURRENCY maps to [celery] worker_concurrency Env vars override the file highest layer wins, silently Set them on every component THECODEFORGE.IO
thecodeforge.io
Airflow Environment Config

3. Config-as-Code in CI: Render, Validate, Diff

Config-as-code means the env block and airflow.cfg live in the repo, get reviewed in pull requests, and get validated before they touch a host. The validation is the entire point — a typo caught in CI costs 30 seconds, the same typo in prod costs the night.

Render from a template per environment, then prove the render is sane. The proof is a three-command pipeline: render, diff against the previous version, and read back the effective values with airflow config list on the real image.

The read-back is the part teams skip. A rendered file that parses cleanly can still override the wrong section, point at the wrong database, or silently reset a knob — because the merge with defaults happens at runtime, not at render time.

Gate the deploy on the diff. If a rollback-shaped diff lands in the pull request, it should need a human to approve it before it ships.

📊 Production Insight
Validation must run inside the real image.
Rendered config that parses can still drift at runtime.
Rule: gate deploys on the effective-config diff.
🎯 Key Takeaway
Render per environment from one template, then validate inside the image.
Diff the effective config against the live baseline in CI.
Punchline: if it didn't diff clean, it didn't deploy.

4. Validating With airflow config list

airflow config list is the swiss-army tool of config debugging. Run it and you get every setting, its source, and whether it came from defaults, the file, or an env var — which is exactly how you catch the sticky-note war between layers.

The output marks each option with where it came from, so the command is also your documentation: it tells you which keys exist in your installed version. That matters more than you'd think — Airflow 2.x and 3.x renamed and dropped settings between releases.

Pair it with airflow config get-value for surgical checks: one command, one answer, the winner of the whole stack. It's the fastest way to answer "why is parallelism 32 when my file says 16?"

One flag to know: airflow config list --defaults prints the built-in defaults, which is the sanctioned way to generate a fresh, clean airflow.cfg from scratch instead of inheriting one across versions and upgrades.

Build it into the runbook. Every config-related incident starts with the same two commands — you want them to be muscle memory.

config_validate.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# See every setting, with its source layer
airflow config list

# Answer one question: what does parallelism actually resolve to?
airflow config get-value core parallelism
# 32

# Prove which layer is winning (file vs env) for a suspicious key
airflow config get-value scheduler scheduler_heartbeat_sec
# 5

# Spot-check the whole stack against your baseline in one shot
diff <(airflow config list) baseline/effective.txt && echo "config OK"
🔥config list Is Version-Aware
Run it on the exact image you deploy. A 3.x-only key printed on your laptop tells you nothing about what the 2.8 container will accept.
📊 Production Insight
airflow config list shows the winner of every layer.
get-value answers the single-key question in one line.
Rule: start every config incident with both commands.
🎯 Key Takeaway
airflow config list reveals sources; get-value reveals winners.
Version differences hide invalid keys until runtime.
Punchline: validate with the CLI, never with your eyes.
Validating config with airflow config list Validate Config: airflow config list Every setting, its source, and winner AIRFLOW__SECTION__KEY env highest priority layer airflow.cfg overrides built-in defaults Built-in defaults lowest priority airflow config list every setting + its source get-value core parallelism resolves to 32 — the winner Start every config incident with both THECODEFORGE.IO
thecodeforge.io
Airflow Environment Config

5. Plugins and the Plugin Folder

Plugins extend Airflow — custom operators, hooks, sensors, even whole UI views — and they're loaded from plugins_folder, a config key in its own right. Misset it and Airflow runs fine, just without half your custom code.

The folder is read at process start. A plugin with a broken import fails the load of every plugin in that folder, and the failure mode is rarely a crash — it's a silent absence. Your custom operator simply isn't there when the DAG tries to import it.

Keep plugins versioned like DAGs: pinned versions, changelog review, and a smoke test that imports every plugin module before deploy. A plugin that fails to import in CI is a 30-second fix; the same failure in prod is a blocked schedule.

📊 Production Insight
Plugins load once, at process start.
A broken import removes every plugin, not just one.
Rule: import-smoke plugins in CI before deploying them.
🎯 Key Takeaway
plugins_folder is a config key with real production weight.
One failing plugin import silently strips your custom operators.
Punchline: smoke-test the plugin folder in CI, always.

6. Version-Specific Config Drift

Config is versioned too — silently. Upgrading Airflow from 2.8 to 3.x renamed settings, moved sections, and dropped keys outright. A 2.x airflow.cfg copied forward either fails validation or, worse, quietly ignores the parts that no longer exist.

Drift is dangerous because it's invisible: the config parses, the defaults kick in, and you run months on settings you never chose. Our duplicate-section incident was one form; version drift is the cousin that arrives with every upgrade.

The defense is a baseline. Capture airflow config list from the current version, store it with the release, and diff it after every upgrade. If a tuned knob drops out of the effective config, the diff will scream about it.

Read the changelog section on configuration for every minor bump. Airflow documents renamed keys — the teams that skip this page are the teams that file the incident tickets.

version_diff.shBASH
1
2
3
4
5
6
7
8
9
# After any Airflow upgrade, before pointing traffic at it:
airflow version
# 3.3.0

airflow config list > baseline/after-upgrade-3.3.txt

diff baseline/prod-2.8.txt baseline/after-upgrade-3.3.txt
# Only the settings YOU care about should appear.
# Any tuned knob that vanished from the output is now a default.
Output
30a31
> [scheduler] parsing_processes = 4
📊 Production Insight
Upgrades drop and rename config keys silently.
Defaults taking over means tuned knobs silently reset.
Rule: diff effective config against baseline after every upgrade.
🎯 Key Takeaway
Config drift arrives with every version bump, invisibly.
Store effective-config baselines per release and diff them.
Punchline: no baseline, no upgrade — diff first.

7. Incident Deep Dive: Failure Flow → Fix

Replay the night. The config push landed at 01:00. It contained a duplicated [core] section — the merge of two feature branches re-added a block that already existed. The parser read the file, took the second block as authoritative, and reset parallelism, executor, and max_active_tasks to their built-in defaults.

At 01:00 the new workers came up, read the reset values, and crashed on startup — mismatched executor settings, one after another, in a loop. The webserver never noticed, because the webserver doesn't run tasks. The scheduler looked alive, because the scheduler process was alive. The fleet was down and the only loud signal was the absence of DAG runs.

The fix had three parts. First, a validated render: the effective config is now produced in CI, diffed against the live baseline, and gated before any host restarts. Second, env vars replaced file edits for every environment-specific knob — the file is now read-only on the servers. Third, the incident runbook starts with airflow config list, because the resolved value, not the pushed file, is what broke the night.

📊 Production Insight
The crash was silent because parsing doesn't validate semantics.
The webserver's health said nothing about workers.
Rule: config changes restart components only after a validated diff.
🎯 Key Takeaway
A duplicated section resets tuned settings to defaults with zero warnings.
Validation inside the real image catches what reviews can't.
Punchline: the effective config is the only config that matters.
Config incident: failure flow to fix Config Incident: Failure Flow → Fix Duplicated [core] section at 01:00 01:00 — the failure The fix Config push lands duplicated [core] section Second block wins parallelism → default 32 Workers crash-loop every restart, in a row The silent signal no DAG runs, UI healthy Validate in CI airflow config list Diff vs live baseline gate before restart Env vars, not edits file read-only on hosts Resolved value is truth runbook starts with config list Parsing doesn't validate semantics a duplicated section reset the fleet THECODEFORGE.IO
thecodeforge.io
Airflow Environment Config
● Production incidentPOST-MORTEMseverity: high

The Typo That Killed the Nightly Fleet

Symptom
After a routine config push, every worker process failed to start in a crash loop. The webserver stayed up and the scheduler looked healthy, but no task ever ran — the nightly pipeline sat dead for hours while logs screamed an error nobody had seen before.
Assumption
The team assumed config errors were loud: a typo would fail fast at startup and show up in the diff review. They also assumed the file was applied exactly as written — one file, one meaning per key.
Root cause
A duplicated [core] section in airflow.cfg. The config parser silently took the second block as authoritative, which reset parallelism and executor-related settings to defaults and left the workers in a state that crashed on restart. No validation ran before the file shipped to every host.
Fix
Moved all environment-specific settings to AIRFLOW__SECTION__KEY env vars rendered from a single source of truth, added airflow config list and airflow config get-value checks to the deploy pipeline, and made every rollout validate the effective config before restarting anything.
Key lesson
  • Config typos are silent: a duplicated section, a wrong key name, or a missing env var all look identical at parse time.
  • The effective config is a merge of layers — the file you review is not the config that runs.
  • Validate with airflow config list in CI before touching a single worker.
  • Environment-specific values belong in env vars, not in per-host edits of airflow.cfg.
Production debug guideSymptom to Action5 entries
Symptom · 01
Workers crash-loop right after a config push
Fix
Run airflow config list on the failing host and diff the effective values against the values in the file you pushed — a layer above the file is overriding it.
Symptom · 02
Webserver and scheduler disagree on settings
Fix
Compare the env of each process (docker compose config or systemctl show) — one service is missing the AIRFLOW__SECTION__KEY variables the other has.
Symptom · 03
A setting you changed in airflow.cfg has no effect
Fix
Check for a second copy of the section in the same file, then check env vars; env vars always win over the file.
Symptom · 04
Config worked on the laptop, fails in prod
Fix
Compare Airflow versions — airflow config list shows defaults per version, and a 3.x-only key is silently ignored (or fatal) on 2.x.
Symptom · 05
A plugin never loads
Fix
Verify the plugins_folder path resolves on the container (not just your laptop) and that the module is importable from that directory.
Where to Set an Airflow Setting
LayerBest ForPrecedenceRollout Risk
Built-in defaultsBaseline sanity; nothing to manageLowestNone
airflow.cfgStable, shared, team-reviewed settingsOverrides defaultsMedium — ships to every host
AIRFLOW__SECTION__KEY env varsPer-environment values; containers and CIOverrides the fileLow — rendered and versioned
Secrets backendPasswords, tokens, API keysOverrides everythingLow — no secrets in the image
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
config_priority.shairflow config get-value core parallelism1. The Config Hierarchy
airflow_env.shAIRFLOW__CORE__PARALLELISM=322. Env-Var Precedence and the AIRFLOW__SECTION__KEY Pattern
config_validate.shairflow config list4. Validating With airflow config list
version_diff.shairflow version6. Version-Specific Config Drift

Key takeaways

1
Airflow config is four override layers; the highest layer that defines a setting wins, silently.
2
AIRFLOW__SECTION__KEY env vars are the portable, versionable lever for per-environment values.
3
Validate with airflow config list inside the real image and diff the effective config before rollout.
4
Plugins load once at start; one broken import silently strips every custom operator.
5
Upgrades rename and drop config keys
keep a per-version effective-config baseline and diff it.

Common mistakes to avoid

4 patterns
×

Editing airflow.cfg directly on running containers

Symptom
Settings revert on the next container restart; nobody knows which config is real.
Fix
Bake the file into the image or inject env vars; the running filesystem is not a config store.
×

Hand-merging branches with duplicated [core] sections

Symptom
Tuned knobs silently reset to defaults; workers crash or misbehave after restart.
Fix
Validate with airflow config list in CI and diff against the baseline before shipping.
×

Setting env vars on one component but not the others

Symptom
Scheduler and workers disagree on concurrency and executor settings all night.
Fix
Render one env block per environment and apply it identically to every component.
×

Copying airflow.cfg across Airflow versions

Symptom
Settings silently ignored after an upgrade; tuned values revert to defaults.
Fix
Diff effective config (airflow config list) per version and re-apply dropped keys deliberately.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
How does Airflow resolve a setting like parallelism, and where does airf...
Q02SENIOR
A worker won't start after you changed the executor setting. Walk me thr...
Q03SENIOR
Design config management for Airflow across dev, staging, and prod. Wher...
Q01 of 03JUNIOR

How does Airflow resolve a setting like parallelism, and where does airflow.cfg live?

ANSWER
Airflow merges four layers: built-in defaults, airflow.cfg in AIRFLOW_HOME, AIRFLOW__SECTION__KEY environment variables, and secrets backends. Each layer overrides the one below it, so the effective value comes from the highest layer that defines it. airflow config get-value core parallelism shows the winner.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between airflow.cfg and environment variables in Airflow?
02
How do I find the effective value of a setting after all layers are applied?
03
Why did my workers crash after a config change that passed review?
04
Can I set Airflow config from within a DAG file?
05
Where should I store secrets like database passwords in Airflow?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
August 01, 2026
last updated
1,750
articles · all by Naren
🔥

That's Airflow. Mark it forged?

5 min read · try the examples if you haven't

Previous
Airflow on Kubernetes
25 / 37 · Airflow
Next
Airflow High Availability