Skip to content

Forward raw TCP streams to a cluster, split them across weighted clusters, and route by TLS SNI without terminating TLS.

The tcp_proxy network filter forwards a connection's bytes to an upstream cluster without interpreting them. Use it for anything that is not HTTP — databases, message brokers, plain TLS passthrough — or when you want L4 forwarding with none of the HTTP machinery.

Enable it

tcp_proxy is the terminal filter of a filter chain, in place of the HTTP connection manager:

listeners:
  - name: listener_tcp
    address:
      socket_address: { address: 0.0.0.0, port_value: 8081 }
    filter_chains:
      - name: tcp_chain
        filters:
          - name: tcp
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
              cluster: backend

The filter takes a cluster and, optionally, access logs. Everything about the upstream side — endpoints, load balancing, health checks, upstream TLS — comes from the cluster.

Weighted clusters

Split connections across clusters by weight instead of naming one:

typed_config:
  "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
  weighted_clusters:
    clusters:
      - name: backend-v1
        weight: 90
      - name: backend-v2
        weight: 10

Each entry carries only name and weight; the split is per connection, not per request.

Route by SNI

Because filter chain selection happens before any filter runs, a listener with the TLS inspector can steer TLS connections to different clusters by SNI — without terminating TLS. Each chain omits a transport_socket, so the encrypted stream passes through untouched:

listeners:
  - name: listener_tls_passthrough
    address:
      socket_address: { address: 0.0.0.0, port_value: 8443 }
    listener_filters:
      - name: envoy.filters.listener.tls_inspector
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
          enable_ja3_fingerprinting: false
    filter_chains:
      - name: api_chain
        filter_chain_match:
          server_names: [api.example.com]
        filters:
          - name: tcp
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
              cluster: api
      - name: default_chain
        filters:
          - name: tcp
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
              cluster: backend

Add a transport_socket to a chain to terminate TLS at Arion instead, and combine tcp_proxy with the RBAC, connection limit, or global rate limit network filters ahead of it. The Route TCP at L4 guide builds a complete L4 front end; its production notes cover the SNI split.

TCP or HTTP?

Reach for tcp_proxy when Arion should not parse the stream: non-HTTP protocols, end-to-end TLS where the backend must see the client hello, or minimum-overhead port forwarding. Use the HTTP connection manager whenever you need per-request behaviour — routing by path or header, retries, HTTP filters, per-request logs.

Verify

With an echo server as the upstream:

printf 'ping\n' | nc -q1 127.0.0.1 8081
ping

Limits

  • One cluster or weighted_clusters per filter; the choice is fixed per chain.
  • Idle and connection-duration timeouts on the filter are not supported — bound connections with the cluster's settings and the connection limit filter.
  • CONNECT tunneling (tunneling_config), max_connect_attempts, and hash-based endpoint selection are not supported and are refused at startup.
  • Per-entry metadata_match on weighted clusters is not supported.

The field table is in the TCP proxy reference.

Continue

Start typing to search the site.

↑ ↓ to move Enter to open Esc to close