Engineering NotesInstallation, operations, and quality
Behind a corporate proxy, list NO_PROXY hosts by name and IP, not CIDR

When you install on-premises OwlAI on a corporate network that can reach the internet only through a proxy, calls to cloud LLMs have to follow HTTPS_PROXY out through that proxy. The same backend also calls a vLLM inference server and a speech recognition server on the internal network, and those requests have to go direct. The installer writes 10.0.0.0/8 into NO_PROXY, as they would for curl, tries the inference server with curl from the host, and it works. Then OwlAI's chat fails with a 403 or a timeout from the proxy. The backend's HTTP client (httpx) does not read CIDR entries in NO_PROXY, so it sends requests for the internal inference server to the proxy as well. On a network where the proxy relays to internal addresses too, the failure is quieter. The calls succeed, but every prompt, with the question and the query results in it, passes through the proxy, and if the inference server is plain HTTP, the proxy sees the content as is.
This post describes how on-premises OwlAI deals with that mismatch. It lays out where each of the three proxy settings actually reaches. Calls to internal sidecars and health checks skip the proxy regardless of settings, because the code turns it off. Calls that follow the proxy use httpx's own reading of NO_PROXY, and when that reading differs from what the installer meant, the preflight check and the startup log say so.
Proxy settings live in three places, and each reaches something different
On a Docker host there are three places to set a proxy. The same proxy address reaches different things depending on where you put it.
| Setting | Reaches |
|---|---|
Docker daemon (Environment=HTTPS_PROXY=… in a systemd drop-in, or "proxies" in daemon.json) |
Image pulls only |
Docker client ("proxies" in ~/.docker/config.json) |
Image builds and the environment of every container (upper and lower case) |
HTTPS_PROXY, HTTP_PROXY and NO_PROXY in the install directory's .env |
The application containers, such as the backend and the worker |
A proxy set on the daemon never reaches inside a container. That is how you get an install where images pull fine but LLM calls cannot get out. A proxy set on the Docker client goes the other way: it lands in every container, including the database, the frontend and the sidecars. Then if a single service name is missing from noProxy, calls between containers to that service go out to the proxy.
Internal calls and health checks skip the proxy regardless of settings
When the backend calls the document rendering and script execution sidecars, the HTTP client ignores proxy settings from the environment (httpx trust_env=False). No deployment needs a proxy to reach a service in the same compose project. If this were left to NO_PROXY, an internal call would go out to the corporate proxy and get a 403 the moment one service name was missing. This option also makes the client ignore SSL_CERT_FILE, so the TLS verification settings are passed explicitly (“Certificate errors are fixed with the right CA and the right name, not by turning off verification”).
Health checks work the same way. In a container where the Docker client injected proxy variables, a health check that calls 127.0.0.1 goes out to the proxy, times out, and the container turns unhealthy. The deploy script replaces services one at a time and waits for each health check, so the deploy stops there. The Python health checks therefore use a URL opener with an empty proxy handler, and the busybox wget in the frontend image turns the proxy off with -Y off. Busybox wget reads HTTP_PROXY but not NO_PROXY (busybox bug 13861), so adding 127.0.0.1 to NO_PROXY does nothing.
LLM, connector and web tool calls follow the environment's proxy
LLM SDKs, connectors (MCP servers) and the web search and web page tools follow the proxy from the environment, because some installs can reach the outside only through a corporate proxy. Internal vLLM, speech recognition and reranker servers fall on this side too. Their addresses are values each install writes into config.yaml, so the code cannot assume they are internal. That is why NO_PROXY has to cover these servers.
The client that connectors and the tools that send or fetch files by URL use resolves the address itself when it opens a connection (“A URL in a tool result stays closed if the model wrote it first”). httpx does not read proxies from the environment for a client built with its own transport, so this client rebuilds the proxy table from the environment with the same rules httpx uses and attaches it. Within one install, the same URL takes the same route no matter which client sends it.
httpx does not read CIDR entries in NO_PROXY
NO_PROXY is a convention with no standard, and each tool reads its entries differently. Here is where a request to 10.20.0.31 goes under the same NO_PROXY=10.0.0.0/8, tool by tool.
| Tool | CIDR entries | Request to 10.20.0.31 |
|---|---|---|
| curl 7.86.0 or later | Read (official CURLOPT_NOPROXY docs) |
Goes direct |
| requests 2.34 | Read | Goes direct |
| httpx 0.28 | Not read | Goes to the proxy |
| Python standard library urllib | Not read | Goes to the proxy |
httpx turns each NO_PROXY entry into a URL pattern. 10.0.0.0/8 becomes a pattern whose host is 10.0.0.0, which matches nothing but that one address. We confirmed this in the httpx 0.28.1 source (get_environment_proxies) and by running it (2026-11-03). The official httpx docs also describe NO_PROXY only as a list of hostnames and URLs. An installer who tests from the host with curl sees it pass, so the difference shows up only after OwlAI sends its first request.
httpx reads four kinds of entries.
- A name:
vllm.corp.example.comexempts that name and its subdomains. - A suffix starting with a dot:
.corp.example.comexempts subdomains only. - An exact IP:
10.20.0.31exempts that one address. *: exempts every request from the proxy.
So NO_PROXY lists the service names used between containers that the preflight check looks for, the inference servers' names or IPs, and the internal domain suffix.
NO_PROXY=localhost,127.0.0.1,postgres,render,studio,backend,versitygw,10.20.0.31,.corp.example.com
The preflight check and the startup log report the mismatch
The preflight check (“The preflight check only reads the host and leaves the fixes to a person”) reads all three settings, records in one line which ones carry a proxy, and, if any does, judges the NO_PROXY the containers will actually receive by the same rules httpx uses.
| Check | Result |
|---|---|
proxy.no_proxy_client, proxy.no_proxy_env |
WARN if localhost, 127.0.0.1 or any service name is missing |
proxy.cidr_client, proxy.cidr_env |
WARN if there are CIDR entries |
proxy.lan_endpoints |
WARN if an endpoint in config.yaml looks like an internal address but NO_PROXY does not cover it |
An address counts as internal if it is a private, loopback or link-local IP, a name with no dot, or a name ending in a suffix used on private networks, such as .internal, .local or .corp. If you turn on the proxy without replacing the placeholder addresses in config.example.yaml, this warning appears for every inference endpoint.
Proxy changes after installation are checked again when the backend and the worker start. If NO_PROXY has CIDR entries, they log net.proxy.no_proxy_cidr_ignored; if a configured inference endpoint would go out through the proxy, they log net.proxy.internal_endpoint_proxied, both as WARNING. The second one carries the provider, role and host, so you know right away which endpoint to add to NO_PROXY. An endpoint whose IP falls inside a CIDR entry in NO_PROXY also counts for this warning, since the installer clearly meant to exempt that range.
Only one case stops startup. If NO_PROXY has an entry httpx cannot read at all, every HTTP client that follows the environment fails with InvalidURL when it is created. IPv6 CIDR ranges (fd00::/8) and bracketed IPv6 addresses ([::1]) do this, even with no proxy configured. With that setting no LLM, connector or web tool call can work, so startup logs the entry and the reason in net.proxy.no_proxy_unusable and stops.
Design choices
For internal sidecar calls and health checks, we turned the proxy off in code instead of relying on NO_PROXY. Relying on NO_PROXY would let the installer manage proxy routing in one place. But one missing service name would stop the deploy at a health check, and every new sidecar would mean editing the customer's .env. The cost is that a sidecar cannot sit behind a proxy, which does not happen inside a compose project on one host.
We kept httpx's reading of NO_PROXY instead of changing it to read CIDR like curl. Changing it would let installers use the CIDR notation they are used to. But then the clients OwlAI builds and the clients libraries build internally would send the same URL by different routes, and the difference would show up as one kind of call failing strangely. The cost is that the installer has to list inference servers one by one by name or IP; if one is missing, the preflight check and the startup log name that host.
When an internal inference server would go through the proxy, we log a warning instead of blocking startup. The test for an internal address is a guess, and some networks require internal addresses to go through the proxy too. Blocking startup would keep those installs from running OwlAI at all. Only the case that is a certain failure, a NO_PROXY entry that cannot be read, stops startup.
If you run a service that uses httpx behind a corporate proxy, the first thing to check is NO_PROXY for CIDR entries, and to replace them with names, suffixes and IPs. Look at the values a container actually received with env | grep -i proxy, and call the inference server with the client your service uses, not curl, to get the same result your service will.
What problem does this solve?
More in Installation, operations, and quality

Engineering Notes · Installation, operations, and quality
The preflight check only reads the host and leaves the fixes to a person
On-premises OwlAI runs as a Docker Compose stack on a single host the customer picks.

Engineering Notes · Installation, operations, and quality
People set the Docker address pool, and the check compares it with internal ranges
Install on-premises OwlAI on a host inside a corporate network, wait a few weeks, and one day an internal data…

Engineering Notes · Installation, operations, and quality
Deploys and air-gapped loads are checked by digest, not by tag
On-premises OwlAI runs on the customer's host as nine container images.
