skip to content

System: Using systemd to bind fail2ban to nftables

In the past we've used an init script to initialise our firewalls, orginally using iptables, and more recently with nftables.

This involved an init script including code to start and stop Fail2Ban when the firewall was stopped an started. But this is not the systemd way...

Configuring Fail2Ban to start/stop with nftables

What we're trying to achieve is the following:

  • on boot, the server starts first nftables.service and then fail2ban.service;
  • if we start nftables.service, it should also start fail2ban.service;
  • if we stop nftables.service, it should also stop fail2ban.service;
  • if we restart nftables.service, it should also restart fail2ban.service;
  • if we start fail2ban.service, it should also start nftables.service;
  • stop and restart actions on fail2ban.service should not affect nftables.service.

We can do all this by creating an override file for fail2ban telling it that it depends on nftables, and than nftables wants it to start:

/etc/systemd/system/fail2ban.service.d/override.conf

[Unit] Requires=nftables.service PartOf=nftables.service [Install] WantedBy=multi-user.target nftables.service

Because we've modified the [Install] section, we need to re-enable the affected service in order for the relevant symlinks to be created. We're also making sure that nftables is installed as a service unit first:

# systemctl enable nftables.service Created symlink /etc/systemd/system/sysinit.target.wants/nftables.service → /lib/systemd/system/nftables.service. # systemctl enable fail2ban.service Created symlink /etc/systemd/system/nftables.service.wants/fail2ban.service → /lib/systemd/system/fail2ban.service. # systemctl daemon-reload

The 'Created symlink' output with nftables.service.wants tells us that our changes have worked and that nftables now 'wants' fail2ban to be started when it does.

How does it work exactly?

In our override file, or full configuration file (see below), we include the following settings:

[Unit]

Requires=nftables.service
starting this service will first start nftables.service.
PartOf=nftables.service
causes this unit to stop or restart (but not start) with nftables.service.

[Install]

WantedBy=multi-user.target nftables.service
causes this unit to start when any of the listed services are started.

As you can see we need to set Requires, PartOf and WantedBy to get the desired behaviour. You can find more details under References below.

Alternative configuration approach

Systemd keeps its main configuration files under /usr/lib/systemd/system/* and these should not be edited. Local configuration can instead be set up under /etc/systemd/system/* where it will take precedence over the default configuration on a per-file or per-service basis.

In the above example we've created an 'override' configuration file for Fail2Ban binding it to nftables. This takes the original configuration and just adds or replaces the lines that appear in the override. This way an APT upgrade can still affect other settings.

An alternative is to create a whole new configuration file, which can be done using:

# systemctl edit --full fail2ban.service

This will open /usr/lib/systemd/system/fail2ban.service in a text editor which, when saved, will create an override file /etc/systemd/system/fail2ban.service. In this case any APT upgrade cannot affect your configuration.

Confirming that it's working

Starting with both nftables and Fail2Ban running:

# systemctl list-units --type=service | egrep fail2ban\|nftables fail2ban.service loaded active running Fail2Ban Service nftables.service loaded active exited nftables

Stopping nftables:

# systemctl stop nftables.service

causes both services to disappear:

# systemctl list-units --type=service | egrep fail2ban\|nftable (no output)

And starting Fail2Ban causes them to reappear as before:

# systemctl start fail2ban.service

We can also stop and start fail2ban without affecting nftables, assuming it's already active.

Building an nftables firewall

Sorry, but we're not going into any detail here. The point of the nftables service unit is to run the contents of /etc/nftables.conf when nftables is started, and to flush the ruleset when it's stopped.

You can find examples under /usr/share/doc/nftables/examples/.

Running another script when firewall starts

The /etc/nftables.conf file lets you define a static firewall to be brought up and taken down with the nftables.service unit, but in many situations you will want additional scripts to run to insert dynamic rules based on DNS lookups or other non-static variables.

Having a script run at boot time is simple enough, using CRON, and you can insert a delay sufficient to allow the firewall to come up first:

@reboot root sleep 10 && /usr/sbin/nft insert rule inet filter ...

But this only runs after a server reboot and not after a systemd action that restarts nftables.service. We can address this by instead creating another override file:

/etc/systemd/system/nftables.service.d/override.conf

[Service] ExecStartPost=/bin/sh -c '/usr/sbin/nft insert rule inet filter ...'

After creating this file, don't forget to run:

# systemctl daemon-reload

And then you can:

# systemctl restart nftables.service

There are other options for running scripts before the firewall starts rather than after. See under References below for details.

Gotchas

An update in Fail2Ban 0.11.2-3 appears to have moved the systemd fail2ban.service file from /lib/systemd/system/ to /usr/lib/systemd/system/ breaking some of the above sym-links.

References

< System

User Comments

Post your comment or question

3 November, 2022

Why not just easily and simply edit the jail.local file?!
See:
www.ruhnke.cloud/server/debian/fail2ban-von-iptables-zu-nftables/

16 March, 2021

This outlines how to set up the fail2ban and nftables services to be dependent on each other, but how do you get fail2ban to actually pass IP addresses to be blocked by nftables?

The short answer is that you just replace 'iptables' with 'nftables' where it appears in the Fail2Ban configuration files.

In particular, in "jail.conf:

banaction = nftables-multiport
banaction_allports = nftables-allports

top