Lesson 3 · Security
Your host firewall is switched off. Today it goes on — safely — and every open port has to earn its place.
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.
What does a default-deny inbound policy actually mean?
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.
Tap a row to flip the verdict. The skill is being able to say why before you look.
| Port | Service | Bound to | Verdict |
|---|---|---|---|
| 22/tcp | sshd | 0.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/tcp | pveproxy — 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/tcp | spiceproxy — 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+udp | rpcbind | 0.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 / 20241 | pvedaemon · postfix · cloudflared | 127.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. | |||
| tailscaled | Tailscale mesh | 100.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?
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.
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).
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.
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.
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.
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.
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.