Skip to content

Port Trunking

Port trunking (also known as VLAN-aware VMs) allows a single instance network interface to reach several private networks at once. Instead of attaching one vNIC per network, a trunk binds a parent port (carrying the untagged traffic) with subports, each mapped to a VLAN segmentation ID. The instance then creates standard 802.1Q VLAN sub-interfaces on its trunked NIC, one per tag.

Typical use cases are network appliances (firewalls, routers) that must sit on several segments at the same time, or workloads that need to reach multiple isolated networks without multiplying virtual interfaces.

Networks and Subnets OpenStack trunk documentation

Note

Port trunking is intended for use with your own private networks. Trunks are covered by a dedicated quota (see Quotas).

How it works

  •   Parent port


    A regular port on a private network. It carries the untagged traffic and is the only port attached to the instance.

  •   Subports


    Existing ports on other private networks, bound to the trunk together with a VLAN tag. They are never attached to an instance directly.

  •   Trunk


    The object that binds the parent port to its subports. Created once with openstack network trunk create, it is the only trunk resource to manage.

  •   Segmentation ID


    The VLAN tag carried by the frames inside the instance. Each tag maps to exactly one subport, and is independent of the underlying network technology.

Security groups apply at port level: the parent port and each subport keep their own rules.

graph LR
    A["Instance<br>vm-trunk"] -->|"untagged"| B["Parent port<br>p-parent"]
    B --> C{"Trunk<br>trunk0"}
    C -->|"VLAN 100"| D["Subport<br>p-v100"]
    C -->|"VLAN 200"| E["Subport<br>p-v200"]
    D --> F["net-v100<br>10.100.0.0/24"]
    E --> G["net-v200<br>10.200.0.0/24"]
    F -->|"untagged"| H["Instance<br>vm-witness"]

Warning

Subports must remain free of any device: do not attach them to an instance, they are only referenced by the trunk. Attaching the subport directly would conflict with the trunk mapping.

Example: multiple VLANs through a single interface

The following walkthrough was validated on the Public Cloud: an instance vm-trunk reaches two additional networks, net-v100 and net-v200, through its single interface on net-parent, and pings a witness instance connected to net-v100.

Resource Network Subnet VLAN tag
Parent port p-parent net-parent 10.10.0.0/24 - (untagged)
Subport p-v100 net-v100 10.100.0.0/24 100
Subport p-v200 net-v200 10.200.0.0/24 200
Witness port p-witness net-v100 10.100.0.0/24 - (untagged, regular port)

Prerequisites

  • The OpenStack CLI is installed and authenticated.
  • An SSH key pair exists.
  • A router provides connectivity to the parent subnet if you plan to reach the instance over SSH (see Floating IPs).

1. Create the networks and subnets

Create the parent network and one network per VLAN:

networks and subnets
openstack network create net-parent
openstack subnet create --network net-parent --subnet-range 10.10.0.0/24 sub-parent
openstack network create net-v100
openstack subnet create --network net-v100 --subnet-range 10.100.0.0/24 sub-v100
openstack network create net-v200
openstack subnet create --network net-v200 --subnet-range 10.200.0.0/24 sub-v200
Example output for the parent network
ik@laptop:~$ openstack network create net-parent
+---------------------------+--------------------------------------+
| Field                     | Value                                |
+---------------------------+--------------------------------------+
| admin_state_up            | UP                                   |
| availability_zone_hints   | az-1, az-2, az-3                     |
| availability_zones        |                                      |
| created_at                | 2026-10-05T13:06:17Z                 |
| description               |                                      |
| dns_domain                |                                      |
| id                        | 2996c18e-babe-4012-b509-cdc9bd51d737 |
| ipv4_address_scope        | None                                 |
| ipv6_address_scope        | None                                 |
| is_default                | False                                |
| is_vlan_qinq              | None                                 |
| is_vlan_transparent       | None                                 |
| l2_adjacency              | True                                 |
| mtu                       | 1500                                 |
| name                      | net-parent                           |
| port_security_enabled     | True                                 |
| project_id                | ac4fafd60021431585bbb23470119557     |
| provider:network_type     | None                                 |
| provider:physical_network | None                                 |
| provider:segmentation_id  | None                                 |
| pvlan                     | None                                 |
| qos_policy_id             | None                                 |
| revision_number           | 1                                    |
| router:external           | Internal                             |
| segments                  | None                                 |
| shared                    | False                                |
| status                    | ACTIVE                               |
| subnets                   |                                      |
| tags                      |                                      |
| updated_at                | 2026-10-05T13:06:17Z                 |
+---------------------------+--------------------------------------+

2. Create a security group

Create a security group and authorize the traffic you need. Here, ICMP and SSH are allowed, and the group will be applied to every port of the example:

security group
openstack security group create sg-trunk-test
openstack security group rule create --protocol icmp sg-trunk-test
openstack security group rule create --protocol tcp --dst-port 22 sg-trunk-test

3. Create the ports

Create the parent port and one port per VLAN. The subports get fixed IP addresses, which will be configured statically inside the instance later:

ports
openstack port create --network net-parent --security-group sg-trunk-test p-parent
openstack port create --network net-v100 --fixed-ip ip-address=10.100.0.10 --security-group sg-trunk-test p-v100
openstack port create --network net-v200 --fixed-ip ip-address=10.200.0.10 --security-group sg-trunk-test p-v200
Port Fixed IP MAC address Role
p-parent 10.10.0.70 fa:16:3e:10:fb:e8 Parent port, attached to the instance
p-v100 10.100.0.10 fa:16:3e:57:69:25 Subport, VLAN 100
p-v200 10.200.0.10 fa:16:3e:ee:e0:13 Subport, VLAN 200

Note

The MAC address of each subport is needed to configure the instance in step 6. It can be retrieved at any time with openstack port show <port name> -f value -c mac_address.

4. Create the trunk

trunk
openstack network trunk create \
  --parent-port p-parent \
  --subport port=p-v100,segmentation-type=vlan,segmentation-id=100 \
  --subport port=p-v200,segmentation-type=vlan,segmentation-id=200 \
  trunk0
output
+-------------------+-------------------------------------------------------------------------------------------------+
| Field             | Value                                                                                           |
+-------------------+-------------------------------------------------------------------------------------------------+
| created_at        | 2026-10-05T13:08:42Z                                                                            |
| description       |                                                                                                 |
| id                | fe15f729-02a3-4e16-bca9-361d81b8d369                                                            |
| is_admin_state_up | True                                                                                            |
| name              | trunk0                                                                                          |
| port_id           | df2c0f44-79cd-4b70-ab60-5ffa21eba807                                                            |
| project_id        | ac4fafd60021431585bbb23470119557                                                                |
| revision_number   | 0                                                                                               |
| status            | DOWN                                                                                            |
| sub_ports         | port_id='5210e7eb-2a3d-4b9d-bfd1-4fef980206ba', segmentation_id='100', segmentation_type='vlan' |
|                   | port_id='e416e3b8-fdb6-4302-921c-f622bb2f7dd4', segmentation_id='200', segmentation_type='vlan' |
| tags              | []                                                                                              |
| updated_at        | 2026-10-05T13:08:42Z                                                                            |
+-------------------+-------------------------------------------------------------------------------------------------+

5. Boot the instance on the parent port

Use --port so the instance attaches to the pre-created parent port instead of getting an auto-generated one:

instances
openstack server create \
  --image "Debian 13 trixie" \
  --flavor a2-ram4-disk80-perf1 \
  --key-name my-keypair \
  --port p-parent \
  vm-trunk

# witness instance used to validate connectivity
openstack port create --network net-v100 --fixed-ip ip-address=10.100.0.20 --security-group sg-trunk-test p-witness
openstack server create \
  --image "Debian 13 trixie" \
  --flavor a2-ram4-disk80-perf1 \
  --key-name my-keypair \
  --port p-witness \
  vm-witness

The witness instance is a regular instance attached to net-v100 with a standard port: it needs no trunk and no VLAN configuration on its side. To reach the instance over SSH, attach a floating IP to the parent port as usual.

6. Configure the VLAN sub-interfaces inside the instance

Connect to the instance and identify the interface connected to the parent network. Its name depends on the image (here enp3s0) and it received an IP address on net-parent by DHCP:

inside the instance
debian@vm-trunk:~$ ip -br a
lo               UNKNOWN        127.0.0.1/8 ::1/128
enp3s0           UP             10.10.0.70/24 metric 100 fe80::f816:3eff:fe10:fbe8/64

Retrieve the MAC address of each subport:

on your workstation
ik@laptop:~$ openstack port show p-v100 -f value -c mac_address
fa:16:3e:57:69:25
ik@laptop:~$ openstack port show p-v200 -f value -c mac_address
fa:16:3e:ee:e0:13

Warning

Port security only accepts frames whose source MAC address belongs to the port. Clone the MAC address of each subport onto its VLAN sub-interface (address=...), otherwise the tagged frames are dropped. As an alternative, allow the instance MAC address on the subport with --allowed-address-pairs or disable port security on the subports.

Create one VLAN sub-interface per subport, with the tag matching the segmentation-id:

create VLAN sub-interfaces
sudo ip link add link enp3s0 name enp3s0.100 address fa:16:3e:57:69:25 type vlan id 100
sudo ip link add link enp3s0 name enp3s0.200 address fa:16:3e:ee:e0:13 type vlan id 200
  • link enp3s0: the physical interface identified above.
  • name enp3s0.100: the new sub-interface, named after its tag by convention.
  • address fa:16:3e:...: the MAC address of the matching subport, cloned as explained in the warning above.
  • type vlan id 100: the VLAN tag, identical to the segmentation-id of the subport.

Note

The DHCP service is not reachable through the tagged sub-interfaces of a trunk: the IP addresses must be configured statically, using the fixed IPs of the subports.

addresses and state
sudo ip addr add 10.100.0.10/24 dev enp3s0.100
sudo ip addr add 10.200.0.10/24 dev enp3s0.200
sudo ip link set enp3s0.100 up
sudo ip link set enp3s0.200 up

Tip

On the image used for this validation (Debian 13), the 8021q kernel module was already loaded and ip link add ... type vlan worked directly. If the command fails, load the module first with sudo modprobe 8021q. These commands are not persistent across reboots: refer to your distribution documentation (netplan, systemd-networkd, ifcfg...) to make the configuration permanent.

Check the result:

inside the instance
debian@vm-trunk:~$ ip -br a
lo               UNKNOWN        127.0.0.1/8 ::1/128
enp3s0           UP             10.10.0.70/24 metric 100 fe80::f816:3eff:fe10:fbe8/64
enp3s0.100@enp3s0 UP             10.100.0.10/24 fe80::f816:3eff:fe57:6925/64
enp3s0.200@enp3s0 UP             10.200.0.10/24 fe80::f816:3eff:feee:e013/64
debian@vm-trunk:~$ ip r
default via 10.10.0.1 dev enp3s0 proto dhcp src 10.10.0.70 metric 100
10.10.0.0/24 dev enp3s0 proto kernel scope link src 10.10.0.70 metric 100
10.100.0.0/24 dev enp3s0.100 proto kernel scope link src 10.100.0.10
10.200.0.0/24 dev enp3s0.200 proto kernel scope link src 10.200.0.10
169.254.169.254 via 10.10.0.2 dev enp3s0 proto dhcp src 10.10.0.70 metric 100

7. Validate connectivity

The instance now reaches the VLAN 100 network. The witness instance requires no particular configuration: as a regular instance attached to the network, it is reachable without any VLAN setup. The validation ping below succeeded between two instances in different availability zones (az-3 to az-2):

inside the instance
debian@vm-trunk:~$ ping -c3 10.100.0.20
PING 10.100.0.20 (10.100.0.20) 56(84) bytes of data.
64 bytes from 10.100.0.20: icmp_seq=1 ttl=64 time=7.46 ms
64 bytes from 10.100.0.20: icmp_seq=2 ttl=64 time=1.31 ms
64 bytes from 10.100.0.20: icmp_seq=3 ttl=64 time=1.30 ms

--- 10.100.0.20 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 1.303/3.356/7.457/2.855 ms

Success

The instance reaches all the networks attached to the trunk through a single interface.

Manage subports

Subports can be listed, added or removed on an existing trunk:

manage subports
openstack network trunk show trunk0
openstack network trunk set --subport port=p-v300,segmentation-type=vlan,segmentation-id=300 trunk0
openstack network trunk unset --subport p-v300 trunk0

Note

A subport added to a trunk must be a free port (not attached to any device), and its VLAN tag must match the tag configured on the instance sub-interface.

Clean up

Delete the resources in reverse order. The trunk must be deleted before its ports:

clean up
openstack server delete vm-trunk vm-witness
openstack network trunk delete trunk0
openstack port delete p-parent p-v100 p-v200 p-witness
openstack subnet delete sub-parent sub-v100 sub-v200
openstack network delete net-parent net-v100 net-v200
  •   Networks and Subnets


    Create the private networks and subnets used by the parent port and the subports.

  •   Security Groups


    Firewall rules applied at port level: the parent port and each subport keep their own rules.

  •   Create and assign a Floating IP


    Expose the trunked instance to the internet through its parent port for SSH or remote administration.

  •   OPNsense template


    Deploy a firewall appliance on the Public Cloud: port trunking lets a single appliance reach several network segments.