# Configure TCP/UDP Proxying

API7 Gateway can proxy Layer 4 (TCP/UDP) traffic in addition to HTTP traffic. This enables you to use the gateway as a unified entry point for non-HTTP protocols such as MySQL, Redis, MQTT, and custom TCP services.

This guide walks through configuring a TCP proxy to a MySQL database as an example. The same approach applies to any TCP or UDP service.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

* An API7 Enterprise instance is running.
* A Gateway Group is created and a Gateway instance is running.
* A [token from the Dashboard](https://docs.api7.ai/api7-gateway/reference/obtain-dashboard-token.md).
* A MySQL client is installed if you want to validate the sample TCP proxy with `mysql`.

## Start a Sample TCP Upstream[​](#start-a-sample-tcp-upstream "Direct link to Start a Sample TCP Upstream")

Start the same sample MySQL server used in the API7 Enterprise TCP proxy best-practice guide:

```
docker run -d \

  --name mysql \

  --network host \

  -e MYSQL_ROOT_PASSWORD=password \

  mysql:8.4 \

  mysqld --mysql-native-password=ON
```

The examples below assume the gateway can reach the Docker host at `host.docker.internal`. If your environment uses a different host address, replace `host.docker.internal` with that address.

### Ensure a Stream Proxy Port Is Available[​](#ensure-a-stream-proxy-port-is-available "Direct link to Ensure a Stream Proxy Port Is Available")

Before traffic can reach a stream route, the gateway must already be listening on a TCP or UDP port for stream traffic.

Admin API and ADC can create stream services and stream routes, but they do not create or expose the gateway listener itself. If your API7 Enterprise deployment already provides an L4 listener, reuse that port in `server_port`. If not, add one in the gateway runtime configuration and redeploy or restart the gateway so the new listener is exposed.

If you manage the gateway runtime directly, add the port to `config.yaml` as follows:

config.yaml

```
apisix:

  stream_proxy:

    only: false

    tcp:

      - 2000
```

If the gateway runs in Docker, also publish the same port from the container to the host. For example, recreate or redeploy the gateway container with `-p 2000:2000`.

For Kubernetes deployments, add the stream proxy ports to your Helm values, ensure the Service exposes them, and redeploy the gateway.

## Create a Stream Service[​](#create-a-stream-service "Direct link to Create a Stream Service")

Once a stream listener is available on the gateway, create a service with type `stream` and configure the upstream.

* Admin API
* ADC

```
curl -k "https://localhost:7443/apisix/admin/services/mysql-service?gateway_group_id={gateway_group_id}" -X PUT \

  -H "X-API-KEY: ${API_KEY}" \

  -H "Content-Type: application/json" \

  -d '{

    "name": "mysql-service",

    "type": "stream",

    "upstream": {

      "scheme": "tcp",

      "nodes": [

        {

          "host": "host.docker.internal",

          "port": 3306,

          "weight": 100

        }

      ]

    }

  }'
```

adc.yaml

```
services:

  - name: mysql-service

    upstream:

      scheme: tcp

      nodes:

        - host: host.docker.internal

          port: 3306

          weight: 100

    stream_routes:

      - name: mysql-route

        server_port: 2000
```

## Create a Stream Route[​](#create-a-stream-route "Direct link to Create a Stream Route")

Create a stream route that matches traffic on the stream proxy port and forwards it to the upstream.

* Admin API
* ADC

```
curl -k "https://localhost:7443/apisix/admin/stream_routes/mysql-route?gateway_group_id={gateway_group_id}" -X PUT \

  -H "X-API-KEY: ${API_KEY}" \

  -H "Content-Type: application/json" \

  -d '{

    "name": "mysql-route",

    "server_port": 2000,

    "service_id": "mysql-service"

  }'
```

adc.yaml

```
services:

  - name: mysql-service

    upstream:

      scheme: tcp

      nodes:

        - host: host.docker.internal

          port: 3306

          weight: 100

    stream_routes:

      - name: mysql-route

        server_port: 2000
```

`server_port` must match an existing gateway stream listener configured under `stream_proxy.tcp` or `stream_proxy.udp`. In the Admin API example, `service_id` references the stream service that contains the upstream configuration.

For ADC workflows, define stream routes under the parent service. To validate the examples end to end, make sure the same stream port is configured on the gateway and exposed to clients by your deployment.

## Validate the Configuration[​](#validate-the-configuration "Direct link to Validate the Configuration")

After the gateway has been redeployed or restarted with the stream listener enabled and exposed, test the TCP proxy by connecting to the gateway's stream proxy port:

```
# MySQL example

mysql -h 127.0.0.1 -P 2000 -u root -p
```

If the connection succeeds and you can interact with the MySQL server, the stream route is working correctly.

For a generic TCP test:

```
# Using netcat

nc -zv 127.0.0.1 2000
```

## Add Stream Plugins[​](#add-stream-plugins "Direct link to Add Stream Plugins")

Stream routes support a limited set of plugins. For example, you can use [`limit-conn`](https://docs.api7.ai/hub/limit-conn.md) to add connection rate limiting:

* Admin API
* ADC

```
curl -k "https://localhost:7443/apisix/admin/stream_routes/mysql-route?gateway_group_id={gateway_group_id}" -X PUT \

  -H "X-API-KEY: ${API_KEY}" \

  -H "Content-Type: application/json" \

  -d '{

    "name": "mysql-route",

    "server_port": 2000,

    "service_id": "mysql-service",

    "plugins": {

      "limit-conn": {

        "conn": 100,

        "burst": 50,

        "default_conn_delay": 0.1,

        "key": "remote_addr",

        "key_type": "var"

      }

    }

  }'
```

PUT replaces the entire stream route, so re-send the existing fields (`name`, `server_port`, `service_id`) along with the new `plugins` block.

adc.yaml

```
services:

  - name: mysql-service

    upstream:

      scheme: tcp

      nodes:

        - host: mysql.example.com

          port: 3306

          weight: 1

    stream_routes:

      - name: mysql-route

        server_port: 2000

        plugins:

          limit-conn:

            conn: 100

            burst: 50

            default_conn_delay: 0.1

            key: remote_addr

            key_type: var
```

## Configure TLS for Stream Routes[​](#configure-tls-for-stream-routes "Direct link to Configure TLS for Stream Routes")

To proxy TLS-encrypted TCP connections with SNI-based routing:

1. Enable a TLS stream proxy port:

   config.yaml

   ```
    apisix:

      stream_proxy:

        tcp:

          - addr: 9443

            tls: true
   ```

2. Create stream routes with the `sni` field to route based on the client's TLS Server Name Indication.

## Next Steps[​](#next-steps "Direct link to Next Steps")

* [Stream Routes](https://docs.api7.ai/api7-gateway/key-concepts/stream-routes.md): Conceptual overview of stream routes.
* [gRPC Proxy](https://docs.api7.ai/api7-gateway/how-to-guides/protocol-proxy/grpc-proxy.md): Proxy gRPC traffic (Layer 7).
* [WebSocket Proxy](https://docs.api7.ai/api7-gateway/how-to-guides/protocol-proxy/websocket-proxy.md): Proxy WebSocket connections.
