Skip to main content

Troubleshooting

Common issues and how to resolve them when deploying the Tunnel Agent.

How routing works

Understanding the request flow helps diagnose issues:

Zihin Cloud sends a request to:
tunnel.zihin.ai/t/{tenant_id}/{service}/{path...}
────────── ──────── ─────────
1. identifies 2. service 3. forwarded
the tunnel name to the agent

Agent receives: { service: "my-service", path: "/api/data" }
Agent resolves: "my-service" → base URL from config
Agent fetches: GET https://internal-host:8080/api/data

The path from the cloud endpoint is appended to the base URL configured in the agent. The agent only provides host and port — the path comes from the request.

Error reference

404 — check the response body

Two different problems return 404 — the body tells them apart:

{"error":"No tunnel found for tenant"} — the agent is not connected.

Possible causeSolution
Token (ztun_...) is incorrect or expiredVerify ZIHIN_TOKEN environment variable, request a new token
Agent is not runningCheck with docker ps or systemctl status
Firewall blocking outbound WSS on port 443Allow outbound HTTPS/WSS to tunnel.zihin.ai

{"error":"Service not found on tunnel: ..."} — the agent is connected, but no service with that name is registered.

The service name in zihin-agent.yml must match the {service} segment in the cloud endpoint URL:

Cloud endpoint: /t/{tenant}/crm/mcp/...
───
Agent config: services:
- name: crm ← must match

401 — "Unauthorized"

Two possible sources — check the response body to distinguish:

SourceResponse bodyAgent logsCause
Tunnel Server{"error":"Unauthorized"}No log (request doesn't reach agent)Invalid or missing Bearer token
Internal serviceHTML or JSON from the serviceRequest completed { status: 401 }Service credential (e.g., X-Api-Key) is invalid

If the response body is {"error":"Unauthorized"}, the issue is at the Tunnel Server level — verify that the cloud service is sending the correct Authorization: Bearer header.

502 — "Bad gateway"

The agent is connected, but the local fetch to the internal service failed.

Possible causeSolution
Internal service URL is unreachable from the containerTest connectivity from inside the container (see below)
Self-signed SSL certificateMount the CA certificate and set NODE_EXTRA_CA_CERTS (see Getting Started)
Wrong port for the internal serviceVerify the correct port
Internal service rejected the authentication tokenTest the token directly with curl from the server

Diagnosis: Set log_level: debug in the agent config. The log line Fetch failed { code, error } shows the exact error.

504 — "Gateway timeout"

The internal service took longer than 30 seconds to start responding.

  • Check the health of the internal service
  • Avoid large queries without filters or pagination
  • Real-time (SSE) responses are not subject to this limit from agent 1.2+ — they stream for as long as needed

406 — "Not Acceptable" (MCP services)

An Accept header in inject_headers is overriding the one the MCP client sends.

Remove Accept from the service's inject_headers and restart the agent — MCP clients already send the correct value. The agent logs a warning at startup when an Accept header is configured. Use inject_headers for credentials only.

Authentication errors (4001, 4003)

CodeMeaningSolution
4001Invalid or expired tokenCheck ZIHIN_TOKEN, request a new token
4002Connection limit for your tunnel reachedHarmless — the extra connection waits and retries automatically. To use more, lower pool_size or ask the Zihin team to raise your tunnel's limit
4003Tunnel not found on the serverContact the Zihin team to verify provisioning

Diagnostic commands

Test from inside the Docker container

If the agent returns 502, test whether the container can reach the internal service:

# Test basic connectivity
docker exec zihin-tunnel wget -qO- --no-check-certificate https://INTERNAL_HOST:PORT/

# If this fails, the container can't reach the service.
# Solution: add network_mode: host to docker-compose.yml

Test the internal service directly (from SSH)

# Test the service endpoint with authentication
curl -sk -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
https://INTERNAL_HOST:PORT/api/endpoint

Enable debug logs

# In zihin-agent.yml
log_level: debug

Or via environment variable:

export ZIHIN_LOG_LEVEL=debug

Restart the agent after changing the log level. Remember to set it back to info after troubleshooting.

Common issues

SymptomLikely causeSolution
Reconnect loopFirewall blocking outbound 443Allow outbound HTTPS/WSS to tunnel.zihin.ai
Agent token is requiredZIHIN_TOKEN not setSet the environment variable before starting
No services definedYAML missing services blockAdd at least one service to zihin-agent.yml
HOST_NOT_ALLOWEDHost policy restricts accessContact the Zihin team to adjust allowed_hosts
RATE_LIMITEDToo many requests per minuteReduce request frequency or request a limit increase
Response timeoutInternal service slow or unreachableVerify the service URL is accessible from the agent machine

HTTP status codes — quick reference

HTTPMeaningWhere it failed
200Success
401Invalid Bearer token or service credentialCloud → Tunnel Server (check response body for {"error":"Unauthorized"}) or Agent → Internal service
404Agent offline or tunnel doesn't existTunnel Server
406Accept header injected for an MCP serviceAgent config — remove Accept from inject_headers
429Rate limit exceededTunnel Server or Agent
502Agent connected, local fetch failedAgent → Internal service
503Too many simultaneous streaming (SSE) connectionsTunnel Server — close unused streams or contact the Zihin team
504Timeout (>30s to first byte)Agent → Internal service

Support

If you need help:

  • Send logs with debug level to the Zihin team
  • For a failing request, include the X-Tunnel-Request-Id response header — every tunnel response carries one, and it lets the team trace that exact request end to end
  • Include the agent version: docker exec zihin-tunnel node dist/index.js --version
  • Describe your setup: number of services, network type, corporate proxy (if any)