WebSocket proxying with Apache APISIX

A planning guide to routing WebSocket connections through Apache APISIX, covering route setup, authentication at upgrade time, long-lived connection limits, 3.19 metrics labels and sticky sessions.

On this page

How Apache APISIX proxies WebSocket connections for your applications

Apache APISIX can carry WebSocket connections on the same gateway that serves your REST traffic, so the upgrade request passes through the same routing and plugin controls as any other API call. The repository lists WebSocket among the protocols APISIX proxies, alongside HTTP and HTTPS, gRPC, MQTT and TCP/UDP. Teams running chat, live dashboards, notifications, trading or device feeds need this page when they want those long-lived connections governed at the edge instead of exposed directly to backend services. The decisions that matter are which routes accept upgrades, what identity is checked before the connection opens, how long connections may stay open, and how sessions are pinned to a backend.

APISIX handles the WebSocket upgrade as an HTTP request first, so the route definition, its upstream and any authentication plugins apply at the handshake. According to the APISIX plugin development documentation, plugins for ws and wss upstreams can run at the ws_handshake, ws_client_frame, ws_upstream_frame and ws_close phases. That gives you a documented point to reject an unauthenticated upgrade and a documented point to observe or act on frames and closure. Confirm with your platform team which bundled plugins support these phases in the release you select, because plugin coverage is not uniform across the more than 100 built-in plugins. Plan the build around the handshake: a connection that is accepted once stays open until either side closes it, so a token that expires mid-session is not re-checked unless your design adds that step.

Setting up a WebSocket route in APISIX step by step

This sequence takes a WebSocket backend from a confirmed upstream scheme to a route that completes the upgrade handshake, requires authentication, and reports traffic under the WebSocket metrics label. Each step ends with a check your team can run before moving on.

  1. Confirm the upstream scheme and endpoint

    Your platform or application team confirms whether the backend accepts plain WebSocket (ws) or WebSocket over TLS (wss), along with the host, port and path clients will use. Record these values and confirm the backend's expected handshake path with its owners. The step is done when someone can open a WebSocket connection directly to the backend from inside the gateway network and receive a 101 Switching Protocols response, which shows the upstream works before APISIX is involved.

  2. Create the route with WebSocket enabled

    The gateway team creates an APISIX route that matches the public WebSocket path and points to the upstream using the scheme confirmed in the previous step. Check the exact route fields, including how WebSocket is enabled on the route and how the upstream scheme is declared, against the getting-started and plugin documentation for your APISIX version before you apply the configuration. The step is done when the route is loaded and the Admin API or configuration file reflects it without errors.

  3. Add authentication and other plugins

    Security and platform owners decide how clients prove identity at connection time, for example with a key, a JWT or an OpenID Connect flow, and attach the matching plugin to the route. Authentication runs during the HTTP upgrade request, so a rejected client never reaches the backend. Confirm which additional plugins apply to WebSocket traffic in the plugin documentation, since some plugins act only on the initial request. The step is done when an request without credentials is refused and one with valid credentials is accepted.

  4. Test the upgrade with a client

    The test owner connects a WebSocket client through the gateway address, first with valid credentials and then without them, and sends a few messages in both directions. The gateway should return 101 Switching Protocols for valid requests and a rejection status for invalid ones, and the backend should echo or process the frames as it does when reached directly. The step is done when the expected success and failure cases both behave as the design specifies and the messages arrive intact.

  5. Confirm the long-lived connection settings

    The platform team reviews how long idle and open connections stay alive at the gateway and at the backend, and confirms the timeouts and connection limits your load balancer, firewall and client library expect. Set these values from the requirements of your application and check them against the APISIX configuration reference for your version. The step is done when a test connection stays open for the agreed idle period and closes cleanly when either side ends it.

  6. Verify the WebSocket metrics label

    The operations team runs a few test connections and checks the gateway metrics. From APISIX 3.19.0 onward, successful WebSocket upgrades are labeled with request_type=websocket in metrics such as apisix_http_status and apisix_http_latency, instead of traditional_http. If your deployment runs an earlier version, expect the traditional label and plan your dashboards and alerts accordingly. The step is done when the label appears on your dashboards and the series match the test traffic you generated.

Which WebSocket configuration options fit which traffic

This table compares the main WebSocket configuration choices in APISIX: upstream scheme, load balancing, handshake-time authentication and connection limits. Read each row for when the option fits and what it adds in complexity or client behavior. The main takeaway is that sticky routing and handshake checks are the choices most likely to change how clients reconnect and how the gateway is operated.

OptionWhen it fitsCost in complexity or client behavior
ws upstream (plain)Backends inside a trusted network segment where the gateway to service hop is already protectedTraffic between APISIX and the service is unencrypted, so confirm network controls and review what your assessor expects for that hop
wss upstream (TLS)Backends that require encryption end to end, or that sit outside the gateway's network boundaryCertificate trust and SNI settings must be configured and maintained for each upstream, and TLS handshakes add setup cost per connection
Round robin load balancingStateless WebSocket services where any backend can serve any reconnecting clientClients that keep session state in the server connection may lose it on reconnect, so the application must tolerate reconnection
Hash-based sticky sessionsStateful services where a client's frames must keep reaching the same backend for the life of its connectionHash keys must be chosen with care, and backend removal or scaling can move clients to another node, so plan for reconnect handling
Handshake-time authenticationPlugins that validate tokens or keys before the upgrade completes, so unauthenticated clients never open a socketBrowsers cannot always set custom headers on WebSocket requests, so confirm how tokens are passed and expire during long sessions
Frame-level checks in later phasesInspection or policy on messages after the connection is open, using the ws_client_frame and ws_upstream_frame phasesPer-frame logic adds CPU cost on busy connections, and a plugin written for these phases must be tested under realistic message rates
Connection and rate limitsProtecting backends from many idle or long-lived connections opened by one client or tenantLimits set too low disconnect legitimate users, so agree on expected concurrency and reconnect behavior before setting thresholds

Where authentication runs during the WebSocket upgrade

Authentication for a WebSocket connection happens at the handshake, and most of the control you have lives there. APISIX documents four plugin phases for ws and wss upstreams: ws_handshake, ws_client_frame, ws_upstream_frame and ws_close. The first runs once, when the client sends the HTTP upgrade request. Any plugin that checks a token, API key or client certificate should be running in that phase, so a request that fails authentication is refused before a long-lived connection exists. The developer documentation for these phases is the place to confirm which phase each plugin you plan to use actually hooks, because the phase list is a property of the plugin and of your APISIX version, not something you can assume from the plugin name.

Before the connection is established, you can check the credential, the origin, the route match and the rate of new connections. What you cannot assume is per-frame enforcement. By default, a token validated at the handshake does not expire or get re-checked while the socket stays open, so a revoked credential or a disabled account can keep sending frames until the connection closes. Whether a given plugin re-validates in ws_client_frame or ws_upstream_frame is a question to answer from its documentation and a test in your environment, not from the feature list. Plan for session lifetime explicitly: a maximum connection age, a re-authentication message from the application, or closing the socket on token expiry.

Tokens sent in query strings need particular care. Browsers cannot set custom headers on the WebSocket constructor, so teams often pass a token as a query parameter. That value then appears in gateway access logs, upstream logs and any tracing headers unless you redact it, so confirm your log format and the logging plugins you enable treat the parameter as sensitive. Prefer headers where the client can set them, keep tokens short-lived when they travel in the URL, and test that the upgrade request, not just the HTTP routes, is covered by your log redaction rules. Your security team should review this choice against your own data handling policy.

How long-lived connections change capacity and timeout planning

WebSocket connections stay open for hours, so the limits that matter are the ones that sit idle for a long time. Idle timers, file descriptors, reconnect behavior, token lifetimes, upgrade draining and per-client caps all interact at the gateway. Measure each one in your own environment before you set a value.

Idle timeouts along the path

Every hop between a client and the upstream can close a quiet socket: cloud load balancers, the APISIX data plane, NAT devices and the application server. Each may have a different idle timer. Map every timer on the path, then confirm that the gateway timeout is longer than the interval at which clients send ping frames. A shortest timer on the path decides when silent sockets drop, so it is the first value to check.

File descriptors and worker capacity

Each open WebSocket holds a client socket and an upstream socket, so connection count translates directly into file descriptor use in the NGINX workers that APISIX runs on. Measure open descriptors per worker at your expected peak, and compare them with the operating system limits and worker settings on your nodes. Ask your team to size for the number of simultaneous sockets, not for requests per second.

Reconnect storms after restarts

When a gateway node restarts, every client connected to it reconnects at nearly the same moment, and the surge can overload the remaining nodes. Test this with a realistic client population. Check whether clients use jittered exponential backoff, and whether the load balancer spreads reconnects across healthy nodes. The reconnect spike, not the steady load, often sets the node headroom you need.

Token expiry on open sockets

Authentication runs when the upgrade request arrives, so a token that expires later does not close an existing socket on its own. Decide whether sessions may outlive their tokens, and if not, define how the client re-authenticates or how the backend closes the connection. Confirm with your identity provider the token lifetime and refresh behavior, and test what happens to sockets when a user is disabled.

Graceful draining during upgrades

Upgrading or replacing an APISIX node must not cut every session at once. Plan a drain step that stops new upgrades on the node, lets existing sockets finish or close with a readable reason, and then removes the node. Measure how long your longest sessions take to end and whether your clients handle a close frame and reconnect cleanly. Include this window in your change calendar.

Per-client connection caps

A single client, a browser tab loop or a misbehaving device can hold many sockets and starve other users. Decide the maximum open connections per consumer, per IP address and per route, and set these limits in APISIX where your plugin set and version support them. Measure normal per-client counts first, since shared NAT addresses in corporate networks can make a per-IP cap block legitimate users.

What changes in metrics labels from APISIX 3.19

From APISIX 3.19.0, successful WebSocket upgrades are labeled with request_type=websocket in metrics such as apisix_http_status and apisix_http_latency, instead of the traditional_http value that earlier versions applied to them. The practical effect is that any dashboard panel, alert or recording rule filtering on traditional_http will silently stop counting WebSocket traffic after an upgrade to 3.19.0 or later. Before the upgrade reaches production, list every query that filters or groups by request_type, and decide whether WebSocket traffic should be counted with HTTP traffic, separately, or both. The `request_type` label is the key change to catch, because a panel that looks healthy can simply lose a traffic class without any error.

To adjust existing queries, start by searching your Grafana dashboards, Prometheus alert rules and recording rules for traditional_http and for any request_type matcher. Where a panel should show all HTTP-family traffic, match both values in one selector (for example, with a regular expression matcher on request_type). Where WebSocket health needs its own view, create a separate panel or alert keyed on request_type=websocket, and set its thresholds on connection-oriented expectations rather than request latency targets copied from REST. Recording rules that aggregate by request_type need the same treatment, and any rule renamed or re-keyed should be tested against a staging gateway that carries real WebSocket traffic before it is deployed.

Confirm the exact label behavior in the APISIX changelog for your target version before editing anything, since the wording of the release notes and the labels emitted by a specific build are the authoritative reference. The APISIX CHANGELOG is the place to check which metrics are affected and whether any other label values changed alongside. Record the result in your change ticket, and plan a comparison of request counts before and after the upgrade so that a drop in WebSocket volume is not mistaken for a drop in demand. The 3.19.0 release was published on September 28, 2026, according to the APISIX downloads page.

Security controls for WebSocket traffic on the gateway

A WebSocket connection starts as an ordinary HTTP request and then stays open, so the controls that matter sit at the handshake, the connection lifetime and the gateway's own configuration. The cards below cover each control and what your team should verify in its own deployment, since APISIX behavior depends on the plugins and routes you enable.

Authenticate the handshake

The upgrade request is the only point where a client presents an HTTP credential, so authentication has to run on that route before the connection is accepted. Confirm which authentication plugin runs on WebSocket routes, whether it reads tokens from headers, cookies or query strings, and whether browser clients can send the header you expect. Test with an invalid token and confirm the upgrade is refused rather than accepted and later dropped.

TLS on client and upstream legs

Clients should reach the gateway over wss and the gateway should reach WebSocket upstreams over wss when the backend supports it, so that tokens and messages are not exposed on either hop. Check the certificate chain and hostname validation on the upstream side, and confirm that the scheme in the route's upstream definition matches the backend's actual listener. A mismatch is a common cause of failed upgrades that teams then fix by disabling verification.

Origin and header trust

Browsers send an Origin header on WebSocket handshakes, but servers must decide which origins are allowed, since the browser does not enforce that policy on the server's behalf. Decide which origins your route accepts, and make sure forwarded headers such as client IP or authenticated identity are set by the gateway and not passed through from the client. Test a handshake from an unlisted origin and a request that spoofs identity headers.

Connection and message rate limits

A long-lived socket keeps a worker, an upstream connection and memory allocated for hours, so a few clients can consume capacity that short requests would have released. Set limits on concurrent connections per consumer or client address, and on message rates where the traffic pattern allows it. Load test with the expected number of idle and busy sockets, then confirm that limits reject new connections cleanly and that existing sessions are not silently cut off.

Protect the Admin API and configuration

Routes, plugins and upstreams for WebSocket traffic are defined through the Admin API and the configuration store, so anyone who can change them can change how sockets are authenticated and routed. Restrict the Admin API to management networks, change the default admin key, and control who can edit WebSocket routes through your normal change process. Confirm that a configuration change to a WebSocket route is logged and reviewable.

Log upgrade failures for audit

Failed handshakes show where authentication, origin checks or upstream connections go wrong, and they are often the first evidence of abuse. Decide which fields to log for refused upgrades, such as client address, route, consumer identity and status code, and send them to your log store with retention that matches your policy. Check that successful upgrades and refused upgrades can be separated in queries, and that metrics for WebSocket traffic use the request type label introduced in APISIX 3.19.0.

Questions teams ask before proxying WebSocket through APISIX

How much more effort is a WebSocket route than a REST route in APISIX?

A WebSocket route takes more effort than a REST route mainly because the gateway handles a long-lived connection rather than a short request and response. The route definition itself is similar: you enable WebSocket on the route and point it at an upstream. The extra work sits in the decisions around that route: how clients authenticate at the upgrade, how idle and long-lived connections are bounded, how sessions stay on one upstream node, and how you prove that frames flow correctly under load. Teams that already run REST routes with a clear authentication model usually find the route change small and the testing and capacity work larger.

Can our own team set up WebSocket proxying without a partner?

Yes, many teams can, provided they already operate APISIX or have an engineer who can read its configuration and plugin documentation. The configuration steps are documented in the project's own materials, and a small proof of concept against a test upstream is a reasonable first step. Where in-house work tends to stall is in the surrounding pieces: a realistic WebSocket test client, dashboards that use the request_type labels correctly, load tests that hold connections open for the expected duration, and a written decision on authentication and connection limits. Ask yourself whether those skills exist on the team before deciding.

What does our team need to provide for a WebSocket engagement?

Plan for three inputs from your side. First, the clients and upstreams: the application that opens the socket, the service behind it, and a way to run realistic WebSocket clients in a test environment. Second, observability access: the metrics store and dashboards where APISIX metrics land, so labels such as request_type can be checked and alerts can be written. Third, network paths: firewall rules and routing from the gateway to each upstream, and from clients to the gateway, including any load balancer or ingress in front of APISIX that must keep connections open. Confirm each of these early, because missing access is a common source of delay.

What drives the duration of a WebSocket proxying project?

Three factors usually drive duration. The authentication model comes first: a route that accepts an API key, a JWT verified at upgrade time, or an OpenID Connect session needs different plugin configuration, consumer design and testing. The number of upstreams comes next, since each upstream needs its own routes, health behavior and sticky session decision. Load testing is the third driver, because long-lived connections need tests that hold many sockets for a realistic period, check memory and file descriptor use, and confirm that reconnects behave correctly. Ask any partner to break its quote down along these three lines.

How do we tune long-lived connection limits in APISIX?

Tune them from measured behavior rather than from defaults you have not checked. Start by listing what the application needs: how long a healthy socket stays open, how many concurrent sockets each gateway node must hold, and what happens when a client goes silent. Then set idle timeouts and connection bounds on the route, and run load tests that match those numbers. Watch gateway memory, open file descriptors, upstream connection counts and reconnect rates during the test. Limits set too low drop healthy users, while limits set too high let idle sockets exhaust the node. Record the chosen values and the test evidence so operations teams can revisit them.

Does WebSocket proxying need sticky sessions or load balancing changes?

It depends on whether the upstream keeps state for a connection. If each socket is self-contained, any upstream node can serve it and standard load balancing may be enough. If the application holds session state in memory, a client's reconnects and messages must reach the same node, which calls for a sticky session or hash-based decision on the route. Decide this before go-live, test it with node failures, and confirm how existing load balancers in front of APISIX treat upgrade requests. Changing the balancing model later can break sessions that were working in testing.

What changes when we upgrade to APISIX 3.19 or later for WebSocket traffic?

The main visible change is in metrics. APISIX 3.19.0 labels successful WebSocket upgrades with request_type=websocket in metrics such as apisix_http_status and apisix_http_latency, where they previously appeared under traditional_http. Dashboards, alert rules and recording rules that filter on the old label will need review, or WebSocket traffic will look like it disappeared from those views. Read the project's changelog for the exact release notes, test the upgrade in a non-production environment, and compare dashboards before and after. Confirm your own behavior with a test run rather than assuming the rest of the route configuration is unchanged.

How DeployOpen scopes and delivers WebSocket routing on APISIX

DeployOpen starts WebSocket work with a written scope that names the routes, upstream services and clients that will use WebSocket, and the connection behavior each one needs. The scope document is the first deliverable: it lists which routes enable WebSocket, which authentication happens at upgrade time, how long connections may stay open, which metrics dashboards and alerts are in scope, and how sticky sessions will be handled if the backend keeps state per connection. Fixed-scope engagements agree this scope, the deliverables and the timeline in writing before build begins, so the client's own team can check each item against what their applications actually do.

The build runs inside the client's own infrastructure, in the cloud accounts, data centers or restricted networks the client already controls. DeployOpen tests upgrade handshakes with the real clients and identity provider, then checks connection behavior under the idle timeouts, load balancer settings and maximum connection counts the client plans to run. Upgrades to newer APISIX releases get the same treatment, with the metrics labels and plugin behavior compared before and after. Where a question depends on the client's environment, such as a load balancer timeout or a specific WebSocket client library, the scope names who confirms it and when.

Ongoing support is optional. Clients who want someone to review metric changes after each APISIX release, rehearse connection drains during node maintenance or keep sticky session settings aligned with application changes can add a retainer after handover. Ask any partner, DeployOpen included, to show how the written scope maps to test evidence, who owns the runbooks after handover, and how changes to WebSocket routes are reviewed and recorded.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.