Self-hosted servers
A PassthroughModel can front an OpenAI-compatible server that you run on
another machine, such as llama.cpp on a Mac Studio or vLLM on a workstation.
Users keep the shared endpoints, their minted API keys or Keycloak JWTs, and
group access control. The server’s own API key stays with the platform.
How the connection works
Section titled “How the connection works”A request crosses two TLS connections:
client ──TLS──▶ gateway (llm.<baseDomain>) ──TLS──▶ self-hosted serverThe gateway always uses TLS toward the provider and verifies the server’s
certificate against spec.provider.hostname. By default it trusts only the
public CA roots built into the Envoy proxy image. If the server’s certificate
comes from a private or corporate CA, set spec.provider.tls.caConfigMapName
so the gateway trusts that CA. The server sees only the platform key: per-user
identity, access groups, and key revocation are enforced at the gateway.
Requirements
Section titled “Requirements”- Reachable from the cluster. The gateway’s Envoy proxy pods must reach the server on its port. Nothing else needs access.
- A DNS name, not an IP address. Envoy Gateway rejects IP addresses and single-label names as backend hostnames. Cluster DNS must resolve the name to the server; avoid a zone that your cluster DNS answers with the gateway address.
- A TLS certificate for that name, issued by a public CA or by a CA you give the cluster (SHA-256 or stronger recommended).
- An OpenAI-compatible API that requires an API key, sent as
Authorization: Bearer <key>, and answering to a fixed model id.
Set up the server
Section titled “Set up the server”The details depend on your hardware and serving framework; see the example below for one complete setup.
- Install the certificate on the host. Get a certificate for the server’s hostname and place it, with any intermediate CAs, on the host along with its private key. Generating the key on the host and sending your CA only a signing request keeps the key from ever leaving it.
- Serve HTTPS with it. Pass the certificate and key to your serving
framework. llama.cpp’s
llama-serverand vLLM accept them directly; for a server that does not, put a TLS-terminating reverse proxy in front of it. - Require an API key, and set the model id that clients will send.
- Keep it running. Start the server on boot, restart it on failure, and open its port to the cluster’s nodes.
Connect it to the cluster
Section titled “Connect it to the cluster”Create the API key Secret and the CA ConfigMap in the PassthroughModel’s namespace, which is the operator namespace. Command substitution strips a trailing newline that would otherwise become part of the key:
kubectl create secret generic macstudio-api-key -n nebari-llm-serving-system \ --from-literal=apiKey="$(cat api-key)"kubectl create configmap corp-ca -n nebari-llm-serving-system \ --from-file=ca.crt=corp-root-ca.pemThe ConfigMap must hold the CA under the ca.crt key or as its only key. A
trust-manager Bundle target that already projects your organization’s CAs into
the namespace qualifies; reference it by name instead. The bundle replaces
the public roots for this provider only, and Envoy Gateway watches the
ConfigMap, so valid CA updates take effect without a restart. Skip the
ConfigMap and spec.provider.tls if a public CA issued the certificate.
An existing cluster needs the pack release that adds spec.provider.tls to the
PassthroughModel CRD, and the matching operator, before you apply the
manifest. An older CRD drops or rejects the field, and an older operator keeps
trusting only the public roots. Helm does not upgrade installed CRDs; see
Upgrading.
Apply the PassthroughModel, declaring each model id the server answers to:
# PassthroughModel routing the shared LLM endpoints to a self-hosted
# llama.cpp server (llama-server) on another machine, e.g. a Mac Studio.
# Hostname, port, and model id are illustrative.
#
# Prerequisites (see the Self-hosted servers guide):
# - llama-server serving HTTPS at macstudio.corp.example.com:8443 with a
# certificate for that name, --alias qwen3-30b-a3b, and
# --api-key-file holding the key below
# - A Secret holding that same key:
# kubectl create secret generic macstudio-api-key -n nebari-llm-serving-system \
# --from-literal=apiKey="$(cat api-key)"
# - A ConfigMap holding the CA that signed the server certificate:
# kubectl create configmap corp-ca -n nebari-llm-serving-system \
# --from-file=ca.crt=corp-root-ca.pem
apiVersion: llm.nebari.dev/v1alpha1
kind: PassthroughModel
metadata:
name: macstudio
namespace: nebari-llm-serving-system
spec:
provider:
hostname: macstudio.corp.example.com
port: 8443
credentialSecretName: macstudio-api-key
tls:
# Trust the corporate CA instead of the gateway's public roots.
caConfigMapName: corp-ca
models:
declared:
# Must match the llama-server --alias so clients and the server agree.
- qwen3-30b-a3b
access:
groups:
- llmThe key-manager lists the model next to served models, so users mint keys for it the usual way.
Verify
Section titled “Verify”kubectl -n nebari-llm-serving-system get passthroughmodel macstudiokubectl -n nebari-llm-serving-system get backendtlspolicy macstudio-backend-tls \ -o jsonpath='{range .status.ancestors[*].conditions[*]}{.type}={.status} {.reason}: {.message}{"\n"}{end}'
curl https://llm.your-cluster.example.com/v1/chat/completions \ -H "Authorization: Bearer sk-your-api-key" \ -H "Content-Type: application/json" \ -d '{"model": "qwen3-30b-a3b", "messages": [{"role": "user", "content": "Hello"}]}'The PassthroughModel reports Ready once its resources are applied, which does
not mean the server is reachable. The BackendTLSPolicy status reports a
missing or empty CA ConfigMap, but Accepted=True does not prove Envoy loaded
the certificate bundle. Malformed PEM can be accepted by the policy while
Envoy rejects the update and continues using its previous valid trust bundle.
After rotating a CA, check the Envoy proxy logs for rejected updates and test
a request against the server presenting its new certificate. A successful
request against the old certificate alone does not prove rotation succeeded.
For log commands and handshake failures, see
Troubleshooting.
Example: llama.cpp on a Mac Studio
Section titled “Example: llama.cpp on a Mac Studio”This is the setup the example manifest above describes: llama-server at
macstudio.corp.example.com:8443, with a certificate from a corporate CA.
Install llama.cpp with Homebrew (brew install llama.cpp), which builds it
with the OpenSSL support HTTPS needs. Generate an API key, then start the
server with the certificate, key, and API key file:
openssl rand -hex 32 > api-keychmod 600 api-key macstudio.key
llama-server -hf <hf-user>/<model>-GGUF:Q4_K_M \ --alias qwen3-30b-a3b \ --host 0.0.0.0 --port 8443 \ --ssl-cert-file macstudio.crt \ --ssl-key-file macstudio.key \ --api-key-file api-key \ --no-ui--aliassets the model id; it matchesdeclaredin the manifest.--api-key-filemakes every endpoint except/healthrequire the key.--no-uiturns off the built-in chat page, whose assets do not require the key.
For an always-on service:
- Run it under launchd. A LaunchDaemon with
RunAtLoadandKeepAlivestarts it at boot and restarts it if it exits. - Stop the Mac from sleeping. Use System Settings → Energy, or
sudo pmset -a sleep 0. A sleeping Mac is an outage for this model. - Allow incoming connections to
llama-serverin the macOS firewall.
From any machine that trusts the CA, confirm TLS and the key before connecting the cluster:
curl --cacert corp-root-ca.pem \ -H "Authorization: Bearer $(cat api-key)" \ https://macstudio.corp.example.com:8443/v1/models