Introduction
A cloud firewall rule is a filter that instructs your infrastructure to allow or drop a network packet based on its source, destination, port, and protocol. Get them right and your VM is accessible only to what it needs to serve. Get them wrong and you have either locked yourself out or left a port open that an automated scanner will find within minutes.
This guide explains how firewall rules work at the network level, what inbound and outbound rules actually control, and how to apply the least privilege principle to design a firewall policy you can reason about and defend. It bridges the gap between knowing that firewalls exist and knowing how to configure them with intention — the difference between a firewall that feels safe and one that actually is.
At Raff Technologies, every VM is placed behind a cloud-level firewall the moment it is provisioned. The default posture is restrictive: only SSH (port 22) is open on a fresh Linux VM, and only RDP (port 3389) on Windows. Over time, though, we see customers gradually add rules — a port here for an app, a port there for monitoring — until the ruleset becomes a graveyard of "I don't know what this is for but I'm afraid to delete it." This guide gives you the framework to audit that graveyard and keep it clean.
How Cloud Firewall Rules Work
A firewall rule is an instruction with four components: a direction (inbound or outbound), a protocol (TCP, UDP, ICMP), a port or port range, and an action (allow or deny). When a packet arrives at your VM, the firewall evaluates rules from top to bottom and applies the first match. If no rule matches, the default policy applies — which on a well-configured firewall is always "deny."
Stateful vs Stateless Firewalls
The single most misunderstood concept in firewall configuration is statefulness — and getting it wrong leads to either over-permissive rulesets or broken connectivity.
A stateful firewall tracks the state of network connections. When your VM initiates an outbound connection (for example, apt update reaching out to Ubuntu's package servers), the firewall automatically permits the return traffic for that established session. You do not need an explicit inbound rule to allow the response — the firewall knows the connection was initiated from inside and lets the response through.
A stateless firewall treats every packet independently with no memory of prior packets. To allow a two-way conversation, you must write rules for both directions explicitly.
The Raff cloud firewall is stateful, as are UFW and modern iptables configurations using connection tracking (conntrack). This has a practical implication: for most services your VM consumes (package updates, API calls, DNS lookups), you do not need outbound rules at all — the stateful firewall handles the return traffic automatically.
Where you do need outbound rules is when you want to restrict what your VM can contact — for example, preventing a compromised VM from phoning home to a command-and-control server, or enforcing that a database VM can only reach your application servers and nothing else.
Rule Evaluation Order and Default Deny
Firewall rules are evaluated in sequence. The first matching rule wins. This is why default deny must be the final rule in your chain: it acts as the catch-all that drops everything not explicitly permitted.
Rule 1: Allow TCP port 22 from 203.0.113.0/24 → Match → Allow
Rule 2: Allow TCP port 443 from anywhere → Match → Allow
Rule 3: Allow TCP port 80 from anywhere → Match → Allow
...
Final: Deny all → Catch-all → Drop
On the Raff cloud firewall, the default policy is implicit deny — you do not write it explicitly. In UFW, you set it with sudo ufw default deny incoming. In raw iptables, you set it with iptables -P INPUT DROP.
A subtle but important point: deny rules and drop rules behave differently. A deny (REJECT) sends the sender an ICMP "port unreachable" message, telling them the port is closed. A drop (DROP) silently discards the packet, leaving the sender to time out. For internal services behind a private network, REJECT gives faster feedback to legitimate clients. For internet-facing ports, DROP is preferable because it gives port scanners no confirmation the host even exists.
Inbound Rules
Inbound rules filter traffic arriving at your VM from external sources — whether from the internet, from other VMs in your network, or from within a private VPC.
The Minimum Viable Inbound Ruleset
For a typical Linux VM running a web application, you need exactly three inbound rules to start:
| Protocol | Port | Source | Reason |
|---|---|---|---|
| TCP | 22 | Your IP only | SSH admin access |
| TCP | 80 | Anywhere | HTTP (redirects to HTTPS) |
| TCP | 443 | Anywhere | HTTPS application traffic |
Everything else is denied by default. Not your database port. Not your monitoring agent port. Not your internal API port. Those come later, scoped to the specific source that needs them.
This is a deliberate starting point, not a finished policy. The process is always the same: start with the minimum, then add rules only when you have a named service and a named source that requires access.
Common Inbound Rule Mistakes
Opening SSH to the world. Port 22 exposed to 0.0.0.0/0 is the single most scanned port on the internet. Automated scanners find it within minutes of a VM being provisioned. Either restrict SSH to your known IP ranges, move it to a non-standard port, or — better — place it behind a VPN (as covered in the WireGuard setup tutorial) so the port is not reachable from the public internet at all.
Leaving development ports open. Port 8080, 3000, 5000, 8888 — these are default ports for local development servers. When you test something on a Raff VM and forget to close the port afterward, that service becomes publicly accessible. A common pattern we see in support tickets: a developer installs Jupyter Notebook for a one-time analysis, leaves port 8888 open, and returns six weeks later to find unauthorized notebooks running.
Using broad CIDR ranges for trusted sources. Allowing 10.0.0.0/8 because "all our VMs are in that range" is imprecise. If your VMs are at 10.0.1.0/24, write /24, not /8. The difference is 16 million addresses vs 254.
Not auditing rules after a project ends. Every rule should have a documented reason. If you cannot explain why a rule exists, it should be deleted. A quarterly rule audit — even just a 15-minute review of the list — catches orphaned rules before they become a liability.
Scoping Inbound Rules to Sources
The most effective firewall posture is not just controlling which ports are open, but controlling who can reach them. The Raff cloud firewall supports source filtering by IP address, CIDR range, or another security group. Use this aggressively:
| Service | Port | Recommended Source Scope |
|---|---|---|
| SSH | 22 | Your office/home IP or VPN subnet only |
| Database (MySQL, Postgres) | 3306, 5432 | Application server IPs only |
| Redis | 6379 | Application server IPs only |
| Internal API | 8080 | Load balancer or app tier only |
| Monitoring agent | 9100 | Monitoring server IP only |
| HTTP/HTTPS | 80, 443 | Anywhere |
This table represents a principle, not a template. Every row requires a decision: who specifically needs this? If the answer is "anyone," that is fine for 80 and 443. For everything else, the answer should be a named IP range.
Outbound Rules
Outbound rules filter traffic leaving your VM. Because the Raff cloud firewall is stateful, return traffic for connections your VM initiates is automatically allowed — so your apt update, DNS queries, and HTTPS API calls work without any explicit outbound rules.
Outbound rules become important when you want to enforce restrictions on what a VM is allowed to contact. This is a more advanced posture, but it is the right default for sensitive infrastructure.
When to Write Explicit Outbound Rules
Database VMs: A database server should not be initiating arbitrary outbound connections. Lock it down to: DNS (UDP 53) for hostname resolution, NTP (UDP 123) for time sync, your backup destination, and nothing else. If a database VM starts making connections to an external IP you don't recognize, the outbound restriction will block it and your monitoring will alert — instead of silently exfiltrating data.
Application servers in a PCI or compliance scope: Regulated environments often require that servers only contact explicitly approved destinations. Outbound allowlists make this auditable.
Build servers and CI runners: These often pull dependencies from the internet. Restricting outbound to known package repositories (Ubuntu mirrors, npm registry, Docker Hub) prevents a compromised build step from reaching arbitrary external endpoints.
For a standard web application VM with no compliance requirements, the practical outbound policy is: allow everything outbound, deny everything inbound except what you explicitly open. This is the default Raff posture and it is reasonable for most workloads.
A Practical Outbound Policy for a Database VM
Allow outbound UDP 53 → Anywhere (DNS)
Allow outbound UDP 123 → Anywhere (NTP)
Allow outbound TCP 443 → backup.yourinfra.com (Encrypted backup)
Deny outbound all → Anywhere (Default deny)
This VM accepts inbound connections from your application servers on port 5432, handles DNS and time sync, sends backups, and nothing else. If it becomes compromised, the attacker cannot use it to pivot outbound to the broader internet.
The Least Privilege Principle Applied to Firewalls
Least privilege is a security principle that states every component should have access to exactly what it needs to perform its function — and nothing more. Applied to firewall rules, it means:
Every port is closed by default. You start from zero and add access, not the reverse. The question is never "should I close this?" but always "have I confirmed this needs to be open?"
Every open port has a named owner. If you cannot answer "what service is listening on this port and who legitimately connects to it?", the port should be closed until you can.
Every rule has a documented reason. In a team environment, rules without documentation become permanent by default. Nobody deletes a rule they don't understand because they're afraid of breaking something. Treat firewall rules like code — they belong in version control with commit messages explaining why each change was made.
Access is scoped to the minimum necessary source. Opening a port to 0.0.0.0/0 when only three application servers need access is not least privilege. It is convenience dressed up as configuration.
The Firewall Rule Lifecycle
Every rule should pass through a deliberate lifecycle rather than being added reactively and forgotten:
1. REQUEST — a named service requires network access
2. SCOPE — identify the exact port, protocol, and source required
3. ADD — add the rule with a comment or tag recording the reason and date
4. VERIFY — confirm the rule works and that nothing unintended is accessible
5. REVIEW — revisit the rule periodically; confirm the service still exists
6. REVOKE — remove the rule when the service is decommissioned
Most organizations are good at steps 1–4 and skip 5 and 6 entirely. The result is firewall rulesets that grow indefinitely. A rule for a development environment that was torn down 18 months ago is still sitting open because nobody wants to be the person who breaks something by deleting it.
Layered Firewall Architecture on Raff
A production Raff VM should have two firewall layers operating independently. This is sometimes called defense in depth — if one layer is misconfigured or bypassed, the other provides a backstop.
Layer 1: Raff Cloud Firewall (Perimeter)
The Raff cloud firewall operates at the network edge before traffic reaches your VM's operating system. It is applied in hardware/hypervisor infrastructure, which means it cannot be disabled from inside the VM — even if an attacker gains root access to the OS, they cannot modify the cloud firewall rules.
Use the Raff cloud firewall for:
- Internet-facing perimeter rules (HTTP, HTTPS, SSH)
- Cross-VM access rules within your infrastructure
- Emergency lockdown — you can close all ports from the panel even if the VM is unresponsive
Layer 2: Host Firewall (UFW or iptables)
UFW or iptables running inside the VM provides a second line of defense. It can enforce rules the cloud firewall cannot — for example, restricting access per Linux user, per network interface, or based on application context using connection tracking.
Use the host firewall for:
- Interface-specific rules (e.g., allow Redis only on
wg0, the WireGuard interface — not oneth0) - Process-level filtering (advanced, using iptables owner module)
- Local loopback restrictions
- Rules that need to be co-located with application configuration in version control (e.g., managed by Ansible or Terraform)
Raff Private Networking and Firewalls
If you are running multiple Raff VMs that need to communicate privately, use Raff private networking (VPC) in combination with your firewall rules. VPC traffic stays within Raff's internal network and never traverses the public internet — but it still benefits from firewall rules that restrict which VMs can reach which services.
A database VM on the private network should have inbound rules that only allow its private IP range, not all VPC traffic. Private does not mean trusted by default.
Verifying Your Firewall Rules
Writing rules is not enough. You must verify that your rules produce the intended outcome from the perspective of an external client. This is the step most teams skip — and where misconfigurations hide.
Scanning From Outside
Use nmap from a machine outside your Raff VM (your laptop, a separate VM) to confirm which ports are actually reachable:
bashnmap -sV -p 1-65535 <your-raff-vm-public-ip>
The output lists every port that responded. Compare it against your intended ruleset. Any open port not on your list is a finding — either a misconfigured rule or a service you forgot was running.
For a quick check on specific ports:
bash# Check if port 22 is open from outside
nc -zv <your-raff-vm-public-ip> 22
# Check if port 3306 (MySQL) is correctly closed
nc -zv <your-raff-vm-public-ip> 3306
A closed port returns Connection refused (REJECT) or times out (DROP). Neither is wrong — DROP is slightly preferable for internet-facing ports as discussed earlier.
Confirming Inbound Rules Are Enforced
A common mistake is configuring UFW correctly inside the VM but forgetting that the Raff cloud firewall has a separate allow rule for the same port. The two layers must be consistent. If the cloud firewall allows port 3306 from anywhere but UFW blocks it, MySQL is protected. But if the cloud firewall blocks 3306 and UFW allows it, the cloud firewall wins — and MySQL is safe regardless of what UFW says.
Test both layers independently:
- Temporarily disable UFW (
sudo ufw disable) and scan — this tests the cloud firewall in isolation - Re-enable UFW (
sudo ufw enable) and scan again — this tests the combined posture
Both scans should produce the same open ports. If they differ, you have an inconsistency that needs resolving.
A Reference Firewall Policy by VM Role
Different VM roles have different legitimate access requirements. This table provides a starting-point policy for each common role — not a finished configuration, but a structured baseline to adapt.
| VM Role | Inbound Allow | Outbound Restrictions |
|---|---|---|
| Web server | 80 (any), 443 (any), 22 (admin IP only) | None (stateful return traffic sufficient) |
| Database server | 5432/3306 (app server IPs only), 22 (admin IP or VPN only) | DNS, NTP, backup destination only |
| Load balancer | 80 (any), 443 (any) | App server IPs on app port only |
| Build/CI runner | 22 (admin IP only) | Package repos, Docker Hub, git hosting |
| Monitoring server | 22 (admin IP only), 9090 (internal only) | All VM IPs on monitoring port (9100, etc.) |
| Bastion/jump host | 22 (your IP only) | Internal VMs on 22 only |
A bastion host is worth calling out specifically: a dedicated VM whose only purpose is to be the SSH entry point into your infrastructure. It has no services of its own, and all other VMs allow SSH only from the bastion's private IP. This creates a single, auditable chokepoint for administrative access — far easier to monitor and harden than SSH open on every VM independently.
Conclusion
A firewall is only as strong as the intentionality behind its rules. The technical mechanics — stateful inspection, inbound vs outbound direction, default deny — are learnable in an afternoon. The harder discipline is maintaining the habit: every new port opened has a documented reason, every decommissioned service has its rules revoked, and every configuration is verified from outside the VM rather than assumed to be correct.
The least privilege principle is not a setting you toggle on — it is a posture you maintain over time. Start with the minimum, add with justification, review quarterly, and delete without sentiment.
For the practical implementation steps that build on this framework, the following resources cover the specifics:
- Set Up WireGuard VPN on Ubuntu 24.04 — eliminate public SSH exposure entirely by routing admin access through an encrypted private tunnel
- SSH Hardening on Ubuntu — disable password authentication, restrict login to specific users, and harden the SSH daemon configuration
- Cloud Server Security Best Practices — the pillar guide for this cluster, covering the full security surface beyond just firewall rules
This guide was written by Serdar from the infrastructure side — the firewall policy patterns described here reflect what we apply to Raff's own internal VM fleet, refined through the kinds of incidents you learn from the hard way.
