---
title: TCP sockets · Cloudflare Workers docs
description: Use the `connect()` API to create outbound TCP connections from Workers.
lastUpdated: 2025-08-12T20:02:42.000Z
chatbotDeprioritize: false
source_url:
  html: https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/
  md: https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/index.md
---

The Workers runtime provides the `connect()` API for creating outbound [TCP connections](https://www.cloudflare.com/learning/ddos/glossary/tcp-ip/) from Workers.

Many application-layer protocols are built on top of the Transmission Control Protocol (TCP). These application-layer protocols, including SSH, MQTT, SMTP, FTP, IRC, and most database wire protocols including MySQL, PostgreSQL, MongoDB, require an underlying TCP socket API in order to work.

Note

Connecting to a PostgreSQL database? You should use [Hyperdrive](https://developers.cloudflare.com/hyperdrive/), which provides the `connect()` API with built-in connection pooling and query caching.

Note

TCP Workers outbound connections are sourced from a prefix that is not part of [list of IP ranges](https://www.cloudflare.com/ips/).

## `connect()`

The `connect()` function returns a TCP socket, with both a [readable](https://developers.cloudflare.com/workers/runtime-apis/streams/readablestream/) and [writable](https://developers.cloudflare.com/workers/runtime-apis/streams/writablestream/) stream of data. This allows you to read and write data on an ongoing basis, as long as the connection remains open.

`connect()` is provided as a [Runtime API](https://developers.cloudflare.com/workers/runtime-apis/), and is accessed by importing the `connect` function from `cloudflare:sockets`. This process is similar to how one imports built-in modules in Node.js. Refer to the following codeblock for an example of creating a TCP socket, writing to it, and returning the readable side of the socket as a response:

```typescript
import { connect } from 'cloudflare:sockets';


export default {
  async fetch(req): Promise<Response> {
    const gopherAddr = { hostname: "gopher.floodgap.com", port: 70 };
    const url = new URL(req.url);


    try {
      const socket = connect(gopherAddr);


      const writer = socket.writable.getWriter()
      const encoder = new TextEncoder();
      const encoded = encoder.encode(url.pathname + "\r\n");
      await writer.write(encoded);
      await writer.close();


      return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } });
    } catch (error) {
      return new Response("Socket connection failed: " + error, { status: 500 });
    }
  }
} satisfies ExportedHandler;
```

* `connect(address: SocketAddress | string, options?: optional SocketOptions)` : `Socket`
  * `connect()` accepts either a URL string or [`SocketAddress`](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socketaddress) to define the hostname and port number to connect to, and an optional configuration object, [`SocketOptions`](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socketoptions). It returns an instance of a [`Socket`](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socket).

### `SocketAddress`

* `hostname` string

  * The hostname to connect to. Example: `cloudflare.com`.

* `port` number

  * The port number to connect to. Example: `5432`.

### `SocketOptions`

* `secureTransport` "off" | "on" | "starttls" — Defaults to `off`

  * Specifies whether or not to use [TLS](https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/) when creating the TCP socket.
  * `off` — Do not use TLS.
  * `on` — Use TLS.
  * `starttls` — Do not use TLS initially, but allow the socket to be upgraded to use TLS by calling [`startTls()`](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#opportunistic-tls-starttls).

* `allowHalfOpen` boolean — Defaults to `false`

  * Defines whether the writable side of the TCP socket will automatically close on end-of-file (EOF). When set to `false`, the writable side of the TCP socket will automatically close on EOF. When set to `true`, the writable side of the TCP socket will remain open on EOF.
  * This option is similar to that offered by the Node.js [`net` module](https://nodejs.org/api/net.html) and allows interoperability with code which utilizes it.

### `SocketInfo`

* `remoteAddress` string | null

  * The address of the remote peer the socket is connected to. May not always be set.

* `localAddress` string | null

  * The address of the local network endpoint for this socket. May not always be set.

### `Socket`

* `readable` : ReadableStream

  * Returns the readable side of the TCP socket.

* `writable` : WritableStream

  * Returns the writable side of the TCP socket.
  * The `WritableStream` returned only accepts chunks of `Uint8Array` or its views.

* `opened` `Promise<SocketInfo>`

  * This promise is resolved when the socket connection is established and is rejected if the socket encounters an error.

* `closed` `Promise<void>`

  * This promise is resolved when the socket is closed and is rejected if the socket encounters an error.

* `close()` `Promise<void>`

  * Closes the TCP socket. Both the readable and writable streams are forcibly closed.

* `startTls()` : Socket

  * Upgrades an insecure socket to a secure one that uses TLS, returning a new [Socket](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets#socket). Note that in order to call `startTls()`, you must set [`secureTransport`](https://developers.cloudflare.com/workers/runtime-apis/tcp-sockets/#socketoptions) to `starttls` when initially calling `connect()` to create the socket.

## Opportunistic TLS (StartTLS)

Many TCP-based systems, including databases and email servers, require that clients use opportunistic TLS (otherwise known as [StartTLS](https://en.wikipedia.org/wiki/Opportunistic_TLS)) when connecting. In this pattern, the client first creates an insecure TCP socket, without TLS, and then upgrades it to a secure TCP socket, that uses TLS. The `connect()` API simplifies this by providing a method, `startTls()`, which returns a new `Socket` instance that uses TLS:

```typescript
import { connect } from "cloudflare:sockets"


const address = {
  hostname: "example-postgres-db.com",
  port: 5432
};
const socket = connect(address, { secureTransport: "starttls" });
const secureSocket = socket.startTls();
```

* `startTls()` can only be called if `secureTransport` is set to `starttls` when creating the initial TCP socket.
* Once `startTls()` is called, the initial socket is closed and can no longer be read from or written to. In the example above, anytime after `startTls()` is called, you would use the newly created `secureSocket`. Any existing readers and writers based off the original socket will no longer work. You must create new readers and writers from the newly created `secureSocket`.
* `startTls()` should only be called once on an existing socket.

## Handle errors

To handle errors when creating a new TCP socket, reading from a socket, or writing to a socket, wrap these calls inside [`try/catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) statement blocks. The following example opens a connection to Google.com, initiates a HTTP request, and returns the response. If this fails and throws an exception, it returns a `500` response:

```typescript
import { connect } from 'cloudflare:sockets';
const connectionUrl = { hostname: "google.com", port: 80 };
export interface Env { }
export default {
  async fetch(req, env, ctx): Promise<Response> {
    try {
      const socket = connect(connectionUrl);
      const writer = socket.writable.getWriter();
      const encoder = new TextEncoder();
      const encoded = encoder.encode("GET / HTTP/1.0\r\n\r\n");
      await writer.write(encoded);
      await writer.close();


      return new Response(socket.readable, { headers: { "Content-Type": "text/plain" } });
    } catch (error) {
      return new Response(`Socket connection failed: ${error}`, { status: 500 });
    }
  }
} satisfies ExportedHandler<Env>;
```

## Close TCP connections

You can close a TCP connection by calling `close()` on the socket. This will close both the readable and writable sides of the socket.

```typescript
import { connect } from "cloudflare:sockets"


const socket = connect({ hostname: "my-url.com", port: 70 });
const reader = socket.readable.getReader();
socket.close();


// After close() is called, you can no longer read from the readable side of the socket
const reader = socket.readable.getReader(); // This fails
```

## Considerations

* Outbound TCP sockets to [Cloudflare IP ranges](https://www.cloudflare.com/ips/) are blocked.
* TCP sockets cannot be created in global scope and shared across requests. You should always create TCP sockets within a handler (ex: [`fetch()`](https://developers.cloudflare.com/workers/get-started/guide/#3-write-code), [`scheduled()`](https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/), [`queue()`](https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer)) or [`alarm()`](https://developers.cloudflare.com/durable-objects/api/alarms/).
* Each open TCP socket counts towards the maximum number of [open connections](https://developers.cloudflare.com/workers/platform/limits/#simultaneous-open-connections) that can be simultaneously open.
* By default, Workers cannot create outbound TCP connections on port `25` to send email to SMTP mail servers. [Cloudflare Email Workers](https://developers.cloudflare.com/email-routing/email-workers/) provides APIs to process and forward email.
* Support for handling inbound TCP connections is [coming soon](https://blog.cloudflare.com/workers-tcp-socket-api-connect-databases/). Currently, it is not possible to make an inbound TCP connection to your Worker, for example, by using the `CONNECT` HTTP method.

## Troubleshooting

Review descriptions of common error messages you may see when working with TCP Sockets, what the error messages mean, and how to solve them.

### `proxy request failed, cannot connect to the specified address`

Your socket is connecting to an address that was disallowed. Examples of a disallowed address include Cloudflare IPs, `localhost`, and private network IPs.

If you need to connect to addresses on port `80` or `443` to make HTTP requests, use [`fetch`](https://developers.cloudflare.com/workers/runtime-apis/fetch/).

### `TCP Loop detected`

Your socket is connecting back to the Worker that initiated the outbound connection. In other words, the Worker is connecting back to itself. This is currently not supported.

### `Connections to port 25 are prohibited`

Your socket is connecting to an address on port `25`. This is usually the port used for SMTP mail servers. Workers cannot create outbound connections on port `25`. Consider using [Cloudflare Email Workers](https://developers.cloudflare.com/email-routing/email-workers/) instead.
