Nginx Routing

MultiAgent never spreads requests by itself: each server runs its own Nginx container — {slug}-nginx, agents-nginx by default — which MultiAgent deploys, configures and updates. All of a server’s applications — agents, products, tools with a web interface and arbitrary services — are served from subfolders of one address: {base}/{slug}/. No separate domains, subdomains or hand-written configs per application: a server has one address, and every application lives in its own subfolder.

How it works

On every managed server MultiAgent raises one {slug}-nginx container (agents-nginx by default; service name nginx) — it is the Debian build of nginx (nginx:1.31), not Alpine: it starts on old kernels where the Alpine build crashes at startup. The container is installed from the server page’s Nginx tab: MultiAgent creates the config files on the server (uploaded over a secure channel), joins the container to the shared Docker network and starts it.

Before installing, MultiAgent checks whether the port nginx should run on is free. If someone else already took it — for example, a web server that has been running on port 80 on the machine for a while — nginx moves to the next free port and the progress feed says so; the panel address and all application links are then counted from the new port, and the port field in the tab updates itself. An nginx that already runs keeps its port on repeated applications — it does not drift upward. Without this check the install would simply fail on a busy port, leaving a stopped container and a closed door to the applications.

The routing config is assembled from the MultiAgent database automatically: on every change (an agent, product or tool record created, changed or deleted; a link or custom route added or edited; central login installed or removed; ports changed) the config is rebuilt and applied on its own. Applying never drops connections: the configuration is validated and reloaded inside the already-running web server, so open pages and live chats stay alive (the container is re-created only when its own settings file changes). Changing the server’s address (host, public address or URL template) also rebuilds the routes and additionally syncs the central-login settings. Installs show their steps in the progress feed; record changes apply in the background. The “Apply configuration” button in the tab runs the same update by hand — handy when an edit was made right on the server.


Where routes come from

The shared routing table is assembled automatically from five sources:

  • Agents and products — every deployed agent record (catalog products included) gets a route automatically: it appears with the record and disappears with its removal;
  • Extra links of agents and products — a link from the Links tab with a route set gets its own subfolder, leading to the chosen server port: it appears with the link and disappears with it;
  • Tools with a web interface — a tool whose template declares a web port gets a route automatically (the port comes from the template and refreshes on every deployment); without a web port the tool stays at the direct address http://{host}:{port};
  • Central login — a derived route with the fixed slug auth (the address {base}/auth/ — the login page): it appears when central login is installed from the Auth tab and disappears when it is removed; read-only, never gated, and it has no toggle of its own;
  • Custom routes — the only manually managed kind: any http(s) address (it does not have to be managed by MultiAgent — a service on its own port, a local system, an external one), with editing, disabling and deletion.

The tab lists all routes sorted by slug, with the public address and the target for each; the automatic kinds are marked “Managed by MultiAgent” and a click leads to the source record (clicking the central-login row opens the server page’s Auth tab).

Application ports are private by default: they are published only on the server’s 127.0.0.1, reachable just by the server itself and the containers of its shared docker network — exactly the path nginx takes. That is how neighboring deployments on one machine never see each other’s ports. A port is opened to the outside with the “Public port” switch (the Ports tab of an agent or an app, the port row of a tool). Always public remain only nginx’s own HTTP/HTTPS listeners and the backup service port, while the shared Postgres is always closed. That is also why a link to an app with a private port points at its address through nginx — {base}/{slug}/ — and not at the unreachable direct http://{host}:{port}. MultiAgent manages such apps itself, over its secure channel to the server.


Traffic path

Traffic flows like this: http(s)://{serverHost}/{slug}/ → the server’s Nginx → the application’s container on the internal Docker network ({container name}:{port}). Applications are addressed by container names, so nothing needs to publish ports to the outside.

  • Resolving on the fly — the config uses the internal resolver and variable addresses: Nginx starts and works no matter which applications exist. A broken, stopped or not-yet-deployed application fails only on its own subfolder; the other routes keep working;
  • WebSocket and large responses — persistent connections (the agents’ live chats), long timeouts and unlimited transfer size are supported;
  • Headers — the application receives service data about its prefix, host and scheme, so prefix-sensitive applications build correct absolute addresses; behind an external proxy, applications get the https scheme and the original host;
  • The root / — served by the server’s home page when one is installed (see below); without it the root behaves as a plain fallback page.

Protecting applications (central login)

When central login is installed on a server (the server page’s Auth tab; details — on the Servers page), protected routes pass through it: a visitor signs in once at the {base}/auth/ login page and then opens any protected application of that server without logging in again.

  • Agents and products are closed behind central login by default — a toggle in the application’s card can open them back up. Tools with a web interface and custom routes are protected by your choice: the “Behind SSO” toggle on the row. The login route itself (auth) is never gated;
  • Protection needs HTTPS: it turns on only when the front works over HTTPS — a local certificate and the HTTPS port from the Nginx tab, or the server’s public address (or a URL template of the form https://{base}/{slug}) when TLS is terminated by an upper reverse proxy. Otherwise an installed central login stays inactive;
  • Machine paths are never gated: /{slug}/api/… and /{slug}/svelteChatHub work without signing in (the applications run their own auth there — JWT or API key), and so does the manifest file (manifest.json) — the browser requests it separately from the page, without the browser’s shared data, and without that a protected tab would show an error. Direct access at host:port, bypassing nginx, keeps the application’s own login. A stopped central login closes only its protected routes — the other subfolders keep working;
  • Identity and the secret: a protected application receives the visitor’s identity headers (name, groups, email), and agents and products additionally the X-SSO-Token secret, by which they make sure the request came through nginx (checked against SSO_PROXY_TOKEN in the agent’s environment variables); tools and custom routes do not get the secret.

The “Apply configuration” button on the “Authorization” tab does more than apply the settings — it also ends every open session: each browser has to sign in again with the current password. That way an access change really becomes visible at once. The manifest file of protected apps (see above) keeps being served — the app still installs and opens as before.

Forgejo connects to the central login by itself — no switch: as soon as the login is installed, protection is working and Forgejo is running, its login page gets a sign-in-through-central-login button, and the first sign-in creates the account from the login’s identity. The Forgejo administrator’s login and password follow the central login; the Forgejo tab has a “Sync authorization” button for that. When the conditions go away, the button disappears by itself, and Forgejo’s own accounts and its own sign-in form keep working as before.


The namespace and reserved prefixes

The first-level slug is one namespace per server, shared by agents, products, tools and custom routes: the same slug may exist on different servers, but on one server it can be claimed only once. Slugs are lowercase letters, digits and hyphens only ([a-z0-9-]+).

MultiAgent’s own service paths are reserved and can never become slugs: api, v1, swagger, servers, agents, tools, workspaces, analytics, accounts, data, licenses, models, skills, forgejo, plus a few technical ones (static, _app, manifest.json, the SignalR hubs) — and the slug auth, reserved on every server whether or not central login is installed, belonging to its route. An agent named api, servers or auth cannot be created — the form flags it immediately, and the server rejects the record.


Custom routes

A custom route points a subfolder at any absolute http(s) address reachable from the server: a service on its own port, a local application, an external system. Creating one takes a name, a slug under the namespace rules, and a target — an absolute http(s)://… address (a value without a scheme is rejected).

  • Every change (create, edit, enable, disable, delete) immediately rebuilds and applies the config;
  • A disabled route removes its subfolder — it starts answering 404, while the record stays in the table;
  • Deleting frees the slug for any kind — another agent, tool or route can claim it;
  • If the target has its own base path (for example http://host/wiki), it replaces the slug: /{slug}/docs/page reaches /wiki/docs/page.

Certificates

Certificates are managed from the same Nginx tab — three kinds:

  • Self-signed — generated by MultiAgent and serving as the fallback certificate; you can download it as a .crt file and import it into your system’s trusted certificates;
  • Your own PEM pair — pasted or uploaded; the chain and the key are validated before saving (the key must match the certificate; an expired one is rejected with a warning);
  • Let’s Encrypt — free certificates through Nginx itself (domain ownership checked by the ACME protocol): a staging (test) and a production endpoint, one order at a time, the domain must point at the server.

Key material lives only on the server (the ssl/{domain}/ folder: full chain + private key) and is mounted into the container read-only — only metadata sits in the MultiAgent database. Every config push renews certificates expiring within 30 days (self-signed ones regenerate, Let’s Encrypt is requested anew), plus there is manual per-row renewal. The HTTPS port is optional: while it is empty, only HTTP is published, and certificates are stored but not served. A successful Let’s Encrypt request with an empty port enables it by itself (443) — the certificate is served right away, while a port you chose or an explicitly disabled HTTPS is never overridden. In local mode (your own certificate and an HTTPS port) all HTTP, including route subfolders, is redirected to HTTPS — the only exception is /.well-known/acme-challenge/, which Nginx keeps answering itself. When TLS is terminated by an upper reverse proxy (the https://{base}/{slug} URL template), the HTTP listener keeps serving the routes — the browser already talks HTTPS to the proxy.


MultiAgent itself in a subfolder

MultiAgent is such an application itself: it can live not only at a domain’s root but in any subfolder (your-domain/multiagent), the same way and without a rebuild. Nothing needs configuring inside the program — MultiAgent understands where it is from the request address. There are two ways to tell it.

Through an environment variable — when the upper proxy passes the path as-is, unchanged. Just add at startup:

- MULTIAGENT_BASE_PATH=/multiagent

MultiAgent strips the prefix itself and once redirects requests typed without it to the subfolder address. The certificate check path (/.well-known/) stays at the root — so issuing keeps working. An empty value means running at the domain root.

Through a proxy header — when the subfolder is owned by the proxy itself: in its config, one line is added to the usual proxying — proxy_set_header X-Forwarded-Prefix /multiagent;. MultiAgent then reads the subfolder from the header and redirects nothing — routing belongs fully to the proxy.

Deploying on a managed server needs none of this: the configuration MultiAgent builds for it already passes the subfolder and the needed header. One domain can host several instances in different subfolders: each has its own database and its own keys, so a sign-in in one never works in another, and in the browser each subfolder keeps its own separate state.


The server home page

Nginx’s root / serves the server home page when one is installed (the server page’s “Home page” tab; requires Nginx installed). It is a static card board that MultiAgent builds from its own database and uploads to the server: one card per agent, product and tool with its address — with the projects’ real logos. Common actions are not cards but a row of icon buttons in the top-right corner: “Git” (while Forgejo runs), “QR login” (when central login is on) and always “Logout” on the far right. The page has no login of its own: with central login on, it is closed together with the other applications. Details — on the Server Home Page page.

Application subfolders always take precedence over the page’s paths: an agent with the slug settings is served at /settings/, while the home page itself keeps working at the root. If Nginx is stopped, only the root suffers — the application subfolders keep working.


The public address

Every application’s canonical public address is {base}/{slug}/. The base is derived in one place, in order: first the server’s public address (the field on the General tab); when it is empty — the base of the agents’ URL template of the form {base}/{slug} (this is how a proxied server gets https links without its own certificate); otherwise http(s)://{serverHost}[:{nginx port}]. The public address and the template are separate on purpose: the template primarily defines how MultiAgent itself reaches the agents’ APIs, while the public address only says where the human-facing links point. The scheme follows one rule: any certificate + an enabled HTTPS port → https, else http. Direct access http://{host}:{port} stays the fallback path and is labeled “direct access” in the interface; for apps with a private port it does not work, and the link points at the address through nginx.

← Back to documentation