FreeBSD.software
Home/Guides/How to Implement VLANs on FreeBSD
tutorial·2026-03-29·19 min read

How to Implement VLANs on FreeBSD

Step-by-step guide to implementing VLANs on FreeBSD. Covers VLAN concepts, if_vlan interface creation, rc.conf configuration, inter-VLAN routing, trunk ports, PF firewall rules, and troubleshooting.

How to Implement VLANs on FreeBSD

VLANs let you carve one physical network into multiple isolated broadcast domains without buying more switches or running more cable. A single FreeBSD box with one NIC can serve as a router between ten VLANs, a DHCP server for each subnet, and a firewall controlling what traffic crosses between them. All through software.

FreeBSD has supported 802.1Q VLANs since the 4.x era through the if_vlan driver. The implementation is mature, stable, and deeply integrated with the rest of the networking stack -- PF, routing, VNET jails, and DHCP all work cleanly with VLAN interfaces. This guide covers everything from basic VLAN creation to production-grade multi-VLAN gateway configurations.

Every command and configuration example has been tested on FreeBSD 14.x. The syntax applies to 13.x as well.

Table of Contents

  1. VLAN Fundamentals
  2. FreeBSD VLAN Support
  3. Creating VLAN Interfaces
  4. Persistent Configuration in /etc/rc.conf
  5. Multiple VLANs on One Physical Interface
  6. Inter-VLAN Routing
  7. PF Firewall Rules Between VLANs
  8. VLAN and DHCP
  9. VLAN and Jails
  10. Switch Configuration Notes
  11. Troubleshooting
  12. FAQ

VLAN Fundamentals

A VLAN (Virtual Local Area Network) is a logical partition of a Layer 2 network. Devices in the same VLAN can communicate at Layer 2 as if they were on the same physical switch, even if they are on different physical switches. Devices in different VLANs cannot communicate without a Layer 3 router forwarding traffic between them.

802.1Q Tagging

The IEEE 802.1Q standard defines how VLAN membership is encoded in Ethernet frames. A 4-byte tag is inserted between the source MAC address and the EtherType field. The tag contains:

  • TPID (Tag Protocol Identifier) -- 0x8100, identifying the frame as 802.1Q tagged.
  • PCP (Priority Code Point) -- 3 bits for QoS priority (0-7).
  • DEI (Drop Eligible Indicator) -- 1 bit indicating the frame can be dropped under congestion.
  • VID (VLAN Identifier) -- 12 bits, giving a range of 1 to 4094 usable VLAN IDs. VLAN 0 is reserved for priority tagging and VLAN 4095 is reserved.

When a switch port is configured as a trunk (or tagged) port, it passes frames with 802.1Q tags intact. The receiving device -- another switch, a router, or a FreeBSD server -- reads the VLAN ID from the tag and delivers the frame to the correct VLAN interface.

When a switch port is configured as an access (or untagged) port, it strips the VLAN tag before delivering frames to the connected device. The end device never sees VLAN tags. Access ports belong to exactly one VLAN.

Trunk vs Access Ports

| Property | Trunk Port | Access Port |

|----------|-----------|-------------|

| Tagged frames | Yes, one or more VLANs | No |

| Untagged frames | Optional (native VLAN) | Yes (single VLAN) |

| Typical connection | Switch-to-switch, switch-to-router | Switch-to-endpoint |

| FreeBSD use case | Server handling multiple VLANs | Server on a single VLAN |

A FreeBSD box acting as a VLAN gateway needs a trunk port from the switch. The FreeBSD kernel handles the tag insertion and removal through the if_vlan driver.


FreeBSD VLAN Support

FreeBSD implements VLANs through the if_vlan kernel module. On a GENERIC kernel, the module is available but not loaded until you create your first VLAN interface. You can load it explicitly:

sh
kldload if_vlan

Verify it is loaded:

sh
kldstat | grep if_vlan

On FreeBSD 14.x with a GENERIC kernel, the module loads automatically when you create a VLAN interface with ifconfig. You do not need to add anything to /boot/loader.conf unless you are using a custom kernel that excluded VLAN support.

To confirm VLAN support is available in your kernel:

sh
sysctl net.link.vlan.mtag_pcp

If this returns a value (typically 0), VLAN support is present.

MTU Considerations

An 802.1Q tag adds 4 bytes to the Ethernet frame. Standard Ethernet has a 1500-byte MTU, and most modern NICs and switches handle the extra 4 bytes transparently -- they support "baby jumbo" frames of 1504 bytes. However, if you are running jumbo frames (MTU 9000), make sure the parent interface and all intermediate switches support the full frame size plus the 4-byte tag overhead.

VLAN interfaces inherit the parent interface's MTU by default. You can set a smaller MTU on the VLAN interface if needed, but you cannot exceed the parent's MTU.


Creating VLAN Interfaces

Creating a VLAN interface on FreeBSD takes one command. Suppose your physical interface is em0 and you want VLAN 10:

sh
ifconfig vlan10 create vlan 10 vlandev em0

This creates a new interface named vlan10 that sends and receives 802.1Q-tagged frames with VLAN ID 10 on the physical interface em0.

Assign an IP address:

sh
ifconfig vlan10 inet 10.0.10.1/24 up

Verify the interface:

sh
ifconfig vlan10

Output:

shell
vlan10: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500 options=4800b8<VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWTSO,VLAN_HWFILTER> ether 00:0c:29:xx:xx:xx inet 10.0.10.1 netmask 0xffffff00 broadcast 10.0.10.255 groups: vlan vlan: 10 vlanproto: 802.1q vlanpcp: 0 parent interface: em0 media: Ethernet autoselect status: active

Key things to check in the output:

  • VLAN_HWTAGGING -- the NIC handles tag insertion/removal in hardware. This is faster than software tagging.
  • parent interface: em0 -- confirms which physical interface carries the tagged traffic.
  • vlan: 10 -- confirms the VLAN ID.

Naming Conventions

FreeBSD lets you name VLAN interfaces however you like. Common conventions:

  • vlan10, vlan20, vlan30 -- simple, matches VLAN ID directly.
  • em0.10, em0.20 -- indicates the parent interface. FreeBSD supports this dot notation natively.

Using the dot notation:

sh
ifconfig em0.10 create vlan 10 vlandev em0 ifconfig em0.10 inet 10.0.10.1/24 up

Both approaches are functionally identical. The vlan10 convention is more common in FreeBSD documentation.

Destroying a VLAN Interface

sh
ifconfig vlan10 destroy

Persistent Configuration in /etc/rc.conf

Runtime ifconfig commands are lost on reboot. For persistent VLAN configuration, use /etc/rc.conf.

Single VLAN

sh
# /etc/rc.conf # Physical interface - no IP address, just bring it up ifconfig_em0="up" # Define VLANs on em0 vlans_em0="10" # Configure VLAN 10 ifconfig_em0_10="inet 10.0.10.1/24"

Multiple VLANs

sh
# /etc/rc.conf # Physical interface ifconfig_em0="up" # Define VLANs on em0 vlans_em0="10 20 30 100" # VLAN 10 - Management ifconfig_em0_10="inet 10.0.10.1/24" # VLAN 20 - Servers ifconfig_em0_20="inet 10.0.20.1/24" # VLAN 30 - Users ifconfig_em0_30="inet 10.0.30.1/24" # VLAN 100 - Guest network ifconfig_em0_100="inet 10.0.100.1/24"

The vlans_em0 variable tells FreeBSD's rc system to create VLAN interfaces on em0 at boot. Each ifconfig_em0_ line configures the corresponding VLAN interface.

Applying Changes Without Rebooting

After editing /etc/rc.conf, restart the network:

sh
service netif restart

Or bring up individual VLAN interfaces:

sh
service netif start em0.10

Be cautious with service netif restart on remote machines. If your SSH session runs through one of these VLANs, you will lose the connection temporarily.


Multiple VLANs on One Physical Interface

A single physical interface can carry traffic for dozens of VLANs simultaneously. This is the standard "router on a stick" design. The FreeBSD box connects to the switch via a trunk port. Each VLAN gets its own logical interface, its own IP subnet, and its own set of services.

Here is a complete /etc/rc.conf for a FreeBSD gateway handling five VLANs on a single NIC:

sh
# /etc/rc.conf - FreeBSD VLAN gateway hostname="gw01.example.com" # Physical interface - trunk to switch, no IP ifconfig_em0="up" # All VLANs on em0 vlans_em0="10 20 30 40 50" # VLAN 10 - Management (switches, APs, IPMI) ifconfig_em0_10="inet 10.0.10.1/24" # VLAN 20 - Server production ifconfig_em0_20="inet 10.0.20.1/24" # VLAN 30 - Server staging ifconfig_em0_30="inet 10.0.30.1/24" # VLAN 40 - Employee workstations ifconfig_em0_40="inet 10.0.40.1/24" # VLAN 50 - Guest WiFi (isolated) ifconfig_em0_50="inet 10.0.50.1/24" # Enable routing between interfaces gateway_enable="YES" # PF firewall pf_enable="YES" pf_rules="/etc/pf.conf" pflog_enable="YES"

This single FreeBSD machine now acts as the default gateway for five network segments. Devices on VLAN 40 use 10.0.40.1 as their gateway. Devices on VLAN 50 use 10.0.50.1. The kernel routes between them, and PF controls which traffic is allowed to cross.


Inter-VLAN Routing

By default, FreeBSD does not forward packets between interfaces. Devices on VLAN 10 cannot reach devices on VLAN 20 even though both VLAN interfaces exist on the same box. You must explicitly enable IP forwarding.

Enable Packet Forwarding

Persistent (in /etc/rc.conf):

sh
gateway_enable="YES"

Immediate (without reboot):

sh
sysctl net.inet.ip.forwarding=1

For IPv6:

sh
sysctl net.inet6.ip6.forwarding=1

Or in /etc/rc.conf:

sh
ipv6_gateway_enable="YES"

How It Works

Once forwarding is enabled, the FreeBSD kernel consults its routing table for every packet that arrives on one interface destined for a subnet on another interface. Because you have directly connected routes for each VLAN subnet, no static routes are needed for inter-VLAN traffic. The kernel already knows:

sh
netstat -rn
shell
Destination Gateway Flags Netif 10.0.10.0/24 link#2 U em0.10 10.0.20.0/24 link#3 U em0.20 10.0.30.0/24 link#4 U em0.30 10.0.40.0/24 link#5 U em0.40 10.0.50.0/24 link#6 U em0.50

A packet arriving on em0.40 (from 10.0.40.50) destined for 10.0.20.10 matches the 10.0.20.0/24 route and is forwarded out em0.20. At the Ethernet level, the FreeBSD box sends the packet with a VLAN 20 tag back out em0 on the trunk.

Controlling Inter-VLAN Access

Enabling forwarding routes all traffic between all VLANs. That is almost never what you want. The guest WiFi VLAN should not reach the server production VLAN. This is where PF comes in.


PF Firewall Rules Between VLANs

PF is the natural firewall for FreeBSD VLAN setups. It filters traffic on each VLAN interface individually, giving you precise control over what crosses between network segments. If you are new to PF, see the full PF firewall guide first.

Basic Inter-VLAN Filtering

Here is a /etc/pf.conf for the five-VLAN gateway described earlier:

shell
# /etc/pf.conf - VLAN gateway filtering # Macros trunk_if = "em0" mgmt_if = "em0.10" prod_if = "em0.20" stage_if = "em0.30" user_if = "em0.40" guest_if = "em0.50" mgmt_net = "10.0.10.0/24" prod_net = "10.0.20.0/24" stage_net = "10.0.30.0/24" user_net = "10.0.40.0/24" guest_net = "10.0.50.0/24" # Options set skip on lo0 set block-policy drop set loginterface $trunk_if # Scrub scrub in all # Default deny block log all # Allow traffic on management VLAN (full access) pass on $mgmt_if from $mgmt_net to any pass on $mgmt_if from any to $mgmt_net # Production servers: accept inbound from users, deny from guests pass on $prod_if from $user_net to $prod_net pass on $prod_if from $prod_net to any # Staging: only accessible from management pass on $stage_if from $mgmt_net to $stage_net pass on $stage_if from $stage_net to $mgmt_net # Users: can reach production and internet, not staging or management pass on $user_if from $user_net to $prod_net pass on $user_if from $user_net to ! { $mgmt_net $stage_net $guest_net } # Guest WiFi: internet only, no access to any internal VLAN pass on $guest_if from $guest_net to ! { $mgmt_net $prod_net $stage_net $user_net }

Key Concepts

Filter on the VLAN interface, not the trunk. PF sees packets after the kernel has stripped the VLAN tag and delivered them to the logical interface. Writing rules on em0.20 means you are filtering VLAN 20 traffic specifically.

Default deny is essential. Start with block log all and then open only what is needed. This ensures a new VLAN added later is isolated by default.

The guest VLAN rule uses negation to allow traffic to any destination except internal subnets. This lets guests reach the internet through a NAT rule (not shown) while blocking all lateral movement.

Load and Test

sh
pfctl -nf /etc/pf.conf # syntax check, do not load pfctl -f /etc/pf.conf # load rules pfctl -sr # show active rules

Monitor blocked traffic:

sh
tcpdump -n -e -ttt -i pflog0

VLAN and DHCP

Running a DHCP server per VLAN is the standard approach for dynamic IP assignment in a segmented network. FreeBSD's isc-dhcpd (or the newer Kea DHCP) can serve multiple subnets from a single configuration file. For a complete DHCP setup guide, see DHCP server on FreeBSD.

Install ISC DHCP Server

sh
pkg install isc-dhcp44-server

Configuration

Edit /usr/local/etc/dhcpd.conf:

shell
# Global settings option domain-name "example.com"; option domain-name-servers 10.0.10.5, 10.0.10.6; default-lease-time 3600; max-lease-time 7200; authoritative; # VLAN 20 - Server production (static assignments only) subnet 10.0.20.0 netmask 255.255.255.0 { option routers 10.0.20.1; option subnet-mask 255.255.255.0; deny unknown-clients; } # VLAN 40 - Employee workstations subnet 10.0.40.0 netmask 255.255.255.0 { range 10.0.40.100 10.0.40.250; option routers 10.0.40.1; option subnet-mask 255.255.255.0; option domain-name-servers 10.0.10.5; } # VLAN 50 - Guest WiFi subnet 10.0.50.0 netmask 255.255.255.0 { range 10.0.50.100 10.0.50.250; option routers 10.0.50.1; option subnet-mask 255.255.255.0; option domain-name-servers 1.1.1.1, 8.8.8.8; default-lease-time 1800; max-lease-time 3600; }

Enable in /etc/rc.conf

sh
dhcpd_enable="YES" dhcpd_ifaces="em0.20 em0.40 em0.50"

The dhcpd_ifaces variable tells the DHCP server which interfaces to listen on. It must list each VLAN interface that should serve DHCP. The server uses the interface's IP address and subnet to match incoming DISCOVER packets to the correct subnet block in the configuration.

Start the service:

sh
service isc-dhcpd start

VLAN and Jails

FreeBSD's VNET (virtual network stack) lets you assign a VLAN interface directly to a jail. The jail gets its own network stack -- its own routing table, its own ARP cache, and its own firewall rules. This is powerful for isolating services at both the network and process levels. For a full walkthrough on jails, see our FreeBSD jails guide.

Create a VLAN Interface for the Jail

First, create a VLAN interface and an epair to bridge it into the jail:

sh
# Create VLAN interface ifconfig vlan60 create vlan 60 vlandev em0 # Create an epair (virtual cable) ifconfig epair0 create # Create a bridge ifconfig bridge0 create ifconfig bridge0 addm vlan60 addm epair0a up # Bring up the interfaces ifconfig vlan60 up ifconfig epair0a up

jail.conf Configuration

shell
webapp { host.hostname = "webapp.example.com"; path = "/usr/local/jails/containers/webapp"; vnet; vnet.interface = "epair0b"; exec.prestart = "ifconfig epair0b up"; exec.start = "/bin/sh /etc/rc"; exec.stop = "/bin/sh /etc/rc.shutdown"; exec.poststop = "ifconfig epair0b -vnet webapp"; mount.devfs; allow.raw_sockets; }

Inside the jail, configure the network:

sh
jexec webapp ifconfig epair0b inet 10.0.60.10/24 jexec webapp route add default 10.0.60.1

Or set it persistently in the jail's /etc/rc.conf:

sh
ifconfig_epair0b="inet 10.0.60.10/24" defaultrouter="10.0.60.1"

Automating with rc.conf on the Host

For persistent VLAN-to-jail setups, add the VLAN, bridge, and epair configuration to the host's /etc/rc.conf:

sh
# VLAN for jail vlans_em0="10 20 30 40 50 60" ifconfig_em0_60="up" # Bridge for jail VLAN cloned_interfaces="bridge0 epair0" ifconfig_bridge0="addm em0.60 addm epair0a up" ifconfig_epair0a="up"

This creates a clean separation: VLAN 60 traffic arrives on the trunk, gets bridged to the epair, and the epair's b-side lives inside the jail. The jail is on VLAN 60 at the Ethernet level -- it sees ARP, broadcasts, and can run its own DHCP client if needed.


Switch Configuration Notes

FreeBSD handles the VLAN tagging on its end, but the switch port must be configured to match. If the switch port is not set to trunk mode (or at least tagged for the correct VLANs), tagged frames from FreeBSD will be dropped silently.

General Trunk Port Setup

On most managed switches, you need to:

  1. Set the port mode to trunk (sometimes called "tagged" or "802.1Q").
  2. Add the required VLANs to the trunk port's allowed VLAN list.
  3. Optionally set a native (untagged) VLAN for management traffic that should not be tagged.

Cisco IOS

shell
interface GigabitEthernet0/1 switchport mode trunk switchport trunk allowed vlan 10,20,30,40,50 switchport trunk native vlan 10

HP / Aruba ProCurve

shell
vlan 10 tagged 1 vlan 20 tagged 1 vlan 30 tagged 1

Juniper EX

shell
set interfaces ge-0/0/0 unit 0 family ethernet-switching port-mode trunk set interfaces ge-0/0/0 unit 0 family ethernet-switching vlan members [vlan10 vlan20 vlan30]

Unmanaged Switches

Unmanaged switches do not understand 802.1Q tags. Some pass tagged frames transparently (acting as a dumb hub for tagged traffic), but many drop oversized frames or strip tags unpredictably. Do not rely on unmanaged switches in a VLAN deployment.


Troubleshooting

tcpdump on VLAN Interfaces

The most useful diagnostic tool for VLAN problems is tcpdump. You can capture on both the parent interface and the VLAN interface.

Capture tagged traffic on the physical interface:

sh
tcpdump -n -e -i em0 vlan

The -e flag shows Ethernet headers including VLAN tags. You will see output like:

shell
00:0c:29:xx:xx:xx > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100), length 64: vlan 10, p 0, ethertype ARP

Capture traffic on a specific VLAN interface:

sh
tcpdump -n -i em0.10

This shows untagged traffic as seen by the VLAN interface -- the kernel has already removed the 802.1Q header.

Common Mistakes

1. Parent interface is down.

The physical interface must be UP for VLAN interfaces to work, even if it has no IP address assigned.

sh
ifconfig em0 up

2. Wrong VLAN ID.

Double-check that the VLAN ID in FreeBSD matches the VLAN ID configured on the switch trunk port. A mismatch means frames are tagged with a VLAN the switch does not expect, and they get dropped.

3. Switch port is in access mode.

If the switch port is set to access (untagged) mode, it will drop incoming tagged frames from FreeBSD. Set it to trunk mode.

4. MTU mismatch.

If you are using jumbo frames, ensure the parent interface, all switch ports in the path, and the VLAN interface all agree on MTU. A mismatch causes silent packet drops for large frames.

5. Missing gateway_enable.

You created VLAN interfaces and assigned IPs, but devices on different VLANs cannot talk to each other. Check that gateway_enable="YES" is set in /etc/rc.conf and that net.inet.ip.forwarding is 1.

sh
sysctl net.inet.ip.forwarding

6. PF blocking inter-VLAN traffic.

If PF is enabled with a default deny policy, you need explicit pass rules for inter-VLAN traffic. Check PF logs:

sh
tcpdump -n -e -ttt -i pflog0

7. Duplicate IP addresses.

Each VLAN interface must have an IP in a different subnet. Two VLAN interfaces with IPs in the same subnet will cause routing confusion.

Verifying VLAN Configuration

Quick checklist:

sh
# List all VLAN interfaces ifconfig -g vlan # Show VLAN details for a specific interface ifconfig em0.10 # Check the parent interface is up ifconfig em0 | grep -w UP # Verify forwarding is enabled sysctl net.inet.ip.forwarding # Check routes exist for each VLAN subnet netstat -rn | grep "10.0." # Test reachability from the gateway to a device on a VLAN ping -c 3 10.0.20.10

FAQ

How many VLANs can FreeBSD handle on a single interface?

The 802.1Q standard supports VLAN IDs 1 through 4094. FreeBSD can create interfaces for all of them on a single parent NIC. In practice, a few dozen VLANs is typical. The limiting factor is usually the switch and how many MAC addresses it can handle, not FreeBSD.

Does VLAN tagging reduce performance?

If your NIC supports hardware VLAN tagging (indicated by VLAN_HWTAGGING in ifconfig output), the performance impact is negligible. The NIC inserts and removes tags in hardware. Without hardware offload, the CPU handles tagging in software, which adds a small overhead -- still not significant on modern hardware unless you are pushing 10 Gbps or more.

Can I use VLANs with WiFi interfaces?

Generally no. Most WiFi drivers do not support 802.1Q tagging because wireless frames use a different header format. The standard approach is to bridge VLAN interfaces to WiFi through an access point that supports multiple SSIDs mapped to VLANs. The AP handles the translation between 802.11 and 802.1Q.

Do I need a separate physical interface for each VLAN?

No. That is the entire point of VLANs. A single physical interface connected to a switch trunk port can carry traffic for hundreds of VLANs. Each VLAN gets its own logical interface in FreeBSD. This "router on a stick" design is standard in small to medium networks.

How do I assign a VLAN to a jail without VNET?

Without VNET, jails share the host's network stack. You can assign a VLAN interface's IP to a jail using the ip4.addr parameter in jail.conf:

shell
myjail { ip4.addr = "em0.10|10.0.10.50/24"; ... }

This binds the jail to that IP on the VLAN interface. The jail can only use that address. However, VNET jails are preferred for stronger isolation because they get their own routing table, ARP cache, and firewall. See our FreeBSD jails guide for details.

Can I run PF rules inside a VNET jail on a VLAN?

Yes. A VNET jail has its own network stack, which includes its own PF instance. You can load PF inside the jail and apply rules independently of the host. This means each jail can have its own firewall policy tailored to the service it runs. The host's PF still filters traffic on the VLAN interface before it reaches the bridge/epair. Both layers apply.

What is the difference between if_vlan and if_lagg with VLANs?

if_lagg is for link aggregation -- bonding multiple physical NICs into one logical interface for redundancy or throughput. You can stack VLANs on top of a lagg interface. Create the lagg first, then create VLAN interfaces using the lagg as the parent:

sh
ifconfig lagg0 create laggproto lacp laggport em0 laggport em1 ifconfig vlan10 create vlan 10 vlandev lagg0

This gives you both link redundancy and VLAN segmentation. The /etc/rc.conf configuration follows the same pattern -- just replace em0 with lagg0 in the vlans_ and ifconfig_ variables.


Summary

VLANs on FreeBSD are straightforward to set up and powerful in production. The if_vlan driver integrates cleanly with the rest of the FreeBSD networking stack, and /etc/rc.conf makes persistent configuration simple. Combined with PF firewall rules for traffic control, a DHCP server for each subnet, and VNET jails for service isolation, a single FreeBSD box can serve as a complete network gateway for a segmented environment.

Start with two or three VLANs to separate your most critical traffic -- management, production, and guest access. Expand from there as your network architecture grows. The configuration patterns scale linearly, and FreeBSD handles the load without breaking a sweat.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.