BPDU Guard err-disables an access port the moment a BPDU arrives. Built in my home lab on three IOL-L2 switches, this lab proves it end to end: a Linux bridge with STP turned on sent an 802.1D BPDU into a PortFast access port, the switch logged %SPANTREE-2-BLOCK_BPDUGUARD and put the port in err-disabled, and shutdown/no shutdown brought it back once the BPDUs stopped.
The useful proof is the chain: a captured BPDU, a matching switch log, an err-disabled interface, and restored connectivity after repair. There is no broadcast flood and no physical loop in this test. Storm control was also on the plan, but both lab images rejected the commands, so this post treats it as untested. For the command options themselves, see the BPDU Guard configuration reference.
Lab topology: three IOL-L2 switches and a Linux bridge
I used three IOL-L2 switches and two Alpine containers: IOL-L2 17.18.2 and Alpine 3.24.2 under Containerlab. Use your own licensed Cisco images.

SW3 Et0/1 ── Et0/2 SW1 Et0/1 ── Et0/1 SW2 Et0/2 ── eth1 BR eth2 ── eth1 H1
10.47.147.3 10.47.147.1 protected edge 10.47.147.100
STP root VLAN 147 throughoutEvery switch link shown is an access link in VLAN 147. SW1 is the root; SW3 gives H1 a second reachable destination across the tree. BR has no lab IP address. Its eth0 management interface stays outside the bridge.
Deploy the topology with containerlab deploy -t <topology-file>, then open each switch CLI from the Containerlab VM using your usual console or SSH access. The steps below build the baseline used for every check in this post.
On all three switches, create VLAN 147 and select Rapid PVST+:
vlan 147
name BPDU-LAB
exit
spanning-tree mode rapid-pvstSet SW1 to spanning-tree vlan 147 priority 4096. SW2 and SW3 use priority 32768. Put SW1 Ethernet0/1 and Ethernet0/2, SW2 Ethernet0/1 and Ethernet0/2, and SW3 Ethernet0/1 into access VLAN 147:
switchport mode access
switchport access vlan 147
no shutdownOn SW1, give interface Vlan147 address 10.47.147.1 255.255.255.0; on SW3 use 10.47.147.3 255.255.255.0. Enable both SVIs with no shutdown.
Baseline: a PortFast edge port forwarding
In the VM shell, set BR and H1 to the Containerlab container names of the bridge and host nodes, then make the Alpine bridge with its STP process initially off:
BR=<bridge-container>
H1=<host-container>
docker exec "$BR" sh -c '
apk add --no-cache iproute2 tcpdump
ip link add br0 type bridge stp_state 0
ip link set eth1 master br0
ip link set eth2 master br0
ip link set eth1 up
ip link set eth2 up
ip link set br0 up
'
docker exec "$H1" sh -c '
ip addr add 10.47.147.100/24 dev eth1
ip link set eth1 up
'On SW2, configure the host-facing interface:
interface Ethernet0/2
description HOST-EDGE-VIA-BR
switchport mode access
switchport access vlan 147
spanning-tree portfast
no shutdownPortFast is appropriate here only while the attached segment behaves as an edge. Do not copy it onto the inter-switch links. Check the root and edge state with show spanning-tree vlan 147 and show spanning-tree interface Ethernet0/2 detail.
Observed excerpt from the converged SW2 baseline:
Et0/1 Root FWD 100 128.2 P2p
Et0/2 Desg FWD 100 128.3 P2p Edge From the VM shell, ping both SVIs:
docker exec "$H1" ping -c 5 -W 1 10.47.147.1
docker exec "$H1" ping -c 5 -W 1 10.47.147.3Both returned five replies from five requests. This establishes forwarding before the fault.
Enable BPDU Guard and turn on STP on the rogue bridge
At the SW2 CLI:
configure terminal
service timestamps log datetime msec
logging buffered 64000
interface Ethernet0/2
spanning-tree bpduguard enable
end
show spanning-tree interface Ethernet0/2 detail
show errdisable recoveryThe interface detail reported Bpdu guard is enabled. Automatic recovery for bpduguard was disabled in this run (see errdisable recovery for the timed option, which I did not test). Per-interface BPDU Guard and the global PortFast BPDU Guard default have different scope: the interface command works with or without PortFast, while the global spanning-tree portfast bpduguard default only covers ports in PortFast operational state. This walkthrough uses the explicit interface command. Cisco explains both in its PortFast and BPDU Guard guide.
Start a bounded capture from the VM shell, then enable STP on BR:
docker exec -d "$BR" sh -c '
timeout -s INT 18 tcpdump -U -i eth1 -s 0 -w /tmp/bpduguard.pcap \
ether dst 01:80:c2:00:00:00 > /tmp/capture.log 2>&1
'
docker exec "$BR" ip link set br0 type bridge stp_state 1BR now emits IEEE STP BPDUs. A second cable is unnecessary. This is a controlled way to introduce the rogue-bridge signal without creating a live loop.
Verify the err-disable: syslog, interface status and the BPDU capture
After several seconds, inspect SW2:
show interfaces status err-disabled
show interfaces Ethernet0/2
show logging | include BPDUGUARD|ERR_DISABLEThe settled-state output showed:
Et0/2 HOST-EDGE-VI err-disabled bpduguard
Ethernet0/2 is down, line protocol is down (err-disabled) The first trigger produced these log lines:
*Oct 21 18:31:47.507: %SPANTREE-2-BLOCK_BPDUGUARD: Received BPDU from bridge aac1.ab35.e69e on port Et0/2 with BPDU Guard enabled. Disabling port.
*Oct 21 18:31:47.507: %PM-4-ERR_DISABLE: bpduguard error detected on Et0/2, putting Et0/2 in err-disable stateRead the capture after its 18-second timeout:
docker exec "$BR" tcpdump -nn -e -vv -r /tmp/bpduguard.pcapThe capture contains an 802.1D configuration BPDU at 18:31:47.506723 (UTC, like the switch log), with bridge ID 8000.aa:c1:ab:35:e6:9e.8001. Its bridge MAC matches the switch log. The Ethernet source address belongs to the transmitting bridge port and differs from that bridge ID; compare the BPDU payload when checking the match. (For the field itself, see how a BPDU carries the bridge ID.)
H1 lost all five pings to SW1 in the first fault check. Ping loss alone would not prove BPDU Guard: enabling Linux STP can also delay bridge forwarding. The switch's err-disable reason and matching BPDU capture establish the cause.
One timing detail matters: our first status query arrived just before the first BPDU. It briefly showed the port up, followed by the guard log. Repeating the same trigger and checking after a delay produced the settled err-disabled output above. A single early query can miss the transition.
Recover the err-disabled port with shutdown/no shutdown
For this test, return BR to its original STP-disabled transit state from the VM shell:
docker exec "$BR" ip link set br0 type bridge stp_state 0In a real incident, remove or correct the unexpected bridge before restoring the port. Here we keep the transit bridge solely to verify the original host path again; switching off its STP process is not a general recommendation for network design.
At the SW2 CLI:
configure terminal
interface Ethernet0/2
shutdown
no shutdown
end
show spanning-tree interface Ethernet0/2 detail
show interfaces statusBPDU Guard remained configured. H1 again received five replies from five requests to both SW1 and SW3. The recovery used manual shutdown / no shutdown; timed automatic recovery was not tested.
BPDU Guard is the guard that err-disables. Root Guard and Loop Guard block the port instead of err-disabling it.
Storm control: rejected on IOL-L2 and IOSv-L2, so untested here
The existing storm-control article already says its virtual IOL-L2 nodes reject storm control. This lab reproduced that parser limitation on IOL-L2 17.18.2:
SW2(config-if)#
storm-control broadcast level 1.00 0.50
^
% Invalid input detected at '^' marker.This is the exact parser output. Percentage, pps and bps forms, action commands, and storm-control verification commands were also rejected. The parser rejected these commands on this image, so storm control is untested here. A parser rejection does not measure broadcast suppression.
The IOSv-L2 fallback (vrnetlab/cisco_vios:L2-20200929, reporting experimental IOS 15.2) rejected the same configuration and show commands with the same % Invalid input detected error. That IOSv-L2 run was only a parser comparison; I did not repeat the BPDU Guard test on it. Storm-control enforcement, rate thresholds and storm counters are untested. No flood-rate or suppression numbers are claimed.
Do not generalize “no physical ASIC” into a guarantee about every virtual platform. Cisco describes Catalyst 9000v as a beta image with software dataplane emulation. That description alone does not verify storm-control enforcement on a particular release.
BPDU Guard gave us a useful, repeatable access-port protection test on this virtual image. The evidence supports that result; it leaves storm-control enforcement for a separately verified platform. For the rest of the access-edge controls, work through the Layer 2 hardening checklist, and for the sister err-disable lab, try the port security vs MAC flooding lab.