vLLM's --api-key Hides a Risk: Locking Down Unauthenticated Endpoints with a Whitelist Proxy

vLLM's --api-key only protects some endpoints; use a reverse proxy, loopback binding, and version updates for defense in depth.
vLLM's --api-key only applies to endpoints under the /v1, /v2, /inference, and /cohere prefixes, leaving sensitive routes like /invocations, /tokenize, /pause, and /update_weights completely unauthenticated. Since vllm serve listens on all interfaces by default, anyone who can reach the port can consume GPU resources or interrupt others' requests. This article presents the officially recommended layered approach: bind the service to loopback with --host 127.0.0.1, store the key in the VLLM_API_KEY environment variable, then use a Caddy or nginx reverse proxy to allow only the endpoints your application actually calls, returning 404 for everything else. It also emphasizes verifying each layer, noting that inter-node communication is a separate trust domain requiring network isolation, and that a proxy can't replace version updates — 0.31.0 has fixed issues from over 30 security advisories.
If you thought running vllm serve --api-key ... automatically turned your server into a private, locked-down instance, vLLM's official security guide will bring you back to reality. The guide explicitly states that the API key only applies to endpoints under the /v1, /v2, /inference, and /cohere path prefixes, while "many other sensitive endpoints exposed on the same HTTP server have no authentication enforcement at all." The official docs even warn outright: "Do not rely solely on --api-key to secure access to vLLM." This means a service that appears to be locked still has plenty of backdoors wide open.

Unprotected Endpoints: More Dangerous Than You Think
The vLLM security guide lists routes accessible without an API key, and these endpoints are far from trivial side features.
/invocations: Provides exactly the same inference capability as/v1. Attackers can bypass authentication and directly consume your GPU resources./pooling,/classify,/score,/rerank,/tokenize,/detokenize: Core functions involving model inference and text processing./pause,/resume,/abort_requests,/update_weights: These are administrative control endpoints that can pause the server, cancel other users' requests, and even update model weights.
The problem isn't just missing authentication. vllm serve listens on all network interfaces by default unless --host is explicitly specified. Its launcher binds to (args.host or "", args.port), where an empty string represents all interfaces. Docker's -p 8000:8000 mapping likewise exposes all interfaces on the host. The net result: anyone who can reach port 8000 — with or without a key — can run prompts on your GPU, pause the server, or cancel others' requests.
vLLM's HTTP service is driven by FastAPI, with routes mounted in prefix-based groups. The --api-key authentication logic is actually a middleware or dependency attached to specific path prefixes (/v1, /v2, /inference, /cohere) rather than a global middleware — this is the root of the problem. Any route registered without one of these prefixes never goes through an authentication check. /invocations is dangerous because it's an inference entry point outside those prefixes, functionally equivalent to /v1/completions; /pooling, /tokenize, and similar routes are model preprocessing and inference helper endpoints; /pause, /update_weights, and the like are administrative control planes. In other words, vLLM registers its "API compatibility layer" and its "internal service interfaces" in the same process and on the same port. Authentication covers only the former; the latter are reachable by default. This is why the official docs repeatedly stress never exposing vLLM directly to untrusted networks.
Official Recommended Defense: A Whitelist Reverse Proxy
The vLLM security guide calls "a reverse proxy that only allows specific endpoints" the "most effective approach." Here's a minimal implementation.
Step 1: Bind to localhost and store the key in an environment variable
export VLLM_API_KEY="$(openssl rand -hex 32)" # Keep a copy for the client vllm serve Qwen/Qwen3-8B --host 127.0.0.1 --port 8000 --disable-fastapi-docs
Using the VLLM_API_KEY environment variable instead of the --api-key argument avoids the key appearing in the process list, where other users on the machine can see it. Even with a proxy configured, keep this key in place in case a proxy misconfiguration exposes /v1 directly. Also, never set VLLM_SERVER_DEV_MODE=1 on an accessible server — it introduces the /collective_rpc endpoint, which the official guide calls "extremely dangerous."
If you're using Docker, publish the port with -p 127.0.0.1:8000:8000, or simply don't configure ports: for the vLLM service and only publish the proxy port. Note that on Linux, Docker-published ports bypass ufw and firewalld rules.
--host 127.0.0.1 makes vLLM listen only on the loopback interface, so external traffic must pass through the reverse proxy first. Loopback binding combined with a proxy whitelist solves both problems at once — "missing authentication" and "interface exposure": even if certain endpoints have no key validation, the outside world can't reach them directly, only through the proxy's whitelisted paths. Using the VLLM_API_KEY environment variable instead of the --api-key command-line argument matters because command-line arguments appear in ps aux, /proc/<pid>/cmdline, and similar locations, where other users on the same machine can read them directly. In Docker scenarios, -p 127.0.0.1:8000:8000 works the same way, publishing the port only to the host's loopback interface. Additionally, on Linux, Docker's port-publishing rules bypass host firewalls based on netfilter, such as ufw and firewalld. So you can't rely on the system firewall to block direct connections — you must restrict the port to loopback at publish time or simply not publish it at all.
Step 2: Configure an endpoint whitelist with Caddy or nginx
Taking Caddy as an example, the core idea is to let through only the routes your application actually calls and deny everything else.
llm.example.com { @api path /v1/chat/completions /v1/completions /v1/embeddings /v1/models @api_with_key { path /v1/chat/completions /v1/completions /v1/embeddings /v1/models header Authorization "Bearer PASTE_YOUR_KEY_HERE" }
handle @api_with_key {
reverse_proxy 127.0.0.1:8000 {
flush_interval -1
}
}
handle @api {
respond "Missing or wrong API key" 401
}
handle {
respond 404
}
}
Caddy's matcher rules require that different match types within the same named matcher must all be satisfied, so @api_with_key requires both the path to be in the allowlist and the request header to match exactly. Handle blocks execute in the order they're written, with the first match taking effect, and blocks without a matcher run last. The path directive uses exact matching, so /invocations, /tokenize, /pause, and /metrics all return 404.
If you're more familiar with nginx, the equivalent configuration uses a map directive to validate the Authorization header, then uses regex in a location block to restrict paths. The core logic is identical: only allow whitelisted endpoints through, deny everything else.
Caddy's matcher semantics are key here: multiple match types within the same named matcher are ANDed by default, so @api_with_key must satisfy both "path is in the whitelist" and "Authorization header exactly equals the specified value." handle blocks are evaluated in order, and the first match wins. So by putting the block with the key first, the fallback block without the key second, and a catch-all block with no matcher last, you get the decision order: "valid key and valid path → allow; valid path but no key → 401; everything else → 404." The path directive is exact matching rather than prefix matching, so paths not in the list — /invocations, /tokenize, /pause, /metrics — fall through to the catch-all block and return 404. In the nginx approach, a common alternative is map $http_authorization $authorized for header validation, plus regex such as location ~ ^/v1/(chat/completions|completions|embeddings|models)$ to restrict paths — logically identical to Caddy.
Verify That Every Layer of Defense Is Working
After configuration, you must verify from the outside that protections are actually effective.
Without key: should return 401
curl -s -o /dev/null -w "%{http_code}\n" https://llm.example.com/v1/models
With key: should return model info
curl -H "Authorization: Bearer YOUR_KEY" https://llm.example.com/v1/models
Access a route not covered by --api-key: should return 404
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://llm.example.com/invocations
Connect directly to vLLM from another machine: should fail
curl --max-time 5 http://<server LAN IP>:8000/version
If the last command returns a response, the proxy can be bypassed. In that case, check the --host argument, Docker's ports: configuration, and use ss -tlnp | grep 8000 to confirm the listening state.
What the Proxy Can't Cover: Inter-Node Communication and Software Updates
A reverse proxy can protect HTTP-layer access, but vLLM's inter-node communication is an entirely different trust domain. The official guide notes that inter-node traffic (PyTorch distributed, KV cache transfer, parallel computation) is insecure by default, and PyTorch's TCPStore listens on all interfaces. Multi-node deployments should keep it on an isolated network and set VLLM_HOST_IP to a private address.
If you're running disaggregated serving with P2pNcclConnector on versions 0.9.2 through 0.23.x, there's a disclosed security vulnerability (GHSA-vfp2-c8pq-v6h6) worth noting: it allows the X-Request-Id header to leak KV cache data, and the proxy configuration above passes that header through unchanged.
More importantly, a proxy can't replace software updates. Users with a valid key can still access inference code, and most of the 30-plus security advisories vLLM has disclosed since September 12 target that code — most have no CVE number and aren't in the OSV database. Version 0.31.0 fixes all of them.
Security is a defense-in-depth system: bind to loopback, use a proxy whitelist, protect inter-node communication with network isolation, and update promptly. No single layer alone is enough to let you rest easy.
This article is based on the vLLM security guide, CLI reference, Docker guide and launcher source code, as well as the official Caddy documentation.
vLLM's multi-node inference (tensor parallelism, pipeline parallelism, disaggregated prefill/decode) relies on PyTorch's distributed communication primitives. PyTorch uses TCPStore as its rendezvous service by default, which binds to all interfaces and has no built-in authentication or encryption; KV caches, weight shards, and control messages transmitted between nodes are likewise plaintext by default. This communication uses a custom binary protocol and doesn't go through the HTTP layer, so a reverse proxy has no way to intervene. An isolated network (VPC subnet, private segment, dedicated interconnect) is the only boundary mechanism — combined with VLLM_HOST_IP to bind the process to a private address and prevent the service from automatically selecting a public-facing NIC. The X-Request-Id header leak in GHSA-vfp2-c8pq-v6h6 illustrates that proxies pass request headers through by default, and attackers can exploit this to exfiltrate data via a KV cache side channel — the fix is to use a patched version or strip untrusted X-Request-Id headers at the proxy layer.
One-line Summary
vLLM's --api-key only protects some endpoints; use a reverse proxy, loopback binding, and version updates to build defense in depth.
Paragraph Summary
vLLM's --api-key only applies to endpoints under the /v1, /v2, /inference, and /cohere prefixes, leaving sensitive routes like /invocations, /tokenize, /pause, and /update_weights completely unauthenticated. Combined with vllm serve listening on all interfaces by default, anyone who can reach the port can consume GPU resources or interrupt others' requests. This article presents the officially recommended layered approach: bind the service to loopback with --host 127.0.0.1, store the key in the VLLM_API_KEY environment variable, then use a Caddy or nginx reverse proxy to allow only the endpoints your application actually calls, returning 404 for everything else. It also emphasizes verifying each layer of defense, noting that inter-node communication is a separate trust domain requiring network isolation, and that a proxy can't replace version updates — 0.31.0 has fixed issues from over 30 security advisories.
Related articles

How Big Can Stars Really Get? The Physical Limits of the Largest Stars
Red supergiants are the largest stars by volume, with radii up to 2,000 times the Sun, engulfing the inner solar system. But there is no precise upper limit on stellar size—models predict up to 15,000 solar radii. The most massive blue stars are far smaller than red supergiants—biggest and heaviest are two different things.

Apple HomeView Smart Display Full Feature Breakdown: Wall-Mount or Desktop, $299
Apple launches HomeView, a $299 smart display with wall-mount and desktop options, 12MP camera for facial recognition, smart home control, music playback, and recipe step-by-step cooking. Full breakdown of watch faces, apps, privacy, and cooking integration.

Where Did the Promised New Studio Go? Craft Computing Answers After Five Years
Craft Computing's host responds to viewer questions: the new studio promised five years ago was shelved as construction costs soared from $30,000 to $75,000, and was replaced by a 168-square-foot garage conversion.