A load balancer receives traffic on its own public IPv4 and IPv6 address and spreads it across machines of one of your private networks, over TCP or HTTP(S). It is a managed service: FFxF runs it, keeps it up to date and obtains its Let's Encrypt certificates; you only describe where traffic goes.
How it fits together
- A pool groups the machines that serve the same thing
(
frontend,backend,db). Each target is a machine of the private network and a port. Unhealthy targets stop getting traffic. - A listener is an entry port on the load balancer's address,
http,httpsortcp, with a default pool. - On HTTP(S) listeners, rules send a hostname and/or a path prefix to
another pool:
app.example.comtofrontend,app.example.com/api/orapi.example.comtobackend.
Targets keep their private address: they need no public IP, and their own firewalls can close 80 and 443 to the Internet, since the private network is never filtered.
Create a load balancer
curl -s -X POST https://api.ffxf.net/v1/load-balancers \
-H "Authorization: Bearer $FFXF_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name": "website", "vpc": 12}'
{ "data": { "id": 4, "name": "website", "status": "pending", "vpc": 12, "ipv4": "23.159.52.40", "ipv6": "2602:f3a4:0:100::40", "pools": [], "listeners": [] } }
Billing starts now, by the hour, like a machine (the account needs 24 hours of credit).
The load balancer is ready within a few minutes: status goes from
pending to active. You can set up pools and listeners
meanwhile; they apply as soon as it is ready.
Pools and targets
curl -s -X POST https://api.ffxf.net/v1/load-balancers/4/pools \
-H "Authorization: Bearer $FFXF_TOKEN" -H "Content-Type: application/json" \
-d '{"name": "frontend", "protocol": "http", "health_type": "http", "health_path": "/health"}'
curl -s -X PUT https://api.ffxf.net/v1/load-balancers/4/pools/9/targets \
-H "Authorization: Bearer $FFXF_TOKEN" -H "Content-Type: application/json" \
-d '{"targets": [{"vm": 4821, "port": 3000}, {"vm": 4822, "port": 3000}]}'
PUT …/targets replaces the whole list: describe the state
you want and replay it as often as you like; a target you keep keeps its health. A
machine is named by its id, as in GET /vms, and must be in the load
balancer's private network. An http pool serves http and
https listeners; a tcp pool serves tcp listeners
and can send the PROXY protocol v2 (send_proxy).
Health checks run every 5 seconds by default: a TCP connection, or for HTTP pools a
GET on health_path where any 2xx or 3xx is healthy. Three
failures take a target out, two successes bring it back.
Listeners
curl -s -X POST https://api.ffxf.net/v1/load-balancers/4/listeners \
-H "Authorization: Bearer $FFXF_TOKEN" -H "Content-Type: application/json" \
-d '{"port": 443, "protocol": "https", "default_pool": 9, "redirect_https": true}'
redirect_https makes port 80 redirect to this listener. HTTP requests carry
X-Forwarded-For, X-Forwarded-Proto and
X-Forwarded-Port. Ports 22 and 8402 are reserved; with an HTTPS listener,
port 80 can only be HTTP. A tcp listener forwards everything to its
default pool without reading it.
Routing rules
curl -s -X PUT https://api.ffxf.net/v1/load-balancers/4/listeners/15/rules \
-H "Authorization: Bearer $FFXF_TOKEN" -H "Content-Type: application/json" \
-d '{"rules": [
{"hostname": "app.example.com", "pool": 9},
{"hostname": "app.example.com", "path_prefix": "/api/", "pool": 10},
{"hostname": "api.example.com", "pool": 10}
]}'
The list is replaced as a whole. Whatever the order you send, rules are evaluated most specific first, and the first match wins:
| Rank | Rule | Example |
|---|---|---|
| 1 | Exact hostname and path (longest path first) | app.example.com + /api/ |
| 2 | Exact hostname | app.example.com |
| 3 | Wildcard and path | *.example.com + /static/ |
| 4 | Wildcard | *.example.com |
| 5 | Path only | /static/ |
| – | Otherwise: the listener's default pool, or 503 without one |
Hostnames are lowercased and internationalised names converted to punycode. A wildcard is refused on HTTPS, where no certificate could cover it.
Certificates and DNS
Every hostname in the rules of an HTTPS listener gets its own Let's Encrypt certificate,
renewed 30 days before it expires. It is requested once the name points to the
load balancer: create an A record to its ipv4 and an
AAAA to its ipv6. Until then the certificate shows
dns_pending in certificates; a failure shows
failed with the reason, and is retried an hour later.
Who can connect
Only ports 80, 443 and your listeners' ports are open on the load balancer's address.
Each listener accepts sources, a list of IPv4/IPv6 addresses or ranges, to
restrict who may connect (for a database, an office or a CDN); null lets
everyone in. While an HTTPS listener exists, port 80 stays open to everyone: Let's
Encrypt validates certificates there.
curl -s -X PATCH https://api.ffxf.net/v1/load-balancers/4/listeners/16 \
-H "Authorization: Bearer $FFXF_TOKEN" -H "Content-Type: application/json" \
-d '{"sources": ["203.0.113.0/24", "2001:db8::/32"]}'
Day to day
| Action | Call | Good to know |
|---|---|---|
| See health | GET /load-balancers/{id} | Each target shows health, current sessions and response_ms. config_applied turns true once your last change is live, within about 10 seconds. |
| Drain a target | PATCH …/pools/{pool}/targets/{target} with {"drain": true} | No new connections; current ones finish. Do it before maintenance. |
| Statistics | GET /load-balancers/{id}/metrics?range=24h | Requests by response class, connections, bytes, peak sessions; 1h, 24h or 7d, kept 7 days. |
| Delete | DELETE /load-balancers/{id} | The address and configuration go away and billing stops; the target machines are not touched. |
Limits and billing
Three load balancers per account by default; per load balancer, 10 pools, 25 targets per pool, 5 listeners, 25 rules per listener and 15 HTTPS hostnames. A load balancer is billed by the hour. Its traffic is counted on the target machines, as if they had served it themselves, so it uses their included bandwidth.
Useful details
Reading needs vms.read; creating needs vms.create and an
Idempotency-Key; deleting needs vms.destroy; pools, targets,
listeners and rules need network.write. The MCP server exposes the same
actions: see MCP tools.