Source: https://crabglamp.com/docs/plain-vms/how-to/add-a-firewall-rule
Last updated: 2026-06-09
Type: how-to

Plain VMs default to inbound TCP/22 (SSH) only. Every other port is closed at the Hetzner edge until you add a rule.

## From the dashboard

1. Open the VM detail page: `/dashboard/plain-vms/{id}`.
2. Scroll to the **Firewall** section.
3. Click **Add rule**.
4. Fill the form:
   - **Protocol** — `tcp` or `udp`. ICMP is not supported at v1 — the API rejects anything else with a 400.
   - **Port** — a single port number like `80`, or a port range like `8000-8100`. Range bounds must satisfy `1 ≤ low ≤ high ≤ 65535`.
   - **Source IPs** — array of CIDR strings. Bare IPs are auto-coerced (`/32` for IPv4, `/128` for IPv6). `0.0.0.0/0` is anywhere; restrict to your own IP for security-sensitive ports.
   - **Description** — free-form label that shows in the rule list.
5. Click **Save**.

The dashboard issues `PUT /api/plain-vms/[id]/firewall` with the **complete** desired rule set (full-replace semantics — partial updates are not supported). Propagation is under 5 seconds.

## The underlying endpoint

The dashboard editor calls `PUT /api/plain-vms/[id]/firewall` with the complete desired rule set:

```json
{
  "rules": [
    { "protocol": "tcp", "port": "22", "sourceIps": ["0.0.0.0/0"], "description": "SSH" },
    { "protocol": "tcp", "port": "80", "sourceIps": ["0.0.0.0/0"], "description": "HTTP" }
  ]
}
```

This endpoint uses your dashboard (Clerk) session — there is no on-VM HMAC path for firewall edits, so manage rules from the dashboard. The `direction` field is server-set to `"in"` on every rule (Hetzner Cloud Firewall only supports inbound at v1) and must not be passed. The response is the canonical post-mutation ruleset, re-fetched from Hetzner — never an optimistic copy of the request.

## Limits

- Maximum 50 rules per VM.
- Full-replace semantics — partial updates require sending the complete rule set every time.
