Akadata Linux · Version 0 Saphira

Load balancer

Deploy HAProxy Layer 7 and ipvsadm Layer 4 DSR, NAT, and IPIP on Akadata Linux. Full cross‑platform real‑server /32 ARP resolution for Linux, FreeBSD, macOS, and Windows.

Saphira, the Akadata Linux Version 0 mascot
Technical preview

Load balancer

Why Akadata ships two load balancers

A load balancer distributes traffic across multiple backend servers so no single machine is overwhelmed, and a failure on one server does not take the service down. Akadata Linux ships HAProxy for Layer 7 (HTTP/TCP content switching) and ipvsadm for Layer 4 (kernel‑level packet forwarding). They complement each other rather than compete. Use HAProxy when you need TLS termination, header inspection, or path‑based routing. Use ipvsadm when you need raw throughput measured in millions of packets per second.

ldirectord, the health‑check daemon for LVS, is a candidate for a future stage4 package. Until it ships, health checks for ipvsadm are configured with external scripts (see below).


Layer 7

HAProxy — TCP and HTTP reverse proxy

HAProxy operates at the application layer. It terminates client connections, inspects headers, routes requests based on hostname or path, and opens new connections to backend servers. The configuration lives at /etc/haproxy/haproxy.cfg.

Quick start

rc-service haproxy start
rc-update add haproxy

Frontend / backend pattern

Every HAProxy configuration has at least one frontend (where clients connect) and one backend (where requests are forwarded).

global
    daemon
    maxconn 4096

defaults
    mode http
    timeout connect 5s
    timeout client 50s
    timeout server 50s

frontend http-in
    bind :80
    default_backend web-servers

backend web-servers
    balance roundrobin
    server web1 10.0.0.1:80 check
    server web2 10.0.0.2:80 check
    server web3 10.0.0.3:80 check

Host‑based routing

frontend http-in
    bind :80
    acl is_app hdr(Host) -i app.akadata.ltd
    acl is_api hdr(Host) -i api.akadata.ltd
    use_backend app-servers if is_app
    use_backend api-servers if is_api
    default_backend fallback

Path‑based routing

acl is_static path_beg /static/
use_backend static-servers if is_static
acl is_upload path_beg /upload/
use_backend upload-servers if is_upload

TLS termination

frontend https-in
    bind :443 ssl crt /etc/ssl/akadata/akadata.ltd.pem
    default_backend web-servers

Combine the certificate and private key into one .pem file. HAProxy handles the TLS handshake; traffic to the backend is plain HTTP unless you configure backend SSL.

TCP mode

frontend postgres-in
    mode tcp
    bind :5432
    default_backend postgres-servers

backend postgres-servers
    mode tcp
    balance leastconn
    server pg1 10.0.0.10:5432 check
    server pg2 10.0.0.11:5432 check

Use mode tcp when HAProxy should not inspect the payload: database connections, raw TLS passthrough, or protocols it does not understand.

Health checks

backend web-servers
    option httpchk GET /health HTTP/1.1\r\nHost: localhost
    server web1 10.0.0.1:80 check inter 3s fall 2 rise 2
    server web2 10.0.0.2:80 check inter 3s fall 2 rise 2

inter 3s checks every three seconds. fall 2 marks the server down after two failed checks. rise 2 brings it back after two successful checks.

Stats dashboard

listen stats
    bind :8404
    mode http
    stats enable
    stats uri /
    stats realm HAProxy\ Statistics
    stats auth admin:<strong-random-password>

Browse to http://your-server:8404/ for a live dashboard of frontends, backends, and per‑server metrics. Replace <strong-random-password> with a value generated by openssl rand -base64 32. Never publish a real credential in configuration files or documentation.

Reload without dropping connections

rc-service haproxy reload

Man pages: man haproxy. Full reference: docs.haproxy.org.


Layer 4

ipvsadm — the kernel load balancer

IPVS (IP Virtual Server) is built into the Linux kernel. The director receives all traffic for a VIP (virtual IP) and forwards packets to a pool of real servers. Because the forwarding happens in the kernel, not in userspace, throughput is limited by the NIC, not by CPU copy loops. A single director can saturate 10 Gbps with modest hardware.

ipvsadm operates in three modes. DR (direct server return) is the fastest and the one you should reach for first. NAT is simpler to configure but the director handles return traffic. Tunnel (IPIP) reaches across L2 boundaries.


DR mode

Direct Server Return

In DR mode the director receives the packet, leaves the source and destination IPs unchanged, and forwards it to the real server by rewriting only the destination MAC address. The real server has the VIP bound to its loopback interface so it accepts the packet. When it replies, the reply goes directly to the client. The director never sees the return traffic.

This is the fastest mode. The director handles only the incoming direction. Throughput is limited by the NIC line rate, not by CPU.

Director setup

# Assign the VIP to the director's public interface
ip addr add 198.51.100.10/32 dev eth0

# Add the virtual service and real servers (DR = -g for gateway)
ipvsadm -A -t 198.51.100.10:80 -s wrr
ipvsadm -a -t 198.51.100.10:80 -r 10.0.0.1:80 -g -w 1
ipvsadm -a -t 198.51.100.10:80 -r 10.0.0.2:80 -g -w 1
ipvsadm -a -t 198.51.100.10:80 -r 10.0.0.3:80 -g -w 2

# Verify
ipvsadm -L -n

The -g flag means gateway mode (DR). The -w sets the weight: real server 3 gets twice the traffic of servers 1 and 2.


Real server configuration — the /32 loopback trick

Every real server must have the VIP bound to its loopback interface as a /32 address. This makes the kernel accept packets destined for the VIP without responding to ARP requests for it, because ARP is answered by the director alone.

Linux

# Add the VIP to loopback
ip addr add 198.51.100.10/32 dev lo

# Suppress ARP responses for the VIP
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2

# Make it persistent across reboots
echo 'net.ipv4.conf.all.arp_ignore = 1' >> /etc/sysctl.d/99-vip.conf
echo 'net.ipv4.conf.all.arp_announce = 2' >> /etc/sysctl.d/99-vip.conf

# Bring the VIP up at boot via the local service
printf 'ip addr add 198.51.100.10/32 dev lo\n' > /etc/local.d/vip.start
chmod +x /etc/local.d/vip.start
rc-update add local default

arp_ignore=1 answers ARP only if the target IP is on the incoming interface, which it never is for loopback. arp_announce=2 always uses the best local address when sending ARP, preventing the VIP from leaking into ARP broadcasts on the physical NIC.

Linux — arptables alternative

# Drop ARP replies that carry the VIP on the physical interface
arptables -A OUTPUT -d 198.51.100.10 -j DROP
arptables -A INPUT  -d 198.51.100.10 -j DROP

arptables is stricter: it drops ARP packets containing the VIP regardless of the kernel's ARP policy. On nftables-based systems this is provided by arptables-nft; the rules are identical. Use it when arp_ignore and arp_announce are not sufficient, for example when multiple VIPs are in play and you need per‑address rules.

FreeBSD

# Add the VIP to loopback
ifconfig lo0 alias 198.51.100.10/32

# Suppress ARP
sysctl net.link.ether.inet.proxyall=0
echo 'net.link.ether.inet.proxyall=0' >> /etc/sysctl.conf

proxyall=0 prevents the kernel from proxying ARP for addresses on other interfaces.

OpenBSD / NetBSD

# Add the VIP to loopback
ifconfig lo0 inet alias 198.51.100.10/32

# Prevent ARP leakage
sysctl net.inet.ip.redirect=0
echo 'net.inet.ip.redirect=0' >> /etc/sysctl.conf

macOS

# Add the VIP to loopback
sudo ifconfig lo0 alias 198.51.100.10/32

# macOS does not need explicit ARP suppression because
# loopback aliases are not advertised via ARP.

macOS handles loopback aliases correctly by default. No sysctl changes are needed; the alias is known only to the local TCP/IP stack.

Windows

# Add the VIP to the loopback adapter
netsh interface ipv4 add address "Loopback" 198.51.100.10 255.255.255.255

# Use weak-host receive on the loopback so it accepts the VIP,
# and keep the physical interface as strong-host.
netsh interface ipv4 set interface "Loopback" weakhostreceive=enabled
netsh interface ipv4 set interface "Loopback" weakhostsend=enabled
netsh interface ipv4 set interface "Ethernet" weakhostreceive=disabled

# Drop source-routed packets (defense in depth)
netsh int ipv4 set global sourceroutingbehavior=drop

# The VIP is bound exclusively to the loopback adapter above,
# so the physical interface never answers ARP for it.

On Windows, the loopback adapter typically has a name like Loopback Pseudo-Interface 1. Run netsh interface ipv4 show interfaces to find the correct index. Windows does not natively offer per‑address ARP suppression like Linux; the safest approach is to bind the VIP exclusively to the loopback adapter and let the director handle all ARP traffic.


NAT mode

In NAT mode the director rewrites the destination IP of every incoming packet from the VIP to the real server's address. The real server sees a normal connection from the client; the VIP does not need to be bound to its loopback. The real server sends its reply back to the director, which rewrites the source IP back to the VIP before forwarding to the client.

NAT is easier to configure than DR: no loopback tricks, no ARP suppression. But the director handles both directions of every connection, which limits throughput to the director's CPU and packet‑forwarding capacity.

# Director setup for NAT (-m = masquerade)
ipvsadm -A -t 198.51.100.10:80 -s wrr
ipvsadm -a -t 198.51.100.10:80 -r 10.0.0.1:80 -m -w 1
ipvsadm -a -t 198.51.100.10:80 -r 10.0.0.2:80 -m -w 1

# Enable IP forwarding (persist across reboots)
sysctl -w net.ipv4.ip_forward=1
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-ipvsadm.conf

# NAT return traffic with nftables (matches the VPN firewall)
nft add table ip akadata_nat
nft add chain ip akadata_nat postrouting '{ type nat hook postrouting priority srcnat; policy accept; }'
nft add rule ip akadata_nat postrouting ip saddr 10.0.0.0/24 oifname "eth0" masquerade

Real servers in NAT mode use a normal default gateway pointing at the director. No special configuration is needed on them.


Tunnel mode (IPIP)

Tunnel mode encapsulates the original packet inside an IPIP header and sends it to the real server. The real server decapsulates it and replies directly to the client, like DR mode, but the director and real servers can be in different L2 domains, even different datacenters.

# Director (-i = IPIP tunnel)
ipvsadm -A -t 198.51.100.10:80 -s rr
ipvsadm -a -t 198.51.100.10:80 -r 203.0.113.1:80 -i

# Real server — enable IPIP tunnel, bind VIP to tunl0
modprobe ipip
# tunl0 is created automatically by the ipip module; configure it directly.
# (Do not 'ip tunnel add tunl0' — it already exists and would fail with EEXIST.)
ip link set tunl0 up
ip addr add 198.51.100.10/32 dev tunl0

# Allow decapsulated packets: disable reverse-path filtering on tunl0
sysctl -w net.ipv4.conf.tunl0.rp_filter=0

# Suppress ARP on the real server as with DR mode
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2

Use tunnel mode when real servers are on different subnets or in different physical locations. The overhead of the IPIP header is 20 bytes per packet, which is negligible for most workloads.


Scheduling

Scheduling algorithms

ipvsadm offers ten scheduling algorithms. The -s flag sets the scheduler on the virtual service.

FlagAlgorithmBest for
rrRound RobinEqual backends, stateless workloads
wrrWeighted Round RobinBackends of different capacity: give bigger servers a higher weight
lcLeast ConnectionLong‑lived connections (SSH, WebSocket, database)
wlcWeighted Least ConnectionLong‑lived connections with unequal backends (Akadata's default)
shSource HashSession persistence: same client always hits the same real server
dhDestination HashCache affinity: same destination always hits the same real server
lblcLocality‑Based Least ConnectionProxy caches where destination affinity matters
lblcrLBLCR with ReplicationProxy caches with hot‑standby failover
sedShortest Expected DelayLowest expected wait time based on active connections and weight
nqNever QueueLike SED but never queues to idle servers

Advanced

Advanced ipvsadm

Connection persistence

# 300-second persistence - same client hits same real server for 5 minutes
ipvsadm -E -t 198.51.100.10:80 -s rr -p 300

View the connection table

ipvsadm -L -n -c

Shows every active connection tracked by the director: protocol, state, source IP, VIP, real server. Essential for debugging sticky‑session issues or verifying that traffic is reaching the right backend.

Throughput statistics

ipvsadm -L -n --stats
ipvsadm -L -n --rate

--stats shows total connections and bytes since the rule was created. --rate shows connections and bytes per second. Use this to monitor live throughput.

Zero‑downtime changes

# Add a real server — existing connections are unaffected
ipvsadm -a -t 198.51.100.10:80 -r 10.0.0.4:80 -g -w 1

# Change a real server's weight without dropping connections
ipvsadm -e -t 198.51.100.10:80 -r 10.0.0.1:80 -g -w 3

# Remove a real server — active connections to it will complete,
# new connections will not be sent to it
ipvsadm -d -t 198.51.100.10:80 -r 10.0.0.2:80

ipvsadm edits are applied live. Connections already dispatched to a removed server will complete normally. New connections are dispatched only to the remaining servers.

Persisting rules across reboots

# Save current rules
ipvsadm-save -n > /etc/ipvsadm.rules

# Restore on boot — add to /etc/init.d/local or create a dedicated service
ipvsadm-restore < /etc/ipvsadm.rules

The /etc/init.d/ipvsadm service automatically loads /etc/ipvsadm.rules if it exists. Save your rules there and the service will restore them on every boot.


Health checks

ldirectord (planned)

ldirectord is a daemon that monitors LVS real servers. It sends health checks (HTTP requests, TCP connects, or custom scripts) and, when a server fails, automatically removes it from the ipvsadm table. When the server recovers, ldirectord adds it back. It is a candidate for a future stage4 package; the configuration below is the upstream format it would use.

# Example configuration — /etc/ldirectord.cf
virtual = 198.51.100.10:80
    real = 10.0.0.1:80 gate
    real = 10.0.0.2:80 gate
    real = 10.0.0.3:80 gate
    scheduler = wrr
    checktype = negotiate
    request = "GET /health HTTP/1.1\r\nHost: localhost\r\n\r\n"
    receive = "OK"
    virtualhost = "akadata.ltd"

Until ldirectord ships, the same effect comes from external health‑check scripts that call ipvsadm -d and ipvsadm -a when a server fails or recovers.


Decision

HAProxy or ipvsadm — which one?

CriteriaHAProxyipvsadm DRipvsadm NAT
OSI layer7 (HTTP) / 4 (TCP)44
Throughput ceiling~50 Gbps per instanceLine rate (kernel forwarding)Director CPU‑bound (both directions)
TLS terminationYes, nativeNo (pass‑through)No (pass‑through)
Content switchingHost, path, header, cookieNo (L4 only)No (L4 only)
Health checksBuilt‑in (TCP, HTTP, script)External (ldirectord or custom)External (ldirectord or custom)
Real server configNone (it's a proxy)Loopback VIP + ARP suppressionDefault gateway → director
Best forWeb apps, APIs, TLS offloadHigh‑throughput HTTP, DNS, NTP, UDP servicesSimple setups, small pools

They are not exclusive. A common pattern: HAProxy handles TLS termination and routing at the edge, then forwards to an internal VIP managed by ipvsadm DR, which distributes across a large backend pool. HAProxy for intelligence, ipvsadm for scale.


References

Man pages and documentation