Routing & HTTP
Mothership sits in front of your fleet as the HTTP layer: external listeners (binds) accept traffic, routes match requests to ships and bays, and built-in middleware handles static files, compression, and CORS preflight caching.
Named binds
Section titled “Named binds”Binds are external listeners declared under [mothership.bind]. Route bind values must match one of these keys:
[mothership.bind]http = "0.0.0.0:80"https = "0.0.0.0:443"ws = "0.0.0.0:8080"
# Bind values support env expansion# http = "0.0.0.0:$PORT"ws is just a bind name, not a special protocol type — every bind is a normal TCP or Unix listener.
PROXY protocol
Section titled “PROXY protocol”Behind a load balancer (AWS ELB/ALB, Cloudflare Spectrum, HAProxy, nginx), enable PROXY protocol to preserve client IPs:
[mothership.bind]http = "0.0.0.0:80" # direct accesshttps = { addr = "0.0.0.0:443", proxy_protocol = true } # behind LBThis enables HAProxy PROXY protocol v1/v2 with auto-detection — it works with or without the protocol header. On the load balancer side: AWS target groups → “Proxy Protocol v2”, HAProxy → send-proxy-v2, nginx → proxy_protocol on.
Routes
Section titled “Routes”Routes map binds to ships/bays with regex patterns, matched in declaration order:
# Object formatroutes = [ { bind = "http", pattern = "/api/.*" }, { bind = "ws", pattern = "/cable" },]
# Shorthand formatroutes = ["http:/api/.*", "ws:/cable"]
# With path strippingroutes = [ { bind = "http", pattern = "/api/.*", strip_prefix = "/api" },]If a route references a bind name that isn’t defined in [mothership.bind], mothership skips the route and logs a warning.
User-Agent routing
Section titled “User-Agent routing”Route traffic to different backends based on User-Agent class:
# Browsers get the full app[[fleet.web]]name = "app"command = "rails"routes = [{ bind = "http", pattern = "/.*", ua_filter = "browser" }]
# LLM agents get markdown[[fleet.web]]name = "markdown-api"command = "rails"routes = [{ bind = "http", pattern = "/.*", ua_filter = "llm" }]
# Bots and crawlers get the static cache[[fleet.web]]name = "static-cache"command = "nginx"routes = [{ bind = "http", pattern = "/.*", ua_filter = "bot" }]Available filters:
| Filter | Matches |
|---|---|
browser |
Chromium, Firefox, Safari browsers |
chromium, firefox, safari |
A specific browser family |
llm |
Claude/Anthropic agents |
bot |
Bots, crawlers, curl, wget, etc. |
~pattern |
Custom regex (e.g. ~MyAgent.*) |
Put specific UA filters before catch-all routes — matching is in declaration order.
Shields (HTTP fingerprinting)
Section titled “Shields (HTTP fingerprinting)”Mothership computes Ja4H fingerprints for incoming requests. Ja4H fingerprints HTTP header order and values, which detects bots and headless browsers even when they spoof User-Agent. Fingerprints are logged with each request:
DEBUG method=GET path=/ ua_kind=Chromium shields=Some("ge11nn06enus_...")Shield-based routing (block or redirect suspicious fingerprints) is planned but not implemented yet.
Static files
Section titled “Static files”Static directories are served before routing, with fallthrough: longest prefix wins, and if the file isn’t found the request falls through to routes.
[[mothership.static_dirs]]path = "./public/assets"prefix = "/assets"
[[mothership.static_dirs]]path = "./public"prefix = "/"bind = "http" # optional: limit to a specific bindCompression
Section titled “Compression”[mothership]compression = trueResponses are compressed with gzip, deflate, or brotli based on the client’s Accept-Encoding.
CORS preflight cache
Section titled “CORS preflight cache”Cache backend OPTIONS responses per-origin to keep preflight storms off your app servers:
[mothership]cors_cache = true # enable with defaults
# Or with custom settings[mothership.cors_cache]enabled = truedefault_ttl = 3600 # fallback TTL in seconds (default: 3600)max_entries = 10000 # maximum cache entries (default: 10000)The cache key includes bind name, host, origin, path, Access-Control-Request-Method, and Access-Control-Request-Headers. TTL is taken from the backend’s Access-Control-Max-Age header when present.
Rate limiting
Section titled “Rate limiting”Optional protection against request floods — off by default:
[mothership.rate_limiting]global_rps = 10000.0 # global requests per secondper_ip_rpm = 100.0 # per-IP requests per minuteLimited clients receive 429 Too Many Requests with a Retry-After header.