4 releases (2 breaking)

Uses new Rust 2024

new 0.3.0 May 13, 2026
0.2.0 May 13, 2026
0.1.1 Feb 25, 2026
0.1.0 Feb 13, 2026

#254 in HTTP server

MIT license

610KB
14K SLoC

pyx

A high-performance reverse proxy written in Rust, designed as a drop-in replacement for h2o with configuration compatibility.

Features

  • Protocol Support: HTTP/1.1, HTTP/2 (end-to-end via ALPN), HTTP/3 (QUIC), TLS 1.2/1.3
  • Reverse Proxy: Connection pooling, header manipulation, X-Forwarded-* injection, streaming
  • TCP Proxy: Layer 4 proxying with weighted load balancing and health checks
  • Static Files: Directory listing, gzip pre-compression, range requests, ETag support
  • TLS: SNI-based certificate selection, wildcard certificates, session resumption, transparent upgrade
  • Load Balancing: Weighted distribution, latency-aware routing, active health monitoring
  • Security: Request body limits, directory traversal protection, rate limiting

Installation

cargo build --release

Binary will be at target/release/pyx.

Usage

pyx [OPTIONS]

Options:
  -c, --config <CONFIG>       Configuration file [default: /etc/pyx/pyx.yaml]
  -l, --log-level <LEVEL>     Log level: trace, debug, info, warn, error [default: info]
      --json-logs             Enable JSON structured logging
  -t, --test                  Test configuration and exit
  -w, --workers <WORKERS>     Worker threads [default: auto-detect]
  -h, --help                  Print help
  -V, --version               Print version

Configuration

pyx uses h2o-compatible YAML configuration.

Minimal Example

hosts:
  "example.com:80":
    listen:
      host: 0.0.0.0
      port: 80
    paths:
      "/":
        proxy.reverse.url: "http://localhost:8080"

Full Example

# Global settings
num-threads: 4
pid-file: /var/run/pyx.pid

# Protocol settings
http2-enabled: ON
http2-idle-timeout: 180
http2-max-concurrent-streams: 256
http3-enabled: OFF

# Request limits
limit-request-body: 10485760

# Proxy defaults
proxy.preserve-host: ON
proxy.timeout.io: 30000

# Static file settings
file.send-gzip: ON

hosts:
  "example.com:443":
    listen:
      host: 0.0.0.0
      port: 443
      ssl:
        certificate-file: /etc/ssl/certs/example.pem
        key-file: /etc/ssl/private/example.key
        minimum-version: "TLSv1.2"

    paths:
      "/":
        proxy.reverse.url: "http://backend:8080"
        header.set:
          - "X-Forwarded-Proto: https"

      "/static/":
        file.dir: /var/www/static
        file.index:
          - index.html
        expires: "7 days"

      "/api/":
        proxy.reverse.url: "http://api-backend:3000"
        proxy.preserve-host: OFF
        header.set:
          - "Cache-Control: no-store"

TCP Proxy with Load Balancing

hosts:
  "tcp.example.com:3306":
    listen:
      host: 0.0.0.0
      port: 3306
      type: tcp

    backends:
      - host: "db1.internal"
        port: 3306
        weight: 100
      - host: "db2.internal"
        port: 3306
        weight: 50

    health:
      interval: 5000
      timeout: 2000
      unhealthy-threshold: 3
      healthy-threshold: 3
      latency-aware: ON

Header Manipulation

# Set header (overwrite if exists)
header.set:
  - "X-Custom: value"

# Set only if header doesn't exist
header.setifempty:
  - "X-Powered-By: pyx"

# Append to existing header
header.merge:
  - "Cache-Control: public"

# Remove header
header.unset:
  - "Server"
  - "X-Powered-By"

Configuration Reference

Setting Default Description
num-threads CPU count Worker thread count
pid-file (none) PID file path
limit-request-body 10485760 Max request body size (bytes)
file.send-gzip OFF Serve pre-compressed .gz files
proxy.preserve-host OFF Preserve Host header when proxying
proxy.timeout.io 30000 Proxy I/O timeout (ms)
http2-enabled ON Enable HTTP/2
http2-idle-timeout 180 HTTP/2 idle timeout (seconds)
http2-max-concurrent-streams 256 Max streams per connection
http2-initial-stream-window 1048576 HTTP/2 stream window size (bytes)
http2-initial-connection-window 2097152 HTTP/2 connection window size (bytes)
http2-max-frame-size 16384 HTTP/2 max frame size (bytes)
http3-enabled OFF Enable HTTP/3 (QUIC)
http3-idle-timeout 30 HTTP/3 idle timeout (seconds)
http3-max-concurrent-streams 256 HTTP/3 max streams per connection

Routing

Routes use longest-prefix matching. More specific paths take priority:

paths:
  "/":           # Catch-all
  "/api":        # Matches /api, /api/users, /api/v1
  "/api/v2":     # Matches /api/v2, /api/v2/users (takes priority over /api)
  "/static/":    # Only matches paths starting with /static/

Path matching prevents false prefixes: /api does not match /apikey.

TLS

Basic TLS

listen:
  host: 0.0.0.0
  port: 443
  ssl:
    certificate-file: /path/to/cert.pem
    key-file: /path/to/key.pem

SNI with Multiple Certificates

Configure multiple hosts on the same port. pyx selects certificates based on SNI:

hosts:
  "site-a.com:443":
    listen:
      port: 443
      ssl:
        certificate-file: /certs/site-a.pem
        key-file: /keys/site-a.key
    paths:
      "/":
        proxy.reverse.url: "http://backend-a:8080"

  "site-b.com:443":
    listen:
      port: 443
      ssl:
        certificate-file: /certs/site-b.pem
        key-file: /keys/site-b.key
    paths:
      "/":
        proxy.reverse.url: "http://backend-b:8080"

Automatic Let's Encrypt Certificates

Enable ACME globally, then opt in per TLS host with ssl.letsencrypt: ON. styx serves HTTP-01 challenges internally from /.well-known/acme-challenge/, stores certificates under cache-dir, loads cached certificates on startup, and renews them before expiry.

letsencrypt:
  enabled: ON
  contact:
    - mailto:admin@example.com
  cache-dir: /var/lib/styx/letsencrypt
  terms-of-service-agreed: ON
  staging: OFF
  renew-before-days: 30
  check-interval-seconds: 43200

hosts:
  "example.com:80":
    listen:
      port: 80
    paths:
      "/":
        redirect: "https://example.com/"

  "example.com:443":
    listen:
      port: 443
      ssl:
        letsencrypt: ON
    paths:
      "/":
        proxy.reverse.url: "http://backend:8080"

certificate-file and key-file remain supported. When omitted for a Let's Encrypt host, styx writes to cache-dir/live/<domain>/fullchain.pem and cache-dir/live/<domain>/privkey.pem. If provisioning or renewal fails, the error is logged and styx keeps running with any currently loaded certificate.

Transparent TLS Upgrade (TCP Proxy)

Accept both plain and TLS connections on the same port for TCP proxy:

hosts:
  "tcp.example.com:8080":
    listen:
      host: 0.0.0.0
      port: 8080
      type: tcp
    tls:
      certificate-file: /path/to/cert.pem
      key-file: /path/to/key.pem
      transparent-upgrade: ON
    backends:
      - host: "backend.internal"
        port: 8080

Static File Serving

paths:
  "/static/":
    file.dir: /var/www/static
    file.index:
      - index.html
      - index.htm
    file.dirlisting: ON
    expires: "30 days"

Features:

  • Automatic MIME type detection
  • Serves .gz files when Accept-Encoding: gzip is present
  • ETag and If-None-Match support
  • Range request support (partial content)
  • Directory traversal protection

Health Checks

For TCP proxy backends:

health:
  interval: 5000          # Check every 5 seconds
  timeout: 2000           # 2 second timeout per check
  unhealthy-threshold: 3  # 3 failures to mark unhealthy
  healthy-threshold: 3    # 3 successes to mark healthy
  latency-aware: ON       # Route less traffic to slow backends
  sigma-threshold: 2.0    # Latency std deviation threshold

Development

# Build
cargo build                    # Debug
cargo build --release          # Release

# Test
cargo test                     # All tests
cargo test --lib               # Unit tests
cargo test --test integration_tests

# Lint
cargo clippy
cargo fmt

# Benchmark
cargo bench
cargo bench --bench routing
cargo bench --bench proxy

License

See LICENSE file.

Dependencies

~131MB
~3M SLoC