<?xml version="1.0"?>
<rss version="2.0"><channel><title><![CDATA[Networking & Firewalls Latest Topics]]></title><link>https://wtmsp.com/forum/7-networking-firewalls/</link><description><![CDATA[Networking & Firewalls Latest Topics]]></description><language>en</language><item><title>Why we put internal web apps behind Nginx Proxy Manager instead of port forwards</title><link>https://wtmsp.com/topic/5-why-we-put-internal-web-apps-behind-nginx-proxy-manager-instead-of-port-forwards/</link><description><![CDATA[<p>A lot of the small offices we pick up have a firewall with a pile of port forwards on it. 8443 goes to one server, 8080 to another, 9000 to a third, and nobody remembers which is which. Each of those services faces the internet with its own TLS settings, or none. A reverse proxy cleans that up. You get one public IP, only ports 80 and 443 open, routing by hostname, and certificates managed in one place. We use Nginx Proxy Manager (NPM) for a lot of this because it puts a usable GUI on top of plain nginx, which helps when the next tech to touch it isn't an nginx person. Below is how we set it up, plus the client IP and redirect loop problems that catch everyone.</p>
<div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>What needs to be in place</strong></p></div>
<ul>
<li>A small Linux VM or container host with Docker, on a network segment that can reach your internal apps.</li>
<li>One public IP with ports 80 and 443 forwarded (or 1:1 NATed) to the proxy, and nothing else.</li>
<li>DNS records for each hostname (for example <code>app.example.com</code>) pointing at the public IP.</li>
<li>For DNS-01 certificates, an API token from your DNS provider, scoped to the zones you need.</li>
</ul>
</div>
<h2>Getting NPM running</h2>
<pre class="ipsCode" data-language="yaml">services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "81:81"     # admin UI - do NOT expose this publicly
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt</pre>
<p>Once it's in production, pin a specific image version so an update doesn't surprise you. Port 81 is the admin UI, so restrict it with the host firewall to your management network or VPN. Log in, change the default credentials right away, and back up the <code>data</code> and <code>letsencrypt</code> folders.</p>

<h2>One proxy host per app</h2>
<p>For each app, create a proxy host with the public hostname and the internal scheme, IP and port (for example <code>http://10.10.50.20:8080</code>), and turn on "Block common exploits." Turn on WebSockets support for anything with live updates, like dashboards, remote consoles or chat. NPM routes on the <code>Host</code> header, so dozens of apps can share the same IP and port 443.</p>
<p>Lock the backends down as well. The app servers should only accept connections from the proxy's IP. Otherwise anyone on the internal network can walk right around whatever the proxy enforces.</p>

<h2>Certificates, HTTP-01 or DNS-01</h2>
<p>With HTTP-01, Let's Encrypt connects to <code>http://hostname/.well-known/acme-challenge/</code>. Port 80 has to be reachable from the internet and the DNS record has to point at the proxy. It's simple, but you get no wildcards, and it won't work for hostnames that are internal-only.</p>
<p>With DNS-01, NPM creates a TXT record through your DNS provider's API. That works for wildcards (<code>*.example.com</code>), for internal-only hosts, and when port 80 is blocked. The cost is an API token sitting on the proxy, so scope it to DNS edit on the specific zone.</p>
<p>Then turn on "Force SSL," and once you're sure everything works over HTTPS, turn on HSTS too.</p>

<h2>Getting the real client IP to the backend</h2>
<p>Skip this and every request in the app's logs comes from the proxy's IP. That wrecks your auditing and rate limiting, and tools like fail2ban will end up banning the proxy, which means banning everyone behind it. NPM sends <code>X-Forwarded-For</code>, <code>X-Real-IP</code> and <code>X-Forwarded-Proto</code> by default. The backend has to be told to trust those headers <em>only from the proxy</em>. If the backend runs nginx, it looks like this.</p>
<pre class="ipsCode" data-language="nginx"># On the backend server, in the http or server block
set_real_ip_from 10.10.50.5;      # the NPM host's IP
real_ip_header   X-Forwarded-For;
real_ip_recursive on;</pre>
<p>If something else sits in front of NPM, such as a CDN or cloud proxy, NPM also needs <code>set_real_ip_from</code> entries for that provider's published ranges and the provider's client IP header. Add them in the proxy host's Advanced tab or a custom include. Never trust <code>X-Forwarded-For</code> from just anywhere, because clients can set it to whatever they like.</p>
<p>Apps that aren't behind nginx have their own setting, usually called "trusted proxies" (Laravel, Nextcloud, and many Node and Python frameworks). Set it to the proxy IP, not to "everything."</p>

<h2>The "too many redirects" loop</h2>
<p>The browser talks HTTPS to NPM, and NPM usually talks plain HTTP to the backend. An app that enforces HTTPS sees a plain HTTP request and redirects to HTTPS. The browser comes back through the proxy, the app sees HTTP again, and around it goes until the browser gives up with "too many redirects." You fix it by making the app honor <code>X-Forwarded-Proto: https</code> from the trusted proxy. A few examples:</p>
<ul>
<li>In Nextcloud, set <code>overwriteprotocol</code> to <code>https</code> and fill in <code>trusted_proxies</code> in <code>config.php</code>.</li>
<li>In Django, set <code>SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")</code>.</li>
<li>In Laravel, configure the TrustProxies middleware with the proxy IP.</li>
<li>For anything else, set the app's "base URL" or "external URL" to <code>https://app.example.com</code>.</li>
</ul>
<div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>Testing it from outside</strong></p></div>
<pre class="ipsCode" data-language="bash"># Certificate and redirect chain from outside your network
curl -sI http://app.example.com | head -n 5
curl -svI https://app.example.com 2&gt;&amp;1 | grep -Ei "subject|expire|HTTP/"

# Confirm only 80/443 are open on the public IP (run from outside)
nmap -Pn -p 1-10000 192.0.2.10</pre>
<p>Run these from outside your network. Then load the app, sign in, and check that its access log shows your real public IP and not the proxy's.</p>
</div>
<div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>Where this has bitten us</strong></p></div>
<ul>
<li>A CDN in "flexible" mode (HTTPS to the browser, HTTP to the origin) in front of an NPM host set to Force SSL gives you a redirect loop before the app is even involved. Use full or strict mode at the CDN.</li>
<li>If the backend is HTTPS with a self-signed cert, set the scheme to <code>https</code> in NPM. Some apps also insist on a matching <code>Host</code> header or reject the proxy.</li>
<li>When uploads fail, it's usually nginx's default body size limit, which is small. Add <code>client_max_body_size</code> in the Advanced tab for file-heavy apps.</li>
<li>Repeatedly failing HTTP-01 validations while you're debugging DNS can hit Let's Encrypt rate limits. Fix DNS first, then request the cert.</li>
<li>Putting an admin login page behind a proxy doesn't make it safe. Add NPM access lists, an SSO layer or VPN-only access for admin interfaces.</li>
</ul>
</div>
<p>Once it's set up right, the proxy is about the most boring box we run, and I mean that as a compliment.</p>]]></description><guid isPermaLink="false">5</guid><pubDate>Sun, 18 Oct 2026 20:15:39 +0000</pubDate></item><item><title>Internal names won't resolve behind cellular routers? Check the router's local domain</title><link>https://wtmsp.com/topic/4-internal-names-wont-resolve-behind-cellular-routers-check-the-routers-local-domain/</link><description><![CDATA[<p>This one has cost us more hours than I'd like to admit. A remote site or a patrol car connects back to the main network through a cellular router. The tunnel is up and you can ping the domain controllers by IP, but names won't resolve. The person in the car gets slow or failed logons, Group Policy doesn't apply, and mapped drives break. Often the cause is the router's own DNS forwarder answering for your internal domain before the question ever reaches your DCs. Here's how we diagnose it and the fixes that have worked for us.</p>
<div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>Gather this first</strong></p></div>
<ul>
<li>Admin access to the cellular or edge router's LAN and DNS settings, either locally or through its cloud management portal.</li>
<li>The internal AD DNS domain (we'll use <code>contoso.local</code>) and the DC IPs (for example <code>10.10.1.10</code> and <code>10.10.1.11</code>).</li>
<li>The remote site's subnet (for example <code>10.10.40.0/24</code>).</li>
<li>Domain admin rights for AD Sites and Services and DNS changes.</li>
</ul>
</div>
<h2>Why the router gets in the way</h2>
<p>Lots of small routers, including some cellular ones, run a dnsmasq-style DNS forwarder for their LAN. That forwarder has a "local domain" configured for its own DHCP clients, and it's <em>authoritative</em> for that domain. Any name in that domain it doesn't know gets an immediate NXDOMAIN instead of being forwarded upstream. Some devices also treat <code>.local</code> specially. If the router's local domain is set to your AD domain, or covers it, every lookup for <code>dc01.contoso.local</code> or the <code>_ldap._tcp.dc._msdcs.contoso.local</code> SRV records gets "does not exist" straight from the router.</p>
<p>The pattern that gives it away is that <strong>public names resolve, internal hostnames fail instantly</strong> (not a timeout), and querying the DC directly works.</p>

<h2>Prove it from a client</h2>
<p>From a machine at the remote site, compare what the router says with what the DC says.</p>
<pre class="ipsCode" data-language="powershell"># Which DNS servers did DHCP hand out?
Get-DnsClientServerAddress -AddressFamily IPv4

# Ask the default resolver (probably the router)
Resolve-DnsName dc01.contoso.local

# Ask the DC directly across the tunnel
Resolve-DnsName dc01.contoso.local -Server 10.10.1.10

# The record domain members actually need to find a DC
Resolve-DnsName _ldap._tcp.dc._msdcs.contoso.local -Type SRV -Server 10.10.1.10</pre>
<p>If the first query fails with "DNS name does not exist" and the direct query to the DC succeeds, the local forwarder is your problem. If the direct query fails too, stop and fix the tunnel, routing or firewall first (UDP and TCP 53 to the DCs).</p>

<h2>Fixing the router</h2>
<p>There are three ways to go. I've listed them in the order I'd try them.</p>
<h3>Change the router's local domain</h3>
<p>Set the router's LAN local domain to something that can't collide with AD, such as <code>lan</code>. The router stops claiming authority over <code>contoso.local</code> and forwards those queries upstream. This is what fixed our case. It was a single setting, pushed to every router in the group.</p>
<h3>Forward the internal zone to the DCs</h3>
<p>If the router supports per-domain forwarding (split DNS), send the AD zone and the relevant reverse zones to the DCs and leave everything else on the normal upstream. In raw dnsmasq terms it looks like this.</p>
<pre class="ipsCode" data-language="ini"># Forward the AD zone and its reverse zone to the domain controllers
server=/contoso.local/10.10.1.10
server=/contoso.local/10.10.1.11
server=/40.10.10.in-addr.arpa/10.10.1.10
# Do not answer authoritatively for the AD domain
# (make sure no local=/contoso.local/ or domain=contoso.local line exists)</pre>
<p>Vendor GUIs usually call this something like "DNS forwarding rules," "split DNS" or "custom DNS servers per domain."</p>
<h3>Hand out the DCs as DNS servers</h3>
<p>You can also have the router's DHCP give clients the DCs as their DNS servers. It's the cleanest option for domain members, but name resolution then depends entirely on the tunnel. If the cellular link or VPN drops, clients lose all DNS, public names included. For vehicles and kiosks that still need the internet when the tunnel is down, I'd stick with one of the first two options.</p>

<h2>Tell AD about the remote subnet</h2>
<p>Even with DNS fixed, domain members on a subnet AD doesn't know about can't work out which site they're in. They may pick a DC across the country, and logons and Group Policy get slower. Add the subnet and tie it to a site.</p>
<pre class="ipsCode" data-language="powershell">New-ADReplicationSite -Name "Branch-North"
New-ADReplicationSubnet -Name "10.10.40.0/24" -Site "Branch-North"</pre>
<p>If the remote site has no DC of its own, associating the subnet with the site that has the nearest DCs is fine.</p>

<h2>Add the reverse zone while you're in there</h2>
<p>A missing reverse zone won't break logons, but it makes troubleshooting misleading. <code>nslookup</code> complains about the server name, logs show IPs instead of names, and some applications care about it. Create one per remote subnet.</p>
<pre class="ipsCode" data-language="powershell">Add-DnsServerPrimaryZone -NetworkId "10.10.40.0/24" -ReplicationScope "Forest"</pre>
<p>Then make sure DHCP or the clients register PTR records.</p>
<div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>Is it actually fixed?</strong></p></div>
<p>Run <code>ipconfig /flushdns</code> first so you aren't looking at a cached NXDOMAIN, then run these.</p>
<pre class="ipsCode" data-language="powershell">Resolve-DnsName dc01.contoso.local
Resolve-DnsName _ldap._tcp.dc._msdcs.contoso.local -Type SRV
nltest /dsgetdc:contoso.local
nltest /dsgetsite
gpupdate /force</pre>
<p><code>nltest /dsgetsite</code> should return the site you created, and <code>/dsgetdc</code> should find a DC without a long delay.</p>
</div>
<div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>Things that tripped us up</strong></p></div>
<ul>
<li>Windows caches negative answers. After we fixed the router, clients kept failing for a while because they'd cached the NXDOMAIN. Flush DNS on the client before you decide the fix didn't work.</li>
<li>If the router is managed from a cloud portal, a local change can be overwritten at the next sync. Make the change in the group or template, not on one box.</li>
<li>We've seen the same problem at more than one site. If one fleet of routers had the setting wrong, assume every fleet configured from the same template does too, and check them all.</li>
<li><code>.local</code> is reserved for multicast DNS, which is one reason some devices handle it specially. It isn't worth renaming a domain over, but keep it in mind when a Mac or a consumer device acts strangely.</li>
<li>If the remote subnet overlaps something else you route, DNS will look broken when it's really routing. Run the direct DC query first.</li>
</ul>
</div>
<p>These days, when internal names fail instantly but the DC answers fine when asked directly, the router's local domain is the first thing I look at.</p>]]></description><guid isPermaLink="false">4</guid><pubDate>Sun, 18 Oct 2026 20:14:38 +0000</pubDate></item><item><title>When the IPsec tunnel says it's up but nothing gets through</title><link>https://wtmsp.com/topic/3-when-the-ipsec-tunnel-says-its-up-but-nothing-gets-through/</link><description><![CDATA[<p>Site-to-site IPsec is easy when you control both ends and they're the same vendor. It's a lot less fun when the other end belongs to a different team, runs a different vendor's gear from a different decade, and you're trading screenshots over email. Most of the failures we see aren't exotic. They come down to overlapping subnets, mismatched proposals, a missing NAT exemption or an MTU problem. Here's the planning sheet we send out, and the order I troubleshoot in when the tunnel shows "up" and nothing passes.</p>
<div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>Before anyone touches config</strong></p></div>
<ul>
<li>Admin access to both firewalls or routers, or a contact on the other side who can read their config to you.</li>
<li>Public IPs for both peers (for example <code>192.0.2.10</code> and <code>198.51.100.20</code>).</li>
<li>The local and remote subnets that need to talk.</li>
<li>A test host on each side that will answer a ping.</li>
</ul>
</div>
<h2>Agree on the worksheet first</h2>
<p>Before anybody configures anything, we send the remote admin a short worksheet and both sides fill in the same values. It feels like paperwork, but it saves a lot of back-and-forth calls later.</p>
<pre class="ipsCode" data-language="plaintext">Peer public IP (A / B):        192.0.2.10 / 198.51.100.20
IKE version:                   IKEv2
Authentication:                PSK (exchanged out of band) or certificate
Phase 1 encryption/integrity:  AES-256 / SHA-256
Phase 1 DH group:              14 (or 19/20)
Phase 1 lifetime:              28800 s
Phase 2 protocol:              ESP
Phase 2 encryption/integrity:  AES-256 / SHA-256 (or AES-GCM)
PFS group:                     14 (or none, but both must agree)
Phase 2 lifetime:              3600 s
Local subnets (A):             10.10.20.0/24
Remote subnets (B):            10.40.0.0/24
NAT-T:                         enabled (UDP 4500)
DPD / keepalive:               enabled</pre>

<h3>Subnets can't overlap</h3>
<p>If both offices use <code>192.168.1.0/24</code>, routing has no way to tell which side a packet belongs to. You can re-address one side, which is the best long-term answer, or NAT inside the tunnel, which works but makes every future rule harder. Also check for overlap with everything else you route, like other tunnels, SD-WAN and remote-access VPN pools. Overlapping a remote-access or mesh VPN range is a classic way to break a site for just one group of users, and those tickets are confusing because it works fine for everyone else.</p>

<h3>Proposals have to match exactly</h3>
<p>Phase 1 (the IKE SA) and phase 2 (the IPsec or child SA) are negotiated separately. A phase 1 mismatch means nothing comes up. A phase 2 mismatch means phase 1 is up and phase 2 never establishes, and some GUIs still show that as a green tunnel. Stay away from legacy options (DES, 3DES, MD5, DH groups 1, 2 and 5) unless the other side truly can't do better, and if you have to use them, write it down.</p>

<h3>PSK or certificates</h3>
<p>A long random pre-shared key is fine for a handful of tunnels. Send it over a different channel than the rest of the config. Certificates scale better, avoid shared secrets, and are the right answer when one peer has a dynamic IP. The catch is that you now own a CA and certificate expiry, so put the expiry date on your calendar.</p>

<h3>NAT-T</h3>
<p>If either peer sits behind NAT, like an ISP modem or a cloud NAT gateway, IKE moves from UDP 500 to UDP 4500 and ESP gets encapsulated in UDP 4500. Allow UDP 500, UDP 4500 and IP protocol 50 (ESP) from the peer's address. Behind NAT, the peer's IKE identity is often its private IP, so you may need to set the local and remote ID explicitly.</p>

<h2>Bring it up with real traffic</h2>
<p>Policy-based tunnels often won't negotiate phase 2 until interesting traffic shows up. Start a continuous ping from a host on one side to a host on the other and watch the logs while it runs. Ping a host, not the remote firewall's inside interface, unless you know that firewall answers pings sourced from the tunnel.</p>

<h2>Up but no traffic, in the order I check it</h2>
<p>Each of these rules out a whole layer, so I go in order even when I think I already know the answer.</p>
<ol>
<li>Start with the SA counters. Look at the phase 2 SA and its encrypt and decrypt (encaps/decaps) counters. On Cisco IOS that's <code>show crypto ipsec sa</code>, and most firewalls have an IPsec monitor page. If encrypts are climbing and decrypts sit at zero, your packets are leaving and nothing is coming back, so the problem is on the far side. If neither is moving, traffic isn't reaching the tunnel on your side.</li>
<li>Check the proxy IDs, also called traffic selectors. For policy-based VPNs, the local and remote subnets have to be mirror images on both ends. If side A says 10.10.20.0/24 to 10.40.0.0/24, side B must say 10.40.0.0/24 to 10.10.20.0/24. A /16 on one side and a /24 on the other is a mismatch for many vendors. When a route-based device (VTI) talks to a policy-based one, set explicit proxy IDs on the route-based side instead of leaving 0.0.0.0/0.</li>
<li>Look at routing. On route-based tunnels you need a route for the remote subnet pointing at the tunnel interface. Then check the return path. The remote host's default gateway has to be the device that terminates the tunnel. If the remote server's gateway is a different router, its replies head out to the internet instead.</li>
<li>Confirm the NAT exemption. If outbound NAT translates your source before IPsec sees it, the traffic no longer matches the selectors. Add a no-NAT or identity NAT rule for local-to-remote subnets above your general outbound NAT rule.</li>
<li>Check firewall policy. A tunnel being up doesn't mean traffic is allowed. Most firewalls need explicit rules for traffic entering from the VPN zone or interface, in both directions. Check host firewalls too, because Windows will happily block ICMP from a "foreign" subnet.</li>
<li>Last, suspect MTU and MSS. If ping works but SMB, RDP or web pages hang, it's probably fragmentation. IPsec overhead, plus NAT-T and PPPoE, shrinks the usable MTU. Clamp TCP MSS on the tunnel. Around 1360 is a safe place to start.</li>
</ol>
<pre class="ipsCode" data-language="plaintext">! Cisco IOS on a tunnel interface (route-based VTI)
interface Tunnel10
 ip mtu 1400
 ip tcp adjust-mss 1360</pre>
<p>To test the path MTU, ping with the don't-fragment bit set and walk the size down until it passes.</p>
<pre class="ipsCode" data-language="powershell"># Windows: 1372 bytes of payload + 28 bytes of headers = 1400
ping 10.40.0.10 -f -l 1372</pre>
<pre class="ipsCode" data-language="bash"># Linux
ping -M do -s 1372 10.40.0.10</pre>
<div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>What working looks like</strong></p></div>
<ul>
<li>Phase 1 and phase 2 SAs are both established, and the encrypt and decrypt counters are climbing on both ends.</li>
<li>Ping works in both directions, started from each side.</li>
<li>A real application test holds up, like an SMB file copy or an RDP session that stays connected for several minutes. That catches MTU problems that ping misses.</li>
<li>The tunnel survives a rekey. Wait out the phase 2 lifetime or force a rekey, and confirm traffic keeps flowing.</li>
</ul>
<p>I don't call a tunnel done until the rekey test passes. Otherwise it tends to come back as a ticket the next morning.</p>
</div>
<div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>Failures we keep running into</strong></p></div>
<ul>
<li>It works for an hour, then dies. That's a lifetime or PFS mismatch. The first negotiation succeeds and the rekey fails, so match lifetimes and PFS exactly.</li>
<li>Only one side can start the tunnel. One peer is set to respond-only or has a dynamic IP. That's fine if it's intended, but write it down so the next person doesn't chase it.</li>
<li>Only the first subnet works when there are several behind one side. Some vendors build one child SA per subnet pair and others want them combined, so look at how each side is building SAs.</li>
<li>One tunnel worked for everyone except staff connected from home. Their remote-access VPN pool used the same range as the remote office.</li>
</ul>
</div>
<p>The counters tell you which side to look at, and that usually settles the "your end or mine" conversation before it gets awkward.</p>]]></description><guid isPermaLink="false">3</guid><pubDate>Sun, 18 Oct 2026 20:13:38 +0000</pubDate></item><item><title>VLANs on Cisco and HP/Aruba switches, and how not to lock yourself out</title><link>https://wtmsp.com/topic/2-vlans-on-cisco-and-hparuba-switches-and-how-not-to-lock-yourself-out/</link><description><![CDATA[<p>Half the networks we inherit have VLANs that someone started and never finished. Usually I find out because the office manager calls to say the desk phones are dead again, or because I'm standing in a closet with a console cable after a trunk change cut me off. So this is how we plan VLANs now, what trunks and access ports actually do, and the config we use on Cisco IOS and on HP/Aruba ProCurve-style switches.</p>
<div class="ipsRichTextBox" data-i-background-color="blue"><div class="ipsRichTextBox__title"><p><strong>Have these ready first</strong></p></div>
<ul>
<li>A managed switch you can reach by console or SSH. Bring the console cable anyway, because you will lock yourself out at least once.</li>
<li>The list of networks you need, like data, voice, guest, cameras, printers and management.</li>
<li>A gateway on the router or firewall (SVI or subinterface) for each VLAN.</li>
<li>Somewhere to write it all down, whether that's a spreadsheet, NetBox or your wiki.</li>
</ul>
</div>
<h2>Write the table before you touch a switch</h2>
<p>Every VLAN gets an ID, a name, a subnet and a purpose. We keep the same scheme at every site we manage, so whoever walks into a building next already knows VLAN 20 is voice. Often that person is me, six months later. A simple plan looks like this.</p>
<pre class="ipsCode" data-language="plaintext">VLAN  Name    Subnet            Gateway        Purpose
10    DATA    10.10.10.0/24     10.10.10.1     Staff PCs
20    VOICE   10.10.20.0/24     10.10.20.1     Desk phones
30    GUEST   10.10.30.0/24     10.10.30.1     Guest Wi-Fi (internet only)
99    MGMT    10.10.99.0/24     10.10.99.1     Switch/AP/UPS management
999   BLACKHOLE  (none)         (none)         Unused native VLAN on trunks</pre>
<p>Two rules I don't bend on. Management gets its own VLAN, never VLAN 1 and never the data VLAN. And VLAN 1 carries nothing. Using an unused VLAN as the native VLAN on trunks closes off VLAN-hopping tricks and keeps untagged junk from landing somewhere useful.</p>

<h2>Access ports, trunks and the native VLAN</h2>
<p>An access port belongs to one VLAN and sends frames untagged. That's what a PC or printer plugs into. A trunk carries many VLANs, with each frame tagged using 802.1Q. You use trunks switch to switch, switch to firewall, and usually switch to AP.</p>
<p>The native VLAN is the one VLAN on a trunk that goes out untagged. Both ends have to agree on it. If they don't, traffic leaks between VLANs, and on Cisco you'll see native VLAN mismatch messages from CDP.</p>
<p>An access port can also carry a tagged voice VLAN, so the phone and the PC plugged into the back of it share one cable. The phone learns which VLAN to use through LLDP-MED, CDP or DHCP options.</p>
<p>Cisco calls these "access" and "trunk." HP/Aruba ProCurve doesn't use those words. Each port is <em>untagged</em> in one VLAN and <em>tagged</em> in any others. An HP port untagged in VLAN 10 and tagged in VLAN 20 is the same idea as a Cisco access port with a voice VLAN.</p>

<h2>The Cisco IOS side</h2>
<pre class="ipsCode" data-language="plaintext">vlan 10
 name DATA
vlan 20
 name VOICE
vlan 99
 name MGMT
vlan 999
 name BLACKHOLE
!
interface range GigabitEthernet1/0/1 - 46
 switchport mode access
 switchport access vlan 10
 switchport voice vlan 20
 spanning-tree portfast
!
interface GigabitEthernet1/0/48
 description UPLINK-TO-CORE
 switchport mode trunk
 switchport trunk native vlan 999
 switchport trunk allowed vlan 10,20,99
!
interface Vlan99
 ip address 10.10.99.11 255.255.255.0
 no shutdown
!
ip default-gateway 10.10.99.1</pre>
<p>On some older or multilayer platforms, <code>switchport mode trunk</code> won't be accepted until you add <code>switchport trunk encapsulation dot1q</code> first. Prune your trunks with <code>allowed vlan</code> instead of letting every VLAN go everywhere.</p>
<p>This next part is where I see people trip most. When you add a VLAN to an existing trunk later, use <code>switchport trunk allowed vlan add 30</code>. Leave off the word <code>add</code> and you replace the whole list, which drops everything else on that trunk, including the management session you're typing into.</p>

<h2>Same thing on HP/Aruba</h2>
<pre class="ipsCode" data-language="plaintext">vlan 10
   name "DATA"
   untagged 1-46
   tagged 48
   exit
vlan 20
   name "VOICE"
   tagged 1-46,48
   voice
   exit
vlan 99
   name "MGMT"
   tagged 48
   ip address 10.10.99.12 255.255.255.0
   exit
ip default-gateway 10.10.99.1</pre>
<p>Making a port untagged in VLAN 10 pulls it out of VLAN 1 automatically. The <code>voice</code> flag marks VLAN 20 so LLDP-MED advertises it to phones. On the uplink (port 48), the native VLAN is just whichever VLAN the port is untagged in. If the port is tagged-only for everything, there's no native VLAN at all. I prefer that when the other end allows it.</p>

<h2>Move management last</h2>
<p>Build the new management SVI and make sure you can reach it over the trunk <em>before</em> you remove the old management IP. On Cisco, run <code>reload in 10</code> before anything risky. If you cut yourself off, the switch rolls back on its own and you're not driving out to the site. Once you're safely back in, cancel it with <code>reload cancel</code>. Then save with <code>write memory</code>, which works on both platforms.</p>
<p>After that, write it down. Record the VLAN table, which ports are trunks and what's on the other end, the native VLAN on each trunk, and each switch's management IP. Port descriptions on uplinks cost nothing, and the next tech who inherits the site will save hours because of them.</p>
<div class="ipsRichTextBox" data-i-background-color="green"><div class="ipsRichTextBox__title"><p><strong>Checking your work</strong></p></div>
<pre class="ipsCode" data-language="plaintext">! Cisco
show vlan brief
show interfaces trunk
show interfaces GigabitEthernet1/0/5 switchport
show cdp neighbors detail

! HP/Aruba
show vlans
show vlans ports 48 detail
show lldp info remote-device</pre>
<p>Each trunk should list the VLANs you expect as allowed and forwarding. Plug in a test PC and make sure it gets a DHCP address in the right subnet, then do the same with a phone and check that it pulls an address from the voice scope.</p>
</div>
<div class="ipsRichTextBox" data-i-background-color="orange"><div class="ipsRichTextBox__title"><p><strong>The time the phones went dead</strong></p></div>
<p>We put a new switch in at an outlying building for one of our municipal clients. PCs were fine, but every desk phone sat at "network unavailable." The access ports had the voice VLAN set correctly, and the phones were even learning the VLAN ID over LLDP-MED. The problem was upstream. The voice VLAN had <strong>never been created on the new switch's uplink path</strong>, so the uplink wasn't carrying it tagged, and the phones were tagging their traffic into a VLAN that went nowhere.</p>
<p>We fixed it by creating the VLAN, tagging it on the uplink, and confirming the upstream switch was tagging it toward us too. Now when phones fail and PCs work, I check the trunk allowed list on every hop between the phone and the call server before I look at anything else.</p>
</div>
<h2>Other things that have bitten us</h2>
<ul>
<li>A native VLAN mismatch between two vendors quietly bridges two VLANs together. Make both ends match, or tag everything.</li>
<li>Forgetting <code>add</code> on Cisco and overwriting the allowed list. For remote work, set <code>reload in</code> first.</li>
<li>The VLAN exists but clients get no DHCP. Usually the firewall or router has no interface or DHCP relay for the new VLAN yet.</li>
<li>APs on trunks. If the AP's management is on the native VLAN and you change the native VLAN, the AP drops off the controller.</li>
</ul>
<p>When something on one VLAN breaks and everything else works, I walk the trunks hop by hop. It's tedious, but it finds the problem faster than guessing.</p>]]></description><guid isPermaLink="false">2</guid><pubDate>Sun, 18 Oct 2026 20:12:38 +0000</pubDate></item></channel></rss>
