High-performance, protocol-agnostic proxy built on Swoole for HTTP, TCP, and SMTP.
- 670k+ concurrent connections per server (8-core/32GB)
- ~33KB per connection memory footprint
- 18k+ connections/sec sustained connection rate
- 60k+ ops/sec TCP request/response throughput (1KB payloads)
- BPF sockmap optional kernel zero-copy relay for CPU-bound workloads
| Metric | Result |
|---|---|
| Peak concurrent connections | 672,348 |
| Memory per connection | ~33 KB |
| Connection rate (sustained) | 18,067/sec |
| CPU at peak | ~60% |
- PHP >= 8.4
- ext-swoole >= 6.0
- ext-redis
composer require utopia-php/proxyThe proxy resolves routing input to backend endpoints via the Resolver interface — one method, one job:
interface Resolver
{
public function resolve(string $data): Result;
}$data is protocol-specific: raw TCP packet bytes, HTTP hostname, SMTP domain. The proxy doesn't parse it — your resolver does.
use Utopia\Proxy\Resolver;
use Utopia\Proxy\Resolver\Result;
use Utopia\Proxy\Server\HTTP\Config;
use Utopia\Proxy\Server\HTTP\Swoole as HTTPServer;
$resolver = new class implements Resolver {
public function resolve(string $data): Result
{
return match ($data) {
'api.example.com' => new Result(endpoint: 'localhost:3000'),
'app.example.com' => new Result(endpoint: 'localhost:3001'),
default => throw new \Utopia\Proxy\Resolver\Exception("Unknown: {$data}", 404),
};
}
};
$config = new Config(port: 8080, workers: swoole_cpu_num() * 2);
(new HTTPServer($resolver, $config))->start();use Utopia\Proxy\Resolver\Fixed;
use Utopia\Proxy\Server\TCP\Config;
use Utopia\Proxy\Server\TCP\Swoole as TCPServer;
$resolver = new Fixed('10.0.0.2:5432');
$config = new Config(
ports: [5432, 3306],
workers: swoole_cpu_num(),
);
(new TCPServer($resolver, $config))->start();For custom routing, parse the raw packet data in your resolver:
$adapter->onResolve(function (string $data) use ($resolver): string {
$databaseId = MyPacketParser::extractDatabase($data);
return $resolver->resolve($databaseId);
});use Utopia\Proxy\Resolver\Fixed;
use Utopia\Proxy\Server\SMTP\Config;
use Utopia\Proxy\Server\SMTP\Swoole as SMTPServer;
$resolver = new Fixed('smtp-backend:1025');
$config = new Config(port: 25);
(new SMTPServer($resolver, $config))->start();The Docker image ships a generic entrypoint (bin/proxy) that supports all three protocols. Mount a resolver file or use the built-in Fixed resolver with environment variables.
docker build -t utopia/proxy .
# TCP proxy
docker run --rm --network=host \
-e TCP_BACKEND_ENDPOINT=10.0.0.2:5432 \
utopia/proxy
# HTTP proxy
docker run --rm -p 8080:8080 \
-e HTTP_BACKEND_ENDPOINT=10.0.0.2:5678 \
utopia/proxy http
# SMTP proxy
docker run --rm -p 25:25 \
-e SMTP_BACKEND_ENDPOINT=10.0.0.2:1025 \
utopia/proxy smtpMount a PHP file that returns a Resolver instance, like mounting haproxy.cfg for HAProxy:
docker run --rm --network=host \
-v ./resolver.php:/etc/utopia-proxy/resolver.php:ro \
utopia/proxy tcpWhere resolver.php is:
<?php
return new class implements Utopia\Proxy\Resolver {
public function resolve(string $data): Utopia\Proxy\Resolver\Result
{
$databaseId = parseStartupMessage($data);
$endpoint = lookupFromRedis($databaseId);
return new Utopia\Proxy\Resolver\Result(endpoint: $endpoint);
}
};If your resolver needs Composer dependencies, mount a project directory:
docker run --rm --network=host \
-v ./my-proxy:/app:ro \
-e PROXY_RESOLVER=/app/resolver.php \
utopia/proxy tcpThe entrypoint loads /app/vendor/autoload.php if present.
docker compose up --build # HTTP proxy on :8080
docker compose up --build tcp-proxy # TCP proxy on :5432/:3306
docker compose up --build smtp-proxy # SMTP proxy on :25For TCP workloads where proxy CPU is the bottleneck, enable BPF sockmap to bypass userspace:
docker run --rm --network=host --user root \
--cap-add=BPF --cap-add=NET_ADMIN --cap-add=SYS_RESOURCE \
-e TCP_SOCKMAP_ENABLED=1 \
-e TCP_BACKEND_ENDPOINT=10.0.0.2:5432 \
utopia/proxyRequires Linux 4.17+, ext-ffi, and libbpf. See benchmarks/README.md for details.
use Utopia\Proxy\Server\TCP\Config;
use Utopia\Proxy\Server\TCP\TLS;
use Utopia\Proxy\Server\TCP\Swoole as TCPServer;
$tls = new TLS(
certificate: '/certs/server.crt',
key: '/certs/server.key',
ca: '/certs/ca.crt',
requireClientCert: true,
);
$config = new Config(ports: [5432, 3306], tls: $tls);
(new TCPServer($resolver, $config))->start();Supported: PostgreSQL STARTTLS, MySQL SSL handshake. Environment variables: PROXY_TLS_ENABLED, PROXY_TLS_CERT, PROXY_TLS_KEY, PROXY_TLS_CA, PROXY_TLS_REQUIRE_CLIENT_CERT.
TCP:
| Variable | Default | Description |
|---|---|---|
TCP_BACKEND_ENDPOINT |
127.0.0.1:5432 |
Fixed backend endpoint |
TCP_POSTGRES_PORT |
5432 |
PostgreSQL listen port |
TCP_MYSQL_PORT |
3306 |
MySQL listen port |
TCP_WORKERS |
cpu_num |
Worker processes |
TCP_SKIP_VALIDATION |
false |
Disable SSRF protection |
TCP_SOCKMAP_ENABLED |
false |
Enable BPF sockmap relay |
TCP_SOCKMAP_BPF_OBJECT |
Path to compiled relay.bpf.o |
HTTP:
| Variable | Default | Description |
|---|---|---|
HTTP_BACKEND_ENDPOINT |
127.0.0.1:5678 |
Fixed backend endpoint |
HTTP_PORT |
8080 |
Listen port |
HTTP_WORKERS |
cpu_num * 2 |
Worker processes |
HTTP_BACKEND_POOL_SIZE |
2048 |
Connection pool size |
HTTP_SKIP_VALIDATION |
false |
Disable SSRF protection |
SMTP:
| Variable | Default | Description |
|---|---|---|
SMTP_BACKEND_ENDPOINT |
127.0.0.1:1025 |
Fixed backend endpoint |
SMTP_PORT |
25 |
Listen port |
SMTP_SKIP_VALIDATION |
false |
Disable SSRF protection |
composer test # Unit tests
composer test:integration # Integration tests (Docker)
composer test:all # All tests
composer check # PHPStan (max level)
composer lint # Pint (PSR-12)composer bench:build # Compile C load generator
composer bench:bpf # Compile BPF sockmap object
# TCP throughput
./benchmarks/tcpbench rr -h 127.0.0.1 -p 5432 -c 200 -d 10 -s 1024
# Kernel tuning
sudo ./benchmarks/setup.sh # Benchmark mode
sudo ./benchmarks/setup.sh --production # Production modeSee benchmarks/README.md for detailed results.
BSD-3-Clause