Production traffic

Load balance X2 with NGINX or HAProxy

Select the X2 Node platform, then configure a production load balancer that preserves S3 signatures and streaming, verifies every backend, and removes unhealthy nodes from rotation.

Select the X2 Node platform

The load balancer can run separately; these categories tailor paths, validation, and operating guidance to the X2 nodes behind it.

X2 Node operating system

Platform preparation

Prepare Linux X2 nodes for load balancing

Prepare Windows X2 nodes for load balancing

Prepare macOS X2 nodes for load balancing

Replace every value enclosed in <...>. The console and S3 endpoints may share one load balancer, while the inter-node mesh on port 9443 must never pass through it.

Linux node pathValidate backends with systemctl status x2-node and /health/ready. NGINX or HAProxy may run on dedicated Linux hosts.
Windows node pathValidate backends with Get-Service X2Node and /health/ready. Use dedicated Linux load-balancer hosts for production NGINX or HAProxy.
macOS node pathValidate backends with launchctl print system/com.edgedrive.x2-node and /health/ready. Use dedicated Linux load-balancer hosts for production traffic.

Public DNS

Console/API
<CONSOLE_DNS>
S3 endpoint
<S3_DNS>
Virtual-host buckets
*.<S3_DNS>

Forwarded-header trust

X2 accepts valid forwarded headers from any peer when no trusted CIDRs are configured. If the X2 listener is also exposed to untrusted networks, use the optional allowlist later in this guide to limit forwarding to your load-balancer addresses.

Two TLS hops

Install the public certificate on the load balancer. Keep HTTPS between the load balancer and X2, and give it the CA that issued the X2 public-listener certificates. Do not disable upstream verification in production.

Upstream certificate name

Every X2 upstream certificate must contain <X2_UPSTREAM_TLS_NAME>. The initial x2-node configure certificate includes the host from --public-url; operator-issued certificates can use a separate shared internal name.

NGINX

NGINX load-balancer setup

Put the map and upstream blocks in the NGINX http context and the server block in the enabled virtual host.

Linux X2 nodes

Install NGINX on one or more dedicated Linux load-balancer hosts. Confirm the distribution in the official package matrix.

sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl enable --now nginx
Windows X2 nodes

Use production NGINX on a dedicated Linux host and point its upstream pool at the Windows node addresses. The native NGINX Windows build is beta and is not intended for high-performance production use.

# Run on each dedicated Linux load-balancer host
sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl enable --now nginx
macOS X2 nodes

Use production NGINX on a dedicated Linux host and point its upstream pool at the macOS node addresses.

# Run on each dedicated Linux load-balancer host
sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl enable --now nginx
NGINX configuration
map $http_upgrade $x2_connection_upgrade {
    default upgrade;
    ''      '';
}

upstream x2_nodes {
    least_conn;
    server <X2_NODE_1_IP>:8443 max_fails=3 fail_timeout=10s;
    server <X2_NODE_2_IP>:8443 max_fails=3 fail_timeout=10s;
    keepalive 64;
}

server {
    listen 443 ssl;
    server_name <CONSOLE_DNS> <S3_DNS> *.<S3_DNS>;

    ssl_certificate     /etc/nginx/tls/x2-public.crt;
    ssl_certificate_key /etc/nginx/tls/x2-public.key;
    client_max_body_size 0;

    location / {
        proxy_pass https://x2_nodes;
        proxy_http_version 1.1;

        proxy_set_header Host $http_host;
        proxy_set_header Forwarded "";
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $x2_connection_upgrade;

        proxy_request_buffering off;
        proxy_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        proxy_ssl_server_name on;
        proxy_ssl_name <X2_UPSTREAM_TLS_NAME>;
        proxy_ssl_trusted_certificate /etc/nginx/tls/x2-upstream-ca.crt;
        proxy_ssl_verify on;
        proxy_ssl_verify_depth 3;
    }
}

This baseline uses HTTP/1.1 for broad package compatibility and streaming S3 requests. If the installed NGINX build includes the HTTP/2 module and uses the current directive syntax, enable http2 on; at server scope; the older listen ... http2 parameter is deprecated.

sudo nginx -t
sudo systemctl reload nginx
Failure detection in this NGINX example is passive.

max_fails and fail_timeout react after proxied requests fail; this open-source configuration does not poll /health/ready in advance. A node can therefore receive a client request before NGINX marks it unavailable. Use the HAProxy example when active readiness checks are required, or deploy an independently verified NGINX health-check mechanism.

HAProxy

HAProxy load-balancer setup

Linux X2 nodes

Install HAProxy on one or more dedicated Linux load-balancer hosts. For vendor packages, use the official HAProxy downloads.

sudo apt-get update
sudo apt-get install -y haproxy
sudo systemctl enable --now haproxy
Windows X2 nodes

Run HAProxy on a dedicated Linux host and point its backend pool at the Windows node addresses.

# Run on each dedicated Linux load-balancer host
sudo apt-get update
sudo apt-get install -y haproxy
sudo systemctl enable --now haproxy
macOS X2 nodes

Run HAProxy on a dedicated Linux host and point its backend pool at the macOS node addresses.

# Run on each dedicated Linux load-balancer host
sudo apt-get update
sudo apt-get install -y haproxy
sudo systemctl enable --now haproxy
HAProxy configuration
frontend x2_public
    bind :443 ssl crt /etc/haproxy/tls/x2-public.pem alpn h2,http/1.1
    mode http
    option httplog
    http-request del-header Forwarded
    http-request del-header X-Forwarded-For
    http-request set-header X-Forwarded-Host %[req.hdr(Host)]
    http-request set-header X-Forwarded-Proto https
    http-request set-header X-Forwarded-For %[src]
    default_backend x2_nodes

backend x2_nodes
    mode http
    balance leastconn
    option httpchk GET /health/ready
    http-check expect status 200
    timeout connect 10s
    timeout server 1h
    server node1 <X2_NODE_1_IP>:8443 ssl verify required ca-file /etc/haproxy/tls/x2-upstream-ca.crt verifyhost <X2_UPSTREAM_TLS_NAME> check
    server node2 <X2_NODE_2_IP>:8443 ssl verify required ca-file /etc/haproxy/tls/x2-upstream-ca.crt verifyhost <X2_UPSTREAM_TLS_NAME> check
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy
HAProxy checks readiness actively.

option httpchk polls /health/ready and the check parameter removes a backend that fails the expected HTTP 200 check. This detects readiness independently of a client request, while application requests still preserve the original host, streaming body, and verified upstream TLS.

Optional X2 configuration

Restrict forwarded-header trust when required

No X2 load-balancer allowlist is required for the basic configurations above. When trusted_proxy_cidrs is absent or empty, X2 accepts valid forwarded headers from any immediate peer. Add an allowlist only when the X2 listener is reachable by networks or clients that must not be allowed to assert forwarded host or HTTPS information.

01

Optionally set public names and a load-balancer allowlist

The public URL and S3 host values are useful when publishing distinct DNS names. The trusted CIDRs are an independent, optional hardening control. Configure the immediate load-balancer connection addresses—not browser or S3 client addresses.

sudo /usr/lib/x2/x2-node configure \
  --metadata '<METADATA_PATH>' \
  --public-url 'https://<CONSOLE_DNS>' \
  --s3-hosts '<S3_DNS>' \
  --trusted-proxy-cidrs '<PROXY_IP_OR_NETWORK_CIDR>'
& 'C:\Program Files\X2\x2-node.exe' configure `
  --metadata '<METADATA_PATH>' `
  --public-url 'https://<CONSOLE_DNS>' `
  --s3-hosts '<S3_DNS>' `
  --trusted-proxy-cidrs '<PROXY_IP_OR_NETWORK_CIDR>'
sudo /usr/local/lib/x2/x2-node configure \
  --metadata '<METADATA_PATH>' \
  --public-url 'https://<CONSOLE_DNS>' \
  --s3-hosts '<S3_DNS>' \
  --trusted-proxy-cidrs '<PROXY_IP_OR_NETWORK_CIDR>'

Multiple load-balancer instances use a comma-separated list. A same-host instance normally uses 127.0.0.1/32,::1/128. Once any CIDR is configured, X2 rejects forwarded headers from every peer outside that allowlist.

02

Optional YAML equivalent

public:
  base_url: "https://<CONSOLE_DNS>"
  s3_endpoint_hosts:
    - "<S3_DNS>"
  trusted_proxy_cidrs:
    - "<PROXY_IP_OR_NETWORK_CIDR>"

When the public listener uses a non-default HTTPS port, include that external port in public.base_url (or --public-url). X2 applies the externally advertised port to the configured S3 domain; the node backend port is not published. The NGINX example preserves the complete public Host authority for S3 signature verification.

X2 updates its cluster-issued node certificate when S3 domains change, including each domain and its wildcard bucket name. If public TLS ends at the load balancer, install a certificate covering those names on the load balancer separately.

To return to unrestricted forwarded-header trust, remove trusted_proxy_cidrs or set it to an empty list, then restart the node through the normal service workflow. Forwarded values are still syntax-checked, conflicting hosts are rejected, and the forwarded public scheme must be HTTPS.

If the load balancer verifies X2 upstream TLS using a different name, install a certificate containing <X2_UPSTREAM_TLS_NAME> with the --tls-cert and --tls-key options.

Verification

Prove management, S3, and streaming paths

Readiness and UI

curl --fail https://<CONSOLE_DNS>/health/ready
curl --head https://<CONSOLE_DNS>/

S3 host routing

xc alias set x2 https://<S3_DNS> '<ACCESS_KEY>' '<SECRET_KEY>'
xc mb x2/proxy-smoke
xc cp ./large-object.bin x2/proxy-smoke/large-object.bin
xc stat x2/proxy-smoke/large-object.bin

Forwarded-header handling

Without a CIDR allowlist, a valid forwarded header is accepted from any peer. When the optional allowlist is configured, a request carrying forwarded headers from an address outside that list must return HTTP 400.

Operational checks

Confirm every upstream passes readiness, large uploads do not buffer to load-balancer disk, WebSocket upgrades succeed, and requests continue without session stickiness when one X2 node is drained.

Load-balancer validation complete

Admit public traffic only after DNS, certificate names, upstream verification, optional load-balancer CIDRs, console login, and signed S3 operations all pass.

Continue to operations →