Lesson 3 · Security

Default-Deny at the Edge

Your host firewall is switched off. Today it goes on — safely — and every open port has to earn its place.

Where we are: Lessons 1 and 2 made you recoverable — a nightly backup and a measured RTO. Now we make you harder to reach. The audit found the host firewall disabled with no rules at all (Finding B), and a service exposed to the whole network for no reason (Finding E). This lesson closes both.

A firewall that's off isn't "permissive" — it's absent. Every port any service decides to open is reachable by anything that can route to the host. The professional inversion is default-deny: block everything inbound, then allow back only the few things you can name and justify. The skill this lesson builds isn't "turn on a firewall" — it's reading your own attack surface and defending each port out loud.

In the field: "default-deny inbound, least exposure" is the baseline posture in every hardening standard (CIS Benchmarks, PCI-DSS, the lot). "Walk me through every listening port on this box and why it's open" is a real audit question and a real interview question. The enterprise word for what you're doing is network segmentation / least-exposure — reducing what an attacker can even talk to before they've found a single bug.

What does a default-deny inbound policy actually mean?

First, read the board

Before you block anything, you audit. Here is what your host is actually listening on right now (ss -tulnp, 2026-06-15). Tap each row to reveal the verdict — keep it, close it, or note it's already safe. Decide for yourself first; that's the rep.

Your host's open ports — justify each one

Tap a row to flip the verdict. The skill is being able to say why before you look.

PortServiceBound toVerdict
22/tcpsshd0.0.0.0 tap
KEEP. This is how you administer the host. It's justified — but it's reachable from the whole LAN, so it's the headline target of Lesson 4 (key-only, no root password) and a candidate to later scope to your management subnet / Tailnet only.
8006/tcppveproxy — web UI* tap
KEEP. The Proxmox management plane — the GUI you log into. Proxmox auto-allows this when you turn the firewall on, so default-deny won't lock you out of it.
3128/tcpspiceproxy — console* tap
KEEP. The SPICE/noVNC console proxy — your out-of-band way into a guest when its network is broken. Also auto-allowed as a management port. Worth keeping precisely because it's your fallback.
111 tcp+udprpcbind0.0.0.0 tap
CLOSE — this is Finding E. rpcbind is the NFS/RPC portmapper. You run no NFS server on this host, so it's listening on 0.0.0.0 for nothing — pure attack surface, and a classic UDP reflection-amplification vector. The right fix is to remove it at the source, not just firewall it (see below).
85 / 25 / 20241pvedaemon · postfix · cloudflared127.0.0.1 tap
ALREADY SAFE — internal. All three bind to 127.0.0.1 (localhost) only. Nothing off-box can reach them: pvedaemon is the local API, postfix sends your alert mail, and cloudflared dials out to build the tunnel — it never listens for inbound. No action needed.
tailscaledTailscale mesh100.x (tailnet) tap
KEEP — and it's already good practice. This lives on the encrypted WireGuard tailnet, not the LAN edge, and it's how you reach home remotely without opening a raw inbound port. Leave it.

Tapped 0 of 6. Three to keep, one to close at the source, one already internal, one already-good — that's a host whose every port you can now defend.

Of your actual open ports, which one should simply not be there?

Two ways to close a port — and which is better

You can stop a port being reachable two ways: firewall it (the service still runs and listens, the firewall drops the packets) or turn it off at the source (the service isn't running, so nothing listens at all). For something you genuinely don't use — like rpcbind — the second is strictly better: a service that isn't running can't be exploited, can't be misconfigured, and removes a line from your audit forever. Firewall the things you need; uninstall the things you don't. Defense in depth means doing both where it counts.

Why this is safe to do on a live host: three guarantees. (1) Proxmox automatically allows the management ports — 8006, 22, 3128, VNC — the instant you enable the firewall, so you won't lock yourself out of the GUI or SSH. (2) Your guests' firewalls are separate and stay off: every guest's firewall enable defaults to 0 and you have no per-guest rule files, so the LAN ports of Plex, Home Assistant and the *arr stack stay open — turning on the host firewall does not filter guest traffic. (3) Rollback lives in the config file, not just the daemon (see the warnings below).
Rollback is two-layered — know the difference. pve-firewall stop stops the running daemon and flushes the live rules, but it leaves the config saying enable: 1 (status becomes enabled/stopped), so it returns on reboot. The real, persistent off-switch is the config file: edit /etc/pve/firewall/cluster.fw (and host.fw) and change enable: 1 to enable: 0. To return to the exact pre-lesson state, delete both files — they didn't exist before.
Expect a brief blip for guests with firewall=1 NICs. Toggling the host firewall rebuilds each guest's firewall-bridge path (the fwbrXXX interfaces), which momentarily interrupts established connections. LAN service ports come straight back, but an app holding a long-lived outbound connection — classically Plex's link to plex.tv — can get stuck and report "server unavailable" even though its port is open. The cure is restarting that one app, not the firewall: pct exec 100 -- systemctl restart plexmediaserver. No data is affected. Do the toggle in a quiet window and keep that restart in your back pocket.
One discipline rule: keep your current SSH session open the whole time, exactly as the Proxmox wiki instructs — "open an SSH connection before enabling the firewall, so you still have access if something goes wrong." Don't enable it and immediately disconnect.

Your tangible win: a default-deny host with one fewer open port

Two moves. Part A removes rpcbind at the source (zero lockout risk). Part B flips the host to default-deny. Both are reversible; the rollback for each is right there in green.

# on the Proxmox host — KEEP THIS SSH SESSION OPEN until you've confirmed it all works
ssh root@192.168.5.121

### PART A — close rpcbind at the source (Finding E) ###
ss -tulnp | grep ':111'                 # see it on 0.0.0.0 right now
systemctl disable --now rpcbind rpcbind.socket
ss -tulnp | grep ':111' || echo "rpcbind: closed"
# rollback if ever needed:  systemctl enable --now rpcbind

### PART B — turn on default-deny at the edge (Finding B) ###
# datacenter level: enable + drop inbound, allow outbound
cat > /etc/pve/firewall/cluster.fw <<'EOF'
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT
EOF

# host level: enable the node's own firewall
cat > /etc/pve/nodes/pve/host.fw <<'EOF'
[OPTIONS]
enable: 1
EOF

pve-firewall compile     # DRY RUN: read the ruleset it WILL apply, before it bites
pve-firewall status      # should now say: enabled/running

# PROVE you didn't lock yourself out (run from another machine / new shell):
#   - browser still loads https://192.168.5.121:8006   (web UI)
#   - a NEW ssh root@192.168.5.121 still connects       (SSH)
#   - Plex etc. still play                              (guests unaffected)

# ROLLBACK if anything looks wrong:
#   pve-firewall stop                    # quick: flush live rules now
#   sed -i 's/^enable: 1/enable: 0/' /etc/pve/firewall/cluster.fw /etc/pve/nodes/pve/host.fw   # persistent
# If a guest app sulks after the toggle (e.g. Plex "unavailable"), just restart it:
#   pct exec 100 -- systemctl restart plexmediaserver

When pve-firewall status reads enabled/running, a fresh SSH session still connects, and the web UI still loads — you've gone from no edge to a default-deny edge, and dropped one needless port off the board. That's the win.

Stay honest about scope. This hardens the host's edge. Your guests still expose their own service ports on the LAN (Plex 32400, the *arr UIs) — those NIC firewall=1 flags you already have are pre-wired but dormant, ready for per-guest microsegmentation when we get to Lesson 5 (Blast Radius). And your internet edge is already in good shape: the audit credited your Cloudflare Tunnel (no raw inbound ports) and Tailscale — this lesson protects the LAN-side of the host, the part those don't cover.

I'm your teacher — ask me anything. Want me to run Part A + B with you and watch the ruleset compile in real time? Curious how to scope SSH/GUI to only your management subnet or Tailnet with a management IPSet (the next level of this)? Or whether enabling those dormant guest firewalls would break Plex? Want to jump to Lesson 4 (locking down SSH itself) instead? Just ask.

Primary source to read next: the Proxmox VE Firewall admin-guide chapter — read the Zones and default management rules sections, then the Firewall wiki for the "don't lock yourself out" procedure and the management IPSet pattern.