Skip to content
Ops Log
Operator RunbookSecurity & Infrastructure28 July 202616 min read

Network segmentation for a 100 to 500 seat office: VLANs, policy as code and the identity overlay

A cutover runbook for turning a flat office LAN into purpose-based zones: the VLAN scheme, the 802.1X and RADIUS layer that actually places a device into a zone, firewall policy held in version control, host-level nftables, and an independent rollback path for every stage.

MJ
Michal Jatczak
Founder, ITSailor

This runbook cuts a flat office LAN into purpose-based zones and keeps the policy defensible afterwards. It covers the VLAN scheme, the 802.1X layer that actually places a device into a zone, firewall policy held in version control, host-level nftables on high-value workloads, and a rollback path for every stage of the cutover.

What a flat network costs, as a number you compute

On a flat network every device reaches every other device. The receptionist's laptop can open a socket to the production database, and a contractor who plugs into a wall port inherits the same reach as a domain admin's workstation, minus the credentials.

Compute the blast radius from your own inventory. Two levers:

  1. Reachable hosts. R_flat = live IP endpoints in the broadcast domain, minus one. R_seg = (endpoints in the compromised device's own zone, minus one) + (destination hosts named in allow rules that apply from that zone). Reduction = 1 - R_seg / R_flat.
  2. Reachable ports. Multiply each reachable host by the destination ports the policy permits from that zone. A flat network scores the full 65,535 per host. A segmented one scores whatever the allow-list names, usually a single digit.

Take a worked example. A 300 seat office runs 900 live endpoints on one 10.0.0.0/16, so R_flat = 900 - 1 = 899 hosts reachable from a compromised laptop. After a ten-zone cut where the user zone holds 300 laptops and its allow-list names six application servers on 443 and four printers on 631 and 9100, R_seg = (300 - 1) + 6 + 4 = 309. The reduction is 899 - 309 = 590 hosts, which is 590 / 899 = 65.6 percent on the first lever alone. Substitute your own endpoint count and allow-list for your own figure.

Prerequisites

  • Two to four weeks of NetFlow or sFlow export from the existing core, so the allow-list is derived from observed flows rather than guesswork.
  • Access switches that support 802.1X, MAC Authentication Bypass, DHCP snooping, dynamic ARP inspection and IP source guard.
  • A RADIUS server bound to the directory: Windows NPS, FreeRADIUS or an equivalent, with the ability to return per-group tunnel attributes.
  • A firewall sized for inter-VLAN routing at the throughput the office actually pushes internally, which is usually far higher than its internet link.
  • Out-of-band access to firewall and core switch, on a path that does not traverse the policy you are about to change.
  • Exported, restore-tested configuration backups of every device in scope.
  • A named owner and a review date recorded for every allow rule before the rule is written.

Step 1: purpose-based VLAN zones

Segment by purpose. A floor-based scheme puts a badge reader and a finance laptop in the same trust boundary, which is exactly the adjacency the project exists to remove.

VLANPurposeTrust posture
10 ManagementSwitch, firewall and hypervisor management interfacesHighest. Reachable from the bastion only, every session logged
20 UserEmployee laptops and desktopsStandard user trust
30 VoiceIP phones, room systems, conferencing endpointsVoice-specific reach only
40 ServerOn-premises servers, NAS, application back-endsHigh. Locked and audited
50 PrinterMultifunction devices, label printersPrint protocols only, no internet egress
60 IoTBuilding systems, cameras, badge readers, room sensorsLow. Named external destinations only
70 LabEngineering benches, test rigs, sandboxLow, treated as hostile to the corporate zones
80 ContractorTime-bounded vendor accessLow, narrow allow-list with an expiry
90 GuestVisitor Wi-FiLowest. Internet only
99 QuarantineDevices that fail authentication or postureCaptive portal and remediation servers only

The default on inter-VLAN routing is deny, with logging. Everything else is an explicit, named rule.

Step 2: the enforcement layer that puts a device into a zone

Static port assignments make a VLAN scheme a labelling exercise. 802.1X with RADIUS-assigned dynamic VLANs is what places a device into a zone at the moment it connects. RFC 3580 specifies that the RADIUS Access-Accept must carry three attributes together for VLAN assignment to work: Tunnel-Type=VLAN (13), Tunnel-Medium-Type=802, and Tunnel-Private-Group-ID holding the VLAN ID as a string in the range 1 to 4094. Return two of the three and the switch silently ignores the assignment.

ini
# FreeRADIUS: managed laptops land in VLAN 20 (User)
DEFAULT Ldap-Group == "corp-managed-laptops"
        Tunnel-Type = VLAN,
        Tunnel-Medium-Type = IEEE-802,
        Tunnel-Private-Group-Id = "20"

# MAC Authentication Bypass for a printer fleet, into VLAN 50 (Printer).
# MAB places a device. It does not authenticate one: a MAC address is
# trivially spoofed, so pair it with IP source guard and a tight allow-list.
#
# Calling-Station-Id formatting is vendor-specific: 00:1B:A9 on some
# platforms, 00-1B-A9 on others, 001b.a900.0000 on Cisco IOS, and no
# separator at all on a few. Capture one real Access-Request with
# radiusd -X before writing this regex, or normalise the attribute in
# policy first. A pattern that matches nothing sends the entire printer
# fleet to VLAN 99 on cutover night.
DEFAULT Calling-Station-Id =~ "^00:1B:A9", Auth-Type := Accept
        Tunnel-Type = VLAN,
        Tunnel-Medium-Type = IEEE-802,
        Tunnel-Private-Group-Id = "50"

# Anything that fails both 802.1X and MAB falls through to the switch's
# configured auth-fail VLAN, which is 99 (Quarantine).

Without this layer, VLAN 99 is decorative and an unplugged desk phone hands its port's voice VLAN to whoever plugs in next.

Step 3: the allow-list matrix

FromToAllowedReason
User 20Server 40443 to named application hostsApplication access
User 20Printer 50631 and 9100 to named devicesPrinting
User 20Voice 30SIP and RTP to the call-control host onlySoftphones
Voice 30SBC or provider media rangesSIP signalling and the negotiated RTP rangeCall media
Server 40Server 40Named app-to-app portsApplication stacks
Server 40Internet443 outbound plus named NTP and DNSUpdates and API calls
Server 40User 20Default deny, with named exceptions belowSessions should originate at the client
IoT 60Named vendor endpoints443 to the vendor's published ranges, NTP, DNSFirmware and telemetry
IoT 60Any internal zoneDenyNo corporate adjacency
Management 10AnyFrom the bastion only, session loggedOperations
Guest 90Internet80, 443, DNS, ICMPVisitor browsing
Guest 90Any internal zoneDenyGuest isolation

The server-to-user row is where absolutes break real estates. Configuration Manager client push (TCP 445 plus RPC), print-server callbacks, and backup and EDR agent push all originate at the server. Write those as narrow rules from named management hosts, never as a general server-to-user path.

Two cross-zone dependencies need more than a firewall rule. PXE is client-initiated: the client broadcasts DHCPDISCOVER carrying the PXEClient option, so its first packet never leaves the VLAN and no allow rule can carry it. Crossing zones needs a DHCP relay, ip helper-address pointing at the PXE service point, with options 66 and 67 set, plus UDP 69 and UDP 4011 permitted from the boot server into the client zone. Wake-on-LAN sent as a directed broadcast (UDP 9) needs ip directed-broadcast enabled on the destination SVI, which is off by default on current platforms; a WoL relay agent inside the target zone avoids re-enabling it.

Step 4: firewall policy as code

The policy lives in git and is generated into the device configuration. Every rule carries an owner and a review date, and tooling drops rules whose review date has passed.

yaml
# segmentation/policy.yaml
policies:
  - name: user-to-app
    source: vlan-20-user
    owner: platform-ops
    destinations:
      - host: app-server-1
        ports: [443]
      - host: app-server-2
        ports: [443]
    log: true
    review_date: 2026-10-15

  - name: voice-media
    source: vlan-30-voice
    owner: platform-ops
    destinations:
      - host: sbc-01.internal      # media terminates on the on-premises SBC
        ports: [10000-20000]
        protocols: [udp]
    # If there is no SBC, replace the entry above with the SIP provider's
    # published media subnets. Never 0.0.0.0/0: that is 10,001 open UDP
    # ports to the whole internet inside a default-deny policy.
    log: false
    review_date: 2026-10-15

  - name: iot-vendor-cloud
    source: vlan-60-iot
    owner: facilities-it
    destinations:
      # FQDN objects resolve through the firewall's own resolver and follow
      # DNS TTL. Against a CDN-fronted vendor cloud the resolved set drifts
      # and the rule can fail closed without warning. Prefer the vendor's
      # published IP ranges where they publish them.
      - fqdn: vendor-cloud.example.com
        resolver: internal-recursive
        min_ttl_seconds: 300
        ports: [443]
    log: true
    review_date: 2027-01-15

default:
  action: deny
  log: true

Step 5: host-level microsegmentation on high-value workloads

Zone policy stops at the L3 boundary. A compromised application server sits inside the server zone with the database. The host firewall is what stops the next hop.

bash
# /etc/nftables.d/db-server.nft   loaded atomically: nft -f /etc/nftables.d/db-server.nft
#
# Reset only this table. Never "flush ruleset" on a host that also runs
# Docker, kube-proxy, libvirt or fail2ban: that destroys every table in
# every family and silently breaks container networking.
table inet seg_db { }
delete table inet seg_db

table inet seg_db {
  chain input {
    type filter hook input priority 0; policy drop;

    iif "lo" accept
    ct state established,related accept
    ct state invalid drop

    # Path MTU discovery and IPv6 Neighbour Discovery. Dropping these produces
    # intermittent hangs on large transfers rather than clean failures.
    ip protocol icmp icmp type { destination-unreachable, echo-request, time-exceeded } accept
    meta l4proto ipv6-icmp icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, nd-neighbor-solicit, nd-neighbor-advert } accept

    # SSH from the bastion only
    ip saddr 10.10.0.5/32 tcp dport 22 accept

    # Postgres from the three named application servers only
    ip saddr { 10.40.0.10, 10.40.0.11, 10.40.0.12 } tcp dport 5432 accept

    # Metrics scrape
    ip saddr 10.10.0.20/32 tcp dport 9100 accept

    # Sample the drops for forensics. The drop itself comes from "policy drop"
    # on the chain, not from this rule: "limit rate" is a match, so this line
    # only logs the first ten packets a minute.
    limit rate 10/minute counter log prefix "seg_db-drop: "
  }

  chain forward { type filter hook forward priority 0; policy drop; }
}

Step 6: the identity overlay

Network zones answer where a packet came from. They do not answer who is behind it or whether the device is healthy. An identity overlay publishes internal applications through a broker that evaluates user identity from the IdP, device compliance state, posture and sign-in context before a session is established. Microsoft documents the mechanism for its own stack as Intune compliance policies feeding Conditional Access: a policy set to require a compliant device blocks access from any device Intune has marked non-compliant. Check one tenant-wide setting before treating that as enforcement. Microsoft Learn states that Mark devices with no compliance policy assigned as defaults to Compliant, so a device that was never assigned a policy passes the grant control untouched, and the same page instructs you to set it to Not compliant when Conditional Access consumes compliance state.

One procurement correction. Tailscale's free Personal plan is capped at six users on its published pricing page, so it does not cover a 100 to 500 seat office, and the paid Standard tier is billed per user per month. Jurisdiction is worth settling before the DPA. Tailscale's terms name two contracting entities: accounts created before 3 September 2024 contract with Tailscale Inc., a Canadian corporation at 100 King Street West in Toronto, and accounts created from that date contract with Tailscale US Inc., a Delaware corporation, under New York governing law. A new EU buyer therefore lands on the US entity, which changes the transfer mechanism the DPA has to name. Confirm the entity printed on the order form, and confirm where the coordination server and its logs sit.

On a Microsoft-centric estate the identity half of this work overlaps heavily with Microsoft 365 tenant hardening, and the tenant side is worth measuring with the M365 Security Scorecard before adding another vendor.

Expected output

Run a policy test suite from a host in each zone, before and after every policy change, and in CI against a staging firewall. The assertions are the allow-list read backwards.

bash
#!/usr/bin/env bash
# policy-test.sh  run from a host in the User VLAN.
# Addresses, not names: the User zone may not resolve internal DNS after
# the cut, and a resolution failure would score as a pass below.
set -u
fail=0
expect() {   # expect <should|shouldnot> <host> <port> <label>
  if nc -z -w2 "$2" "$3" 2>/dev/null; then r=open; else r=closed; fi
  case "$1:$r" in
    should:open|shouldnot:closed) printf 'PASS  %-40s %s\n' "$4" "$r" ;;
    *)                            printf 'FAIL  %-40s %s\n' "$4" "$r"; fail=1 ;;
  esac
}

# A should line proves reachability. A shouldnot line proves only that
# nothing answered: a powered-off host, a stopped service or a mistyped
# address returns the same "closed" as a firewall deny. Read every
# shouldnot PASS together with the deny counter on the matching rule.
expect should    10.40.0.10 443  "user to app-server-1/443"
expect should    10.50.0.31 9100 "user to printer-01/9100"
expect shouldnot 10.40.0.20 5432 "user to db-01/5432 must be blocked"
expect shouldnot 10.60.0.20 80   "user to IoT zone must be blocked"
expect shouldnot 10.10.0.1  443  "user to management zone must be blocked"
exit "$fail"

A correct run prints five PASS lines and exits 0:

text
PASS  user to app-server-1/443                  open
PASS  user to printer-01/9100                   open
PASS  user to db-01/5432 must be blocked        closed
PASS  user to IoT zone must be blocked          closed
PASS  user to management zone must be blocked   closed

A shouldnot PASS is corroborating evidence, not proof. Pair each one with the deny counter on the rule that should have dropped the packet: if that counter did not move, the target was unreachable for some other reason and the policy was never tested.

On the firewall, expect deny counters to climb steadily for the first fortnight, dominated by broadcast, discovery and agent chatter rather than anything adversarial. Treat that log as the queue of missing allow rules, and require a named owner for each one.

Side effects

  • Discovery breaks first. mDNS uses 224.0.0.251, a link-local scope address that routers do not forward, so the L3 cut stops it with no rule needed. SSDP uses 239.255.255.250, which is administratively scoped rather than link-local: absent multicast routing it does not leave the VLAN, but where PIM or a reflector is running it will, so set an explicit scope boundary. Either way, casting and driverless printing stop the moment devices sit in different zones, and restoring them takes a deliberate choice: an mDNS reflector on the firewall for named groups, a cast SSID bridged into the room's zone, or a unicast casting product.
  • Server-initiated management stops. Client push and agent deployment fail until the narrow exceptions from Step 3 exist. PXE and Wake-on-LAN need the relay and SVI work from Step 3 instead, since no firewall rule fixes either.
  • ICMP filtering creates PMTU blackholes. Large transfers hang rather than fail, which is the hardest class of ticket to attribute back to the change.
  • DHCP snooping, dynamic ARP inspection and IP source guard drop statically addressed devices until their bindings are entered by hand.
  • 802.1X locks out anything without a supplicant until a MAB profile covers it. Inventory the printers, cameras, badge readers and lab rigs before the enforcement window, not during it.
  • In a firewall-routed topology the firewall becomes the internal bottleneck. Internal traffic usually runs an order of magnitude above the internet link, so size for it.

Rollback

Every stage below is independently revertible. Exercise each one in a change window before it is needed under pressure.

  • Host firewall. nft delete table inet seg_db removes the table and restores host reachability without touching Docker, libvirt or fail2ban tables. Because nft -f applies the file as a single transaction, a syntax error leaves the previous state intact.
  • Firewall policy. The policy is a git tag. Rollback is a deploy of the previous tag, and the diff is reviewable in the ticket afterwards.
  • Zone enforcement. Flip one zone at a time, in the order IoT, Guest, Voice, Printer, Server, User. Rollback scope is then a single zone, and the user zone, where disruption is most visible, is last.
  • 802.1X. Revert to open authentication (authenticate but do not enforce) rather than removing the configuration. Telemetry keeps flowing, so the next attempt starts with data instead of a blank sheet.
  • RADIUS. Keep a fallback policy that returns the previous static VLAN for every port, so a RADIUS outage degrades to the pre-change network rather than to quarantine.
  • Management plane. Confirm out-of-band access works before the first change. A management-zone mistake is not recoverable over the network it just broke.
  • Time-box. If a zone produces unexplained tickets past its agreed window, revert it to log-only and keep collecting flow data. Partial enforcement with a growing exception list is worse than a clean second attempt.

The compliance evidence this actually supports

FrameworkWhat the control says
ISO/IEC 27001:2022Annex A 8.20 Networks security and 8.22 Segregation of networks. The standard is paywalled, so the IEC catalogue entry cited below is the resolvable pointer. The design document and the versioned allow-list are the artefacts.
NIS2, Directive (EU) 2022/2555Article 21(2)(a) risk analysis and information system security policies, 21(2)(e) security in network and information systems acquisition, development and maintenance, and 21(2)(i) human resources security, access control policies and asset management. Note that 21(2)(h) is cryptography and encryption, and is the wrong citation for segmentation.
DORA, Commission Delegated Regulation (EU) 2024/1774Article 13 on network security management requires the segregation and segmentation of ICT systems and networks, documentation of all network connections and data flows, a separate and dedicated network for the administration of ICT assets, and network access controls that detect connections by unauthorised devices. VLAN 10 plus bastion access implements that third point literally.
PCI DSS v4.0.1Requirement 1 is Install and Maintain Network Security Controls. PCI DSS does not require segmentation; segmentation is an optional scope-reduction technique. Where it is used, Requirement 11.4.5 mandates penetration testing of the segmentation controls at least once every 12 months, and 11.4.6 makes that every six months for service providers. Budget for the test before choosing segmentation as a scoping strategy.

An assessor asks for four artefacts: the documented design, evidence of implementation, evidence of operation (logs showing the policy in force), and evidence of review (dated, with named owners and outcomes). Policy-as-code with review dates produces all four as a by-product of running the system.

Limitations

This runbook assumes a single-tenant office of roughly 100 to 500 seats that owns its own switching. Below about 50 seats the operating cost of RADIUS plus a policy pipeline dominates the risk it removes, and a four-zone cut (User, Server, IoT, Guest) with host firewalls is the sensible stopping point. In a serviced or shared office where the landlord owns the switches, Steps 1 to 3 do not apply at all: you cannot enforce VLANs you do not control, and the practical controls are host firewalls plus an identity overlay.

Segmentation addresses lateral movement inside a network you operate. NIST SP 800-207 is explicit that zero trust prioritises protecting resources rather than network segments, and on a mostly-SaaS estate the LAN cut moves a small share of total risk. Audit the SaaS and identity side on its own terms.

On a dual-stack estate every rule needs its IPv6 twin; a v4-only policy on a v6-enabled network routes around itself, quietly. MAB is a placement mechanism and a spoofable one, so treat any MAB-authorised device as untrusted whichever zone it lands in. The compliance table above maps a control to a technique. No assessment outcome and no auditor's opinion is claimed here. Vendor pricing, free-tier limits and contracting entities all move, so re-read the Tailscale pricing page and terms before either goes into a budget or a DPA.

Last verified 2026-07-28. Checked against RFC 3580, the netfilter project wiki, NIST SP 800-207, EUR-Lex for NIS2 and the DORA ICT risk management RTS, the PCI SSC document library, the IEC catalogue entry for ISO/IEC 27001:2022, Microsoft Learn for the Intune compliance default, and Tailscale's pricing page and terms of service.

Sources and further reading

Was this field note useful?
Apply the runbook

Turn the procedure into a tenant decision.

The Architecture Workshop maps the checks, side effects, and rollback path to your own Microsoft 365 environment.

Review the workshop