The Principle
If your security depends on a single mechanism, a single misconfiguration breaks everything. Defense in depth means layering independent controls so that one failure doesn't cascade into a breach. Applied to network access, this means two firewalls: one at the infrastructure level, one on the server itself.
Layer 1: The Cloud Firewall
Oracle Cloud's Security Lists filter traffic before it reaches the VM. This is configured at the infrastructure level through the OCI console. I allow only the ports the server needs to be reachable on — SSH (on a non-default port), HTTP, and HTTPS. Everything else is dropped before the packet ever touches my operating system.
The advantage: even if I misconfigure firewalld on the server, the cloud layer still blocks unauthorized traffic. And if someone gains shell access to the server, they can't modify OCI Security Lists from inside — those require separate cloud console credentials.
Layer 2: firewalld
Oracle Linux uses firewalld with zone-based rules. My server runs in the public zone. Where possible, I used service names instead of raw port numbers:
firewall-cmd --list-all
Six months from now, seeing services: http https is immediately readable during an audit. A list of port numbers requires cross-referencing documentation to remember what each one does.
I also removed the cockpit service. Cockpit is a web-based admin panel on port 9090 that ships enabled by default on Oracle Linux. I don't use it. An open port running a service you don't use is the definition of unnecessary attack surface. Removed.
Why Both?
Consider these failure scenarios:
If I accidentally open a port in firewalld, the OCI Security List still blocks it externally. The mistake has no impact.
If I misconfigure the OCI Security List too broadly, firewalld still enforces restrictions at the OS level. Again, no breach.
Both would need to fail simultaneously, in the same way, for an unauthorized port to be reachable. That's significantly less likely than either failing alone.
The Traffic Path
Every incoming request passes through both layers in sequence:
Internet → OCI Security List → firewalld → nginx → Application
Each layer independently decides whether to allow or drop the connection. This is defense in depth in practice: redundant controls, independent configuration, compound failure required for compromise.