9Router proxy: endpoints, keys, and errors
Understand the 9Router proxy address, local versus upstream credentials, Docker networking, streaming checks, and how to isolate 401, 429, or proxy failures.
The default 9Router client API base is http://localhost:20128/v1. A SOCKS5 egress proxy is a separate connection used by the router to reach a provider. Diagnose those two hops separately: client → 9Router, then 9Router → provider. A working local dashboard says nothing about the second hop.
The commands below use Bash/zsh, curl and, for chat payloads, jq. They are diagnostic examples reviewed against documentation, not evidence of a live provider test. Run network comparisons from the machine or container that actually runs 9Router. Do not publish credentials or raw authorization callbacks.
1. Prove the client can reach the local router
Set NINEROUTER_API_KEY locally to your router-issued key. Provider credentials belong in the router’s provider connection.
curl --noproxy '*' --connect-timeout 5 --max-time 15 -i \
http://localhost:20128/api/health
curl --fail-with-body --noproxy '*' --max-time 15 \
http://localhost:20128/v1/models \
-H "Authorization: Bearer $NINEROUTER_API_KEY"
Here --noproxy '*' deliberately bypasses any shell proxy for the local address. Expect a health response and then a JSON model list. Connection refused points to the process/address/port; a local 401 points to client authentication. HTML instead of JSON often means the wrong route or a login intermediary.
For a remote client, replace localhost with the protected address it can reach. Inside a container, localhost refers to that container. A dashboard path is not an API base, and a Chat Completions route is not automatically the right endpoint for Codex or Claude Code.
2. Compare direct and SOCKS5 egress from the router’s environment
Choose a public HTTPS URL on the actual upstream API host. The example uses OpenRouter’s host without a credential; replace it for another provider. An HTTP error can still show that DNS/TCP/TLS reached that host, but it does not prove API access. These connectivity probes do not request model generation.
PROBE_URL='https://openrouter.ai/api/v1/models'
curl --noproxy '*' --connect-timeout 5 --max-time 15 \
-sS -o /dev/null \
-w 'direct http=%{http_code} connect=%{time_connect} total=%{time_total}\n' \
"$PROBE_URL"
Only run a direct comparison where direct egress is permitted. Then substitute your actual SOCKS endpoint; 127.0.0.1:1080 is an example, not a provider service supplied by 9Router:
curl --proxy 'socks5h://127.0.0.1:1080' --noproxy '' \
--connect-timeout 5 --max-time 15 -sS -o /dev/null \
-w 'socks http=%{http_code} connect=%{time_connect} total=%{time_total}\n' \
"$PROBE_URL"
The empty --noproxy ensures a shell bypass list does not silently turn this into another direct test. socks5h delegates destination-name resolution to the proxy; socks5 resolves it locally. If you specifically suspect DNS, repeat the same command with socks5:// and compare the error. This is curl behavior, not a guarantee that your installed router supports every proxy scheme.
For a Docker router, a host-shell success is insufficient. Enter your existing container with docker exec -it 9router sh, check command -v curl, and run the probes there if curl is available. Otherwise use your existing approved container-network diagnostics; record the container test as unperformed until it is done. The proxy address must be reachable from that container. Do not copy a host-loopback address into it unchanged.
3. Read the difference before changing settings
| Result | Likely boundary and next check |
|---|---|
| Direct reaches HTTP; SOCKS cannot connect | Check the proxy listener, reachable host/port and container network |
socks5h works; socks5 reports host resolution failure | Investigate the router environment’s local DNS resolver |
socks5 works; socks5h fails resolving the target | Investigate DNS/reachability at the proxy side |
| Proxy authentication/handshake rejected | Confirm scheme and credential requirements with the proxy operator; an API key is not a SOCKS password |
| Both fail before HTTP | Check the common destination/network/TLS failure; HTTP 000 is not a provider status |
| Both reach HTTP, router still times out | Inspect the router’s actual egress setting, installed proxy support and request log |
curl exit codes help locate the failure: 5 is proxy-name resolution, 6 host-name resolution, 7 connection failure and 28 timeout. Preserve the accompanying message and elapsed time. A 15-second diagnostic cutoff is your test limit, not proof of a service outage. For an authenticated proxy, supply credentials through your private curl configuration/credential workflow; do not paste them into the example URL or a public log.
Issue #5111 is a SOCKS5 timeout report, not a confirmed universal fix. Likewise, issue #5120 shows a 503 wrapper around an upstream 401: inspect nested errors before treating every 503 as a network problem.
4. Compare nonstreaming and streaming through the same route
Set ROUTER_MODEL to one exact model ID from your local catalog, without a fallback combo. These two requests may consume provider usage. Start with a tiny non-sensitive prompt:
jq -n --arg model "$ROUTER_MODEL" \
'{model:$model,messages:[{role:"user",content:"Count from one to five."}],stream:false}' \
> /tmp/9router-proxy-probe.json
curl --fail-with-body --noproxy '*' --max-time 60 -i \
http://localhost:20128/v1/chat/completions \
-H "Authorization: Bearer $NINEROUTER_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @/tmp/9router-proxy-probe.json
jq '.stream = true' /tmp/9router-proxy-probe.json \
> /tmp/9router-proxy-stream.json
curl --fail-with-body --noproxy '*' --max-time 60 -N -i \
http://localhost:20128/v1/chat/completions \
-H "Authorization: Bearer $NINEROUTER_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @/tmp/9router-proxy-stream.json
The first should finish as JSON. For OpenAI-style chat streaming, look for text/event-stream, data: events and normal completion, commonly a final [DONE]. -N prevents curl from buffering its own output. It cannot disable buffering in another reverse proxy.
If local streaming completes but the same authenticated request through your remote HTTPS address arrives all at once, inspect that intermediary’s response buffering. If it cuts off at a repeatable elapsed time, compare proxy/server timeout settings with the request log. If even local streaming fails, inspect provider response and gateway translation first. A 60-second curl timeout is a test cutoff; a slow model can hit it without proving a broken stream.
Keep the payload and model constant while changing one hop. Redact keys before sharing status, timestamps, the first error and whether any stream events arrived. Once this small route works, restore the client’s intended endpoint and add tools or fallback separately.
References for this guide
- 9Router API and configuration
- SOCKS5 timeout report
- Cline authentication report
- curl proxy options and exit codes
Steps were checked against the linked documentation and source. Provider access and model availability depend on your account. We do not claim a completed live model test unless explicitly stated.