Skip to content
Français
Conteneurs & Orchestration medium

Incus network ACLs: segmenting like Security Groups

15 min de lecture

Read this page in French

incus logo

Inside an OVN VPC, instances talk to each other freely by default. For a serious application you want the opposite: the web tier reaching the app tier, the app tier reaching the database, and the web tier never touching the database. Incus network ACLs play exactly the role of Security Groups: rules that reference one another and apply per instance. This guide shows how to segment a three-tier application, and takes stock of the default reject, long caught out and fixed since Incus 6.22. Tested on an Incus OS cluster. For people hardening a private cloud.

What you will learn

  • What an Incus network ACL is and how it relates to Security Groups.
  • Write rules that reference other ACLs (role-based segmentation).
  • Apply an ACL to an instance.
  • The real state of the default reject, depending on your Incus version.

Prerequisites

  • A working OVN network: see OVN in Incus. ACLs only apply to OVN networks.
  • Instances already running on that network (for example web1, api1, db1).

An Incus ACL is a Security Group

A network ACL is a set of ingress (incoming traffic) and egress rules that you attach to an instance. Its strength: a rule can name another ACL as its source or destination. Every instance carrying that ACL then forms a logical group, exactly like a Security Group referencing another one.

You create one group per tier:

Fenêtre de terminal
incus network acl create tier-web
incus network acl create tier-app
incus network acl create tier-db

Writing the segmentation rules

The app tier only accepts the web tier, the database only accepts the app tier. You express that with source pointing at the ACL of the upstream tier:

Fenêtre de terminal
incus network acl rule add tier-app ingress action=allow protocol=tcp source=tier-web destination_port=8080
incus network acl rule add tier-db ingress action=allow protocol=tcp source=tier-app destination_port=5432

The rule "allow port 8080 from tier-web" means: any instance carrying the tier-app ACL accepts port 8080 only from instances carrying tier-web. That is the Security Group mechanism.

Applying ACLs to instances

An ACL only takes effect once attached to the NIC of an instance. Since the eth0 device already exists (created by --network), you modify it with set rather than override:

Fenêtre de terminal
incus config device set web1 eth0 security.acls=tier-web \
security.acls.default.ingress.action=drop \
security.acls.default.egress.action=allow

Repeat for every instance: tier-web on the web tier, tier-app on the app tier, tier-db on the database. The security.acls.default.ingress.action=drop key sets a restrictive default policy: anything not explicitly allowed is blocked.

The default reject: a fixed bug, and what to keep from it

That default.ingress.action=drop should be enough to block the web tier from reaching the database. For a while it was not: on versions earlier than Incus 6.22, the implicit reject was not applied to east-west traffic, and the web tier reached the database when it should have been blocked. The cause was not in your rules but in the OVN programming of the default: the default egress action was set to allow, meaning without connection tracking, while any custom allow rule was itself translated into allow-related.

The defect has been fixed (lxc/incus#2851, PR #2985) by switching the default egress action to allow-related. The fix ships in Incus 6.22.0 and was backported to the 6.0 branch, so it is present in 6.0.6 LTS. Every supported version since March 2026, including 7.0 LTS, enforces the default reject correctly.

That said, the explicit drop rule keeps a value that depends on no bug at all. It writes the intent into the configuration instead of leaving it in an implicit default, it survives a change to default.ingress.action, and it reads back during an audit. This is the same logic as an explicit deny rule in a Security Group.

Fenêtre de terminal
incus network acl rule add tier-db ingress action=drop source=tier-web

An explicit rule applies immediately, with no instance restart.

Checking the segmentation

You test from each tier towards a target port (with a service listening, or /dev/tcp for a plain connection test):

Fenêtre de terminal
# from web1: web -> app must pass, web -> db must be blocked
incus exec web1 -- bash -c 'echo > /dev/tcp/10.151.218.4/8080' && echo OPEN || echo BLOCKED
incus exec web1 -- bash -c 'echo > /dev/tcp/10.151.218.6/5432' && echo OPEN || echo BLOCKED
# from api1: app -> db must pass
incus exec api1 -- bash -c 'echo > /dev/tcp/10.151.218.6/5432' && echo OPEN || echo BLOCKED
web1 -> app:8080 OPEN
web1 -> db:5432 BLOCKED
api1 -> db:5432 OPEN

The web tier reaches the app tier, the app tier reaches the database, but the web tier never touches the database. The three-tier application is segmented, the way it would be on a public cloud.

Key points

  • Incus network ACLs are the Security Groups of the OVN VPC; they work only on OVN.
  • A rule can reference another ACL as its source: that is the role-based group (web, app, db).
  • An ACL is applied by setting it on the NIC (security.acls through config device set).
  • The default reject has been fixed since Incus 6.22 (backported to 6.0.6 LTS); below that, add explicit drop rules, which read better in any case.
  • Rules apply immediately, with no instance restart.

Next steps

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn