click the screen · press Enter
← back to blog
Kubernetes Journey · Topic 40

Falco in One Page: What the Old Guides Get Wrong

Falco runtime security reference for Kubernetes: the driver reading system calls from the Linux kernel, the rules engine matching them, and the alert leaving on an output channel

Nearly every Falco walkthrough on the internet was written around version 0.28, and three of its steps have quietly stopped being true. The signing key it tells you to import has been revoked. The kernel headers it makes you install are only needed for a driver you probably shouldn't pick. And the reload signal it finishes with is handled for you now. That's the thing this page argues with: the copy-paste block you've got open in another tab.

So no story here, no build-up. If you're standing up Falco on a cluster you own, or revising for the CKS with course notes older than the tool, this is the page I wanted: the paths, the keys and the defaults as they stand in Falco 0.44. One anchor before the tables, because a cheat sheet with nothing to hang off is useless. Falco watches the system calls that programs make to the Linux kernel, checks each one against a set of rules, and shouts when something matches. A system call is just a program asking the kernel to do something on its behalf, like open a file or start a process.

Falco install guides compared: the old 0.28 era steps with a revoked GPG key, kernel headers and a manual SIGHUP reload, against current Falco 0.44 with a keyring, the built-in modern eBPF probe and automatic config reload
Left is the guide you're probably following. Right is what the tool actually does now.

Three steps that stopped being true

These are the ones that actually break things, in the order you'd hit them. I haven't run this install myself yet, so I'm going off the current docs rather than my own terminal, and I'll say so again where it matters.

Old guide vs Falco 0.44
What the old guide saysWhat happens now
apt-key add the key ending 3672BA8FThat key expired in Feb 2023 and is listed as revoked. apt-key is gone too. Use a keyring file and signed-by.
apt-get install linux-headers-$(uname -r), alwaysHeaders, dkms and make are only needed to build the kernel module. The default driver needs none of them.
kill -1 $(cat /var/run/falco.pid) to reloadwatch_config_files is on by default, so Falco picks up config and rule changes on its own.
The first one is the nasty one: the repo signature fails at apt update, before you've installed anything.

The key rotation started in December 2025. The current signing key fingerprint is 478B2FBBC75F4237B731DA4365106822B35B1B1F, valid to December 2028, and it lives at falco.org/repo/falcosecurity-packages.asc. Grab it from there rather than from any blog post, mine included.

The two drivers

A driver is the bit of Falco that sits in the kernel and forwards system-call events up to the rules engine. There used to be three options. As of the May 2026 docs there are two, because the old legacy eBPF probe has been dropped.

Built into the Falco binary, so nothing to download or compile. Needs BTF and BPF ring buffer support in the kernel, which in practice means 5.8 or newer. Can run with capabilities rather than full root: CAP_SYS_BPF, CAP_SYS_PERFMON, CAP_SYS_RESOURCE, CAP_SYS_PTRACE. Managed Kubernetes providers are much happier with this one. Set it with engine.kind: modern_ebpf.
A .ko object loaded into the running kernel. Works back to kernel 3.10, so it is the fallback for older hosts. Needs dkms, make and matching headers, or a prebuilt driver pulled by falcoctl. Requires full privileges, cannot run on capabilities alone, and needs re-handling after a kernel upgrade. Some managed providers will not allow it at all.
Pick the left one unless the kernel is too old for it. That's genuinely the whole decision.

Installing it

On a Debian or Ubuntu host, four commands. Note the keyring rather than apt-key, and note that I've left the header packages out because the default driver doesn't want them.

curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | \
  sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] \
https://download.falco.org/packages/deb stable main" | \
  sudo tee -a /etc/apt/sources.list.d/falcosecurity.list

sudo apt-get update -y
sudo FALCO_FRONTEND=noninteractive apt-get install -y falco

The install normally opens a dialog box asking which driver you want and whether falcoctl should auto-update your rules. Three environment variables steer it if you'd rather it didn't ask: FALCO_FRONTEND=noninteractive skips the dialog, FALCO_DRIVER_CHOICE takes kmod, modern_ebpf or none, and FALCOCTL_ENABLED=no turns off automatic rule updates.

Enabling a driver's unit creates a falco.service alias pointing at it, which is why systemctl status falco works regardless of which one you chose.

sudo systemctl list-unit-files "falco*"
UNIT FILE STATE PRESET falco-custom.service disabled enabled falco-kmod-inject.service static - falco-kmod.service disabled enabled falco-modern-bpf.service enabled enabled falco.service alias - falcoctl-artifact-follow.service disabled enabled
Expected output, not captured from my run. Taken from the Falco 0.44 setup docs.

On Kubernetes the usual route is the Helm chart, which runs one Falco pod per node as a DaemonSet:

helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
  --namespace falco --create-namespace \
  --set driver.kind=modern_ebpf

kubectl get pods -n falco -o wide

There's a real argument for the systemd install instead, and it's the one thing I'd push back on in most tutorials. Falco running on the node lives outside the cluster it watches. Someone who gets control of the Kubernetes API can delete a DaemonSet; they can't as easily delete a service on a host they haven't reached yet. That said, the DaemonSet is far less work to run and upgrade, so I'd still start there unless the cluster is holding something that justifies the extra effort.

Paths worth memorising

Files and what they're for
PathWhat lives there
/etc/falco/falco.yamlMain config. Rule file list, output channels, log settings.
/etc/falco/config.d/Config fragments that override the main file. Added in 0.38, and the tidy way to keep local changes across upgrades.
/etc/falco/falco_rules.yamlThe shipped rules, lists and macros. Read it, never edit it, upgrades replace it.
/etc/falco/falco_rules.local.yamlYour rules and your overrides. This is the file you actually work in.
/etc/falco/rules.d/Drop-in directory for extra rule files. Handy when rules come from more than one source.
Order in the rules_files setting decides who wins, so your file goes after the defaults.

Rule anatomy

A rule needs five keys and won't load without them: rule is a short unique name, desc is a sentence saying what it catches, condition is the filter expression tested against each event, output is the alert text, and priority is the severity. Everything else is optional, including tags, enabled, source and exceptions.

Priority is severity, nothing more. It doesn't decide which rule fires first, and it's easy to read the name and assume otherwise. From loudest to quietest: EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG.

Two supporting pieces keep conditions readable. A macro is a named chunk of condition you reuse. A list is a named set of values you test against with in. Both ship in the defaults, so you rarely need to redefine them.

- list: shell_binaries
  items: [bash, csh, ksh, sh, tcsh, zsh, dash]

- macro: container
  condition: (container.id != host)

- rule: Shell spawned in a container
  desc: A shell process started inside a container
  condition: spawned_process and container and proc.name in (shell_binaries)
  output: Shell started (user=%user.name shell=%proc.name container=%container.id image=%container.image.repository)
  priority: WARNING

The bit worth knowing that older guides miss entirely: you don't copy a whole default rule into your local file to tweak it. Since 0.37 there's an override block that says which key you're changing and whether you're appending to it or replacing it.

# /etc/falco/falco_rules.local.yaml
- rule: Terminal shell in container
  priority: WARNING
  override:
    priority: replace

- list: shell_binaries
  items: [fish]
  override:
    items: append

The older append: true style still works but is deprecated and due for removal in Falco 1.0.0. One trap when appending to a condition: your text is glued straight onto the end of the original, so if that original mixes and with or without brackets, you can change its meaning without meaning to.

Where the alerts go

Every channel is a block in falco.yaml with an enabled flag. Standard output is on by default and everything else is opt-in.

stdout_output

On by default. What kubectl logs and journald pick up.

file_output

Append to a file. Falco never rotates it, so wire up logrotate.

syslog_output

Facility LOG_USER, and the rule priority becomes the syslog priority.

program_output

Pipes each alert into a command. One program only. This is the Slack webhook trick.

http_output

POST to a URL. Plain HTTP or valid HTTPS only, no self-signed certificates.

json_output

Not a channel. A flag that changes the format on all of them.

Most real setups turn on JSON and let Falcosidekick handle the fan-out rather than chaining shell commands.
json_output: true

program_output:
  enabled: true
  program: "jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/XXX"

Turn on json_output and each alert becomes one line of JSON carrying time, rule, priority, output, hostname, tags, source and an output_fields object with every interpolated field split out. That last one is what makes the alerts searchable later, and it's the reason I'd switch JSON on before I'd wire up any channel at all.

A plain-text alert looks like this, which is the one everybody sees first because it fires the moment you kubectl exec into anything:

journalctl -fu falco
10:20:05.408091526: Warning Sensitive file opened for reading by non-trusted program (user=root command=cat /etc/shadow file=/etc/shadow)
Expected output, not captured from my run. This example line comes from the Falco output docs.

The three that matter most

If you keep nothing else off this page: take the driver default and move on, because modern eBPF removes the entire kernel-headers problem that eats an afternoon. Write in falco_rules.local.yaml with an override block, never in the shipped rules file. And decide where alerts are going before you decide which rules to add, because a detection nobody reads is just CPU you paid for.

What I'd tell someone setting this up today: install it on a lab node first and leave it running for a day before you touch a single rule, because the volume of the defaults on a real workload is the part no tutorial prepares you for. And I'd rather see one custom rule that matches your own application's normal behaviour than fifty stock rules firing into a channel nobody has opened. If you're the person who'd get paged by these, the one thing worth checking tonight is where stdout_output is currently landing on your nodes, because on plenty of clusters the honest answer is nowhere. Happy to compare notes if you've had Falco running long enough to have tuned it, since I haven't yet and that's exactly the part I want to get wrong in a lab rather than in anger.

References

FAQ

Does Falco still need a kernel module?

No. The modern eBPF probe is the default driver and it is built into the Falco binary, so there is nothing to compile or download. It needs a kernel with BTF and BPF ring buffer support, usually 5.8 or newer. The kernel module is still supported and is the fallback for older kernels.

Should I run Falco as a DaemonSet or install it on the node?

The Helm DaemonSet is the normal path and it is far easier to run. Installing Falco on the node as a systemd service keeps it outside the cluster, so an attacker with control of the Kubernetes API cannot simply delete the thing watching them. Pick the DaemonSet unless you have a reason not to.

Where do I put custom Falco rules?

Put them in /etc/falco/falco_rules.local.yaml or a file under /etc/falco/rules.d/, never in falco_rules.yaml, which package upgrades overwrite. Load order matters: your file must be listed after the default rules file in the rules_files setting, or your changes will not apply.

Do I still need to send SIGHUP to reload Falco?

Not on current versions. The watch_config_files option is enabled by default, so Falco notices changes to its config and rules files and reloads on its own. The kill -1 on the PID file that older guides finish with is a leftover. If you disable the option, restart the service instead.

What is the difference between a Falco rule, a macro and a list?

A rule is the thing that fires an alert and needs five keys: rule, desc, condition, output and priority. A macro is a named piece of condition you reuse across rules. A list is a named set of values, like shell binary names, that a condition can test against with the in operator.