Skip to content

Reverse proxy & TLS

Axium does not terminate TLS. In anything beyond a laptop, put a reverse proxy or a load balancer in front of both the core and the ui and let it hold the certificates.

What the core needs from the proxy

Setting Value Why
TRUST_PROXY true So the core reads the real client IP and protocol from X-Forwarded-For / X-Forwarded-Proto. Without it the firewall policy and the client_ip trace column see the proxy's address.
PUBLIC_BASE_URL https://axium-api.your-domain.com Only matters for Gemini resumable uploads; otherwise the upload URL handed to the client would be http://.
Request body size ≥ MAX_UPLOAD_SIZE (default 50 MB) Otherwise the proxy rejects uploads before Axium sees them.
Read/response timeout several minutes LLM generations can take minutes; streaming keeps the connection open for the whole answer.
Response buffering off Streaming (text/event-stream) must reach the client chunk by chunk. The core already sends X-Accel-Buffering: no and Cache-Control: no-cache.
HTTP/1.1 keep-alive to upstream on Cheaper connections, correct SSE behaviour.

The ui needs nothing special; it serves static files. What matters is that its API_URL is the public HTTPS URL of the core, see Web console.

Example: nginx

server {
    listen 443 ssl http2;
    server_name axium-api.your-domain.com;

    ssl_certificate     /etc/ssl/axium/fullchain.pem;
    ssl_certificate_key /etc/ssl/axium/privkey.pem;

    client_max_body_size 50m;          # match MAX_UPLOAD_SIZE

    location / {
        proxy_pass         http://axium-core:3000;
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Connection        "";

        proxy_buffering    off;          # streaming
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    }

    # Lock the installer down once installed (see Security)
    location /v1/install {
        allow 10.0.0.0/8;
        deny  all;
        proxy_pass http://axium-core:3000;
    }
}

server {
    listen 443 ssl http2;
    server_name axium.your-domain.com;
    ssl_certificate     /etc/ssl/axium/fullchain.pem;
    ssl_certificate_key /etc/ssl/axium/privkey.pem;
    location / { proxy_pass http://axium-ui:3001; }
}

Example: Caddy

axium-api.your-domain.com {
    request_body { max_size 50MB }
    reverse_proxy axium-core:3000 {
        flush_interval -1          # streaming
        transport http { response_header_timeout 10m }
    }
}

axium.your-domain.com {
    reverse_proxy axium-ui:3001
}

Caddy sets X-Forwarded-For and X-Forwarded-Proto by default and obtains certificates automatically.

Example: Traefik (Docker labels)

services:
  core:
    image: quay.io/axiumlab/core:v0.1.0
    environment:
      TRUST_PROXY: "true"
      PUBLIC_BASE_URL: https://axium-api.your-domain.com
    labels:
      traefik.enable: "true"
      traefik.http.routers.axium-core.rule: Host(`axium-api.your-domain.com`)
      traefik.http.routers.axium-core.tls.certresolver: letsencrypt
      traefik.http.services.axium-core.loadbalancer.server.port: "3000"
      traefik.http.middlewares.axium-body.buffering.maxRequestBodyBytes: "52428800"
      traefik.http.routers.axium-core.middlewares: axium-body

Traefik does not buffer responses by default, which is what streaming needs.

Cloud Run

The Google Cloud installer already sets TRUST_PROXY=true because Cloud Run terminates TLS and always rewrites the X-Forwarded-* headers. Cloud Run's request timeout defaults to 5 minutes; raise it with gcloud run services update axium-core --timeout=900 if you expect longer generations.

Mixed content

If the ui is served over https://, API_URL must be https:// too. The browser blocks an http:// API from an https:// page and the console shows a generic network error. See Web console.