Firewall configuration

Which ports HeroCtl uses, which need to stay open, and which should never be exposed to the internet.

Networking·9 min read·last reviewed 2026-04-26

A healthy HeroCtl cluster has a small, fixed set of ports. This document shows which they are, what each one does, and how to configure the operating system firewall to open exactly what's needed, no more and no less.

Ports in use

PortProtocolFunctionWho needs access
80TCPIngress router (HTTP)The whole internet
443TCPIngress router (HTTPS)The whole internet
8080TCPControl plane APIOperators and CLI, via VPN or allowlist
8443TCPTLS web panelOperators, via VPN or allowlist
4646TCPInternal coordination between nodes (consensus)Only other cluster nodes
4647TCPRPC between coordinator and workersOnly other cluster nodes
4648TCP+UDPGossip between nodesOnly other cluster nodes

The general rule is simple:

  • 80 and 443 open to the world. They are the entry point for your applications.
  • 8080 and 8443 should never be open to the public internet.
  • 4646, 4647, and 4648 stay restricted to the cluster's internal IPs.

Warning: Exposing 8080 without an allowlist is the most common misconfiguration in new clusters. Anyone with the admin token can submit jobs. Treat this port like SSH.

                     internet
                        │
                  ┌─────┴─────┐
                  │  80, 443  │   ← any origin
                  └─────┬─────┘
                        │
                ┌───────┴───────┐
                │   cluster     │
                │   nodes       │
                └───────┬───────┘
                        │
                  ┌─────┴─────┐
                  │ 4646-4648 │   ← internal IPs only
                  └───────────┘
                        │
                  ┌─────┴─────┐
                  │ 8080,8443 │   ← VPN or allowlist only
                  └───────────┘

Ubuntu and Debian (ufw)

Minimum configuration for a server node:

# regras default
sudo ufw default deny incoming
sudo ufw default allow outgoing

# acesso administrativo (ajuste o IP de origem)
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp comment 'SSH operador'
sudo ufw allow from 203.0.113.10 to any port 8080 proto tcp comment 'API'
sudo ufw allow from 203.0.113.10 to any port 8443 proto tcp comment 'Painel'

# tráfego público
sudo ufw allow 80/tcp comment 'Ingress HTTP'
sudo ufw allow 443/tcp comment 'Ingress HTTPS'

# comunicação interna entre nós (substitua pelos IPs reais)
for ip in 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4; do
  sudo ufw allow from $ip to any port 4646 proto tcp
  sudo ufw allow from $ip to any port 4647 proto tcp
  sudo ufw allow from $ip to any port 4648
done

sudo ufw enable
sudo ufw status numbered

On worker-only nodes (no exposed panel), skip the 8080 and 8443 lines.

RHEL, Fedora, AlmaLinux (firewalld)

# zonas separadas: pública para 80/443, interna para portas de cluster
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https

# zona internal recebe os IPs do cluster
sudo firewall-cmd --permanent --zone=internal --add-source=10.0.0.0/24
sudo firewall-cmd --permanent --zone=internal --add-port=4646/tcp
sudo firewall-cmd --permanent --zone=internal --add-port=4647/tcp
sudo firewall-cmd --permanent --zone=internal --add-port=4648/tcp
sudo firewall-cmd --permanent --zone=internal --add-port=4648/udp

# acesso admin: zone trusted com IP do operador
sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.10
sudo firewall-cmd --permanent --zone=trusted --add-port=8080/tcp
sudo firewall-cmd --permanent --zone=trusted --add-port=8443/tcp

sudo firewall-cmd --reload
sudo firewall-cmd --list-all-zones

iptables directly

If you prefer explicit rules, or use a system without ufw/firewalld:

# tráfego estabelecido
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT

# ingress público
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# admin restrito
iptables -A INPUT -p tcp -s 203.0.113.10 --dport 22 -j ACCEPT
iptables -A INPUT -p tcp -s 203.0.113.10 --dport 8080 -j ACCEPT
iptables -A INPUT -p tcp -s 203.0.113.10 --dport 8443 -j ACCEPT

# cluster interno (repita para cada IP)
iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 4646 -j ACCEPT
iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 4647 -j ACCEPT
iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 4648 -j ACCEPT
iptables -A INPUT -p udp -s 10.0.0.0/24 --dport 4648 -j ACCEPT

# default deny
iptables -P INPUT DROP

# persistir
sudo netfilter-persistent save

Cloud firewall as an upper layer

Even with ufw correctly set on each node, it's worth enabling the cloud provider firewall as a second layer. If ufw goes down due to a bad change, the cloud firewall keeps holding.

DigitalOcean Cloud Firewall

Create a firewall and assign it to all cluster droplets:

DirectionTypeSourcePort
InboundTCPAnywhere80, 443
InboundTCPOperator IPs22, 8080, 8443
InboundTCPTag heroctl-cluster4646, 4647, 4648
InboundUDPTag heroctl-cluster4648
OutboundAllAnywhereAll

Use tags instead of IPs for 4646-4648 — when you add a new node with the tag, it joins the rule automatically.

AWS Security Groups

Create two security groups:

  • heroctl-public: 80 and 443 from 0.0.0.0/0. Assign to all nodes.
  • heroctl-cluster: 4646-4648 with source set to the security group itself (self-reference). For 8080 and 8443, source the bastion's security group.

Hetzner Cloud Firewall

Hetzner has no self-reference. Use the project's private network and open by CIDR:

allow tcp 80,443 from 0.0.0.0/0
allow tcp 4646,4647,4648 from 10.0.0.0/16
allow udp 4648 from 10.0.0.0/16
allow tcp 8080,8443 from <ip-do-operador>/32

Cloudflare in front

For protection against volumetric attacks, putting Cloudflare in proxy mode in front of the cluster works well. Watch out for:

  • For certificate issuance, use DNS-01 (not HTTP-01). Proxy mode breaks HTTP-01.
  • Restrict ports 80 and 443 on the nodes to accept only Cloudflare IP ranges. The official list lives at https://www.cloudflare.com/ips-v4. Update via monthly cron.
  • Enable "Full (strict)" on Cloudflare. Don't use "Flexible" — that makes Cloudflare speak HTTP with your cluster even when the user has HTTPS.

Blocking administrative access

The most important rule in this document. Port 8080 cannot be reachable from the public internet. Three viable paths:

  1. VPN. WireGuard or Tailscale between operator machines and the cluster. Block 8080 from any source outside the VPN network. Recommended for teams.
  2. Fixed IP allowlist. Works for solo operators with stable residential IPs or a bastion VPS.
  3. SSH tunnel. ssh -L 8080:localhost:8080 server every time you use the CLI. Works, but adds friction.

The combination most often seen in production is VPN + cloud firewall. The operator joins the VPN, the cloud firewall only allows 8080 from the VPN range, and the node's ufw does the same on the inside.

Validation

After applying the rules, validate from outside:

# from your machine, no VPN, against a cluster IP
nmap -p 80,443,8080,8443,4646,4647,4648 <ip-do-no>

Correct result: 80 and 443 open, all others closed (closed or filtered).

Next steps

#firewall#ufw#iptables#network#security