Default Ports¶
Every port MFCloud opens, by component.
Master Console (single-node)¶
Ports opened by docker-compose up on the console host. None of the container ports below are restricted to localhost by the bundled compose file — firewall everything except 80/443 to trusted/admin subnets only, or bind them to 127.0.0.1: in docker-compose.yml if nothing else needs direct access.
| Port | Proto | Component | Purpose |
|---|---|---|---|
| 22 | TCP | sshd (host) | Admin access, and outbound from here to every compute/storage host — see Outbound access below. |
| 80 | TCP | Caddy | HTTP → HTTPS redirect. |
| 443 | TCP | Caddy → FastAPI | The web console, JSON API, and noVNC/WebSocket VM console proxy — all share this one port. |
| 8000 | TCP | uvicorn (app) | Direct FastAPI access, bypassing Caddy. Exposed so Caddy can reach it uniformly across HA hosts; not needed for a browser. |
| 5432 | TCP | PostgreSQL | The console database. Reachable from any host that can route to this port unless firewalled — restrict to the console host itself / admin subnet. |
| 6379 | TCP | Redis | Celery broker + WebSocket task-broadcast pub/sub. Same exposure caveat as PostgreSQL. |
| 3370 | TCP | LINSTOR Controller | Only if LINSTOR/DRBD storage is configured — see Storage below. |
RabbitMQ — only for Agent Trust API onboarding
5672 (AMQP) and 15672 (management API) are not part of the bundled docker-compose.yml. They only apply if you stand up a separate RabbitMQ instance to use the Agent Trust API onboarding path (RMQ_ADMIN_USER/RMQ_ADMIN_PASS/RMQ_API_URL in .env) — the default onboarding paths (Imaged, SSH-Push) and the Celery task queue (Redis, port 6379 above) don't need it.
HA Console (3-node cluster)¶
Additional ports on every control-plane host once HA is enabled, on top of the single-node table above. Cluster-internal ports (everything but the VIP's 80/443) should be restricted to the control-plane subnet.
| Port | Proto | Component | Purpose |
|---|---|---|---|
| 5432 | TCP | HAProxy | PostgreSQL entry point — routes writes to the current Patroni primary. |
| 5433 | TCP | Patroni | Each host's actual PostgreSQL (behind HAProxy; 5432 is taken by HAProxy on that same host). |
| 8008 | TCP | Patroni REST | Role/health API (/patroni, /primary) — used by HAProxy's health checks and the HA status widget. |
| 2379, 2380 | TCP | etcd | Client API / peer replication (cluster quorum). |
| 7000 | TCP | HAProxy stats | Load-balancer status page (/stats). |
| 9444 | TCP | mfcloud-haman | HA Manager out-of-band UI — restrict to operator subnets. |
| 9445 | TCP | mfcloud-mcp | MCP Server — Streamable HTTP endpoint for external AI clients (Claude Desktop, Chatbox, etc). Standalone container, not part of the compose stack; optional. |
| — | VRRP | keepalived | VIP advertisement (IP protocol 112, unicast between the 3 hosts — not TCP/UDP, must be explicitly allowed if the firewall filters by protocol). |
Also required between the three control-plane hosts: TCP 22 (root SSH mesh — HA Manager peer control and code shipping) and the etcd/Patroni/HAProxy ports above must reach all three hosts' real LAN IPs, not just localhost.
Outbound access the console needs¶
The console host also needs to reach out, which is easy to miss when only inbound rules are audited:
| Destination | Port | Proto | Why |
|---|---|---|---|
| Every compute node | 22 | TCP | SSH-push onboarding, provisioning, VM/host management commands. |
| Every compute node | 16509/16514 | TCP | libvirt RPC — VM lifecycle, live migration source/destination. |
| Every compute node | 8443 | TCP | Health checks against each host's Emergency UI/agent. |
| K8s cluster API VIP | 6443 | TCP | kubectl against managed Kubernetes clusters — see Managed Kubernetes. |
| Ceph monitors | 3300, 6789 | TCP | Ceph client traffic (datastore/image operations), if Ceph is used. |
| LINSTOR satellites | 3366 | TCP | LINSTOR/DRBD storage operations, if LINSTOR is used. |
| NFS datastores | 2049, 111 | TCP/UDP | Mounting/reading NFS-backed datastores, if configured. |
| LDAP/AD server (customer) | 389 or 636 | TCP | LDAP/AD integration, if enabled — port is whatever the customer's directory uses. |
| Identity provider (customer) | 443 | TCP | OIDC/SSO token/userinfo calls, if enabled. |
| Alert webhook URL (customer) | 443 | TCP | Discord/Slack-style CPU/RAM threshold alerts, if configured. |
| registry.mfcloud.io | 443 | TCP | Pulling console/agent container images (registry-pull deployments). |
Compute Node (Hypervisor)¶
| Port | Proto | Component | Purpose |
|---|---|---|---|
| 22 | TCP | sshd | SSH access. |
| 8000 | TCP | mfcloud-agent (HTTP) | Reserved for plain-HTTP fallback if TLS is mis-configured. |
| 8443 | TCP | mfcloud-agent (TLS) | Emergency Host UI + agent JSON API. |
| 5900–6500 | TCP | QEMU VNC | Per-VM noVNC console proxy targets. |
| 16509 | TCP | libvirtd | libvirt RPC (used for live migration). |
| 16514 | TCP | libvirtd (TLS) | libvirt RPC over TLS. |
| 49152–49215 | TCP | QEMU live migration | TCP migration data channel. |
| 9100 | TCP | node-exporter | Prometheus metrics scraping. |
VNC (5900–6500) is unauthenticated — it must be firewalled to the master
QEMU exposes each VM's console with listen='0.0.0.0' and no VNC password
(the RFB handshake advertises security type 1 = None). The console's
per-session token is enforced by the Caddy/websocket proxy on the master, not
by the VNC port itself — so anything that can reach 5900–6500 on a hypervisor gets
full, passwordless keyboard/mouse/screen access to whatever VMs are running there.
There is no in-product control for this — OVN security groups don't cover it, because VNC lives on the host's management interface, outside the tenant data path. The firewall is the only thing protecting it. On every compute node, restrict 5900–6500 to the control-plane (master) hosts only and drop everything else:
# Allow VNC only from the control-plane hosts (the console proxy's source IPs).
# Use each control-plane host's real management IP; add the console VIP too if
# the app egresses through it. Confirm the source with: ss -tnp | grep 5900
for src in <cp-host-1-ip> <cp-host-2-ip> <cp-host-3-ip>; do
firewall-cmd --permanent \
--add-rich-rule="rule family=ipv4 source address=$src port port=5900-6500 protocol=tcp accept"
done
firewall-cmd --permanent \
--add-rich-rule='rule family=ipv4 port port=5900-6500 protocol=tcp drop'
firewall-cmd --reload
Verify from a host that is not a control-plane node — the probe must now time out (filtered), not connect:
Storage¶
| Port | Proto | Component | Purpose |
|---|---|---|---|
| 3300, 6789 | TCP | Ceph monitors | MON quorum / cluster map. |
| 6800–7300 | TCP | Ceph OSDs | OSD ↔ OSD and client traffic. |
| 3366, 3370 | TCP | LINSTOR | Controller (3370) and satellite (3366). |
| 7000–7999 | TCP | DRBD | Per-resource replication. |
| 2049 | TCP | NFS | NFS data path (datastore mounts). |
| 111 | TCP/UDP | rpcbind | NFS portmapper. |
Networking (OVN)¶
| Port | Proto | Component | Purpose |
|---|---|---|---|
| 6641 | TCP | OVN Northbound DB | Master ↔ northbound writes. |
| 6642 | TCP | OVN Southbound DB | Chassis ↔ southbound subscriptions. |
| 4789 | UDP | VXLAN | Overlay tenant traffic (legacy). |
| 6081 | UDP | Geneve | Overlay tenant traffic (default in OVN). |
Managed Kubernetes (KaaS clusters)¶
Ports used by the VMs that make up a deployed Kubernetes cluster (k3s or RKE2).
These are opened inside the tenant/VM network, between the cluster's own control-plane
and worker VMs — plus one path back to the master, which drives every cluster over SSH and
runs kubectl against the API. The cluster VMs are ordinary MFCloud VMs, so their host-level
firewall (or an OVN security group) is what gates these.
| Port | Proto | Scope | Component / Purpose |
|---|---|---|---|
| 6443 | TCP | CP + kube-vip VIP | Kubernetes API server. Workers, the kubeconfig, and the master's kubectl all target the VIP:6443 on HA clusters. |
| 9345 | TCP | CP nodes | RKE2 only — supervisor/registration port workers join through (server: https://<cp>:9345). k3s reuses 6443. |
| 10250 | TCP | all nodes | Kubelet API — kubectl logs/exec, metrics-server. |
| 2379–2380 | TCP | CP nodes (HA only) | Embedded etcd client/peer — CP↔CP quorum on 3-CP HA clusters. |
| 8472 | UDP | all nodes | Flannel/Canal VXLAN overlay (the pod network). Must be open between every node. |
| 7946 | TCP+UDP | all nodes | MetalLB speaker memberlist (L2/ARP mode) — LoadBalancer IP election. |
| 30000–32767 | TCP | all nodes | NodePort Service range (Kubernetes default). |
kube-vip and MetalLB are ARP-based (L2 mode)
Neither adds a listening port of its own. kube-vip carries the control-plane VIP on 6443 and MetalLB announces LoadBalancer service IPs — both via gratuitous ARP on the local segment. So the requirement isn't a port, it's that the VIP and the MetalLB pool IPs are routable and answer ARP on the cluster's L2 segment, and that 6443 (VIP) and each Service's port are reachable. The API VIP must come from a different IPAM pool than the MetalLB pool.
Outbound from the master to a cluster: TCP 22 (SSH — clusters are provisioned and managed entirely over SSH-CLI) and TCP 6443 (kubectl against the API VIP). Both are per-VM paths already covered by Outbound access, but 6443-to-the-VIP is the one easy to miss.
Quick firewall snapshot¶
Console host, single-node — a public/edge-facing firewall should only need:
Everything else in the Master Console table (8000, 5432, 6379, 3370) is for host-to-container or admin access and belongs on an internal/admin-only rule, not the edge:
firewall-cmd --zone=internal --add-port=8000/tcp --add-port=5432/tcp --add-port=6379/tcp --permanent
firewall-cmd --reload
Console host, HA cluster — add the HA Console ports, all restricted to the other two control-plane hosts:
firewall-cmd --zone=internal --add-port=5433/tcp --add-port=8008/tcp --add-port=2379-2380/tcp \
--add-port=7000/tcp --add-port=9444/tcp --add-protocol=vrrp --permanent
firewall-cmd --reload
Freshly-imaged hypervisor you should see something like:
$ firewall-cmd --list-ports
8000/tcp 8443/tcp 16509/tcp 3366/tcp 3370/tcp \
7000-7999/tcp 6789/tcp 3300/tcp 6800-7300/tcp 6641-6642/tcp \
4789/udp 6081/udp 49152-49215/tcp 16514/tcp 9100/tcp
VNC (5900–6500) is deliberately not in that blanket list
It must be a source-restricted rich-rule (master hosts only), never an
open --list-ports entry — see the VNC danger callout
above. Check it separately:
If that comes back empty, VNC is wide open — fix it before the node carries
tenant VMs. A disabled firewall (firewall-cmd --state → not running)
means every port above, VNC included, is exposed to anything that can route
to the node.
If a non-VNC port is missing and a feature using it doesn't work, open it with: