Home
Softono
swerver

swerver

Open source MIT Zig
14
Stars
0
Forks
1
Issues
0
Watchers
1 week
Last Commit

About swerver

A zero-copy, zero-allocation HTTP server written in pure Zig.

Platforms

Web Self-hosted

Languages

Zig

swerver

swerver

A bare metal HTTP server written in pure Zig.

swerver.net · docs · x402

HTTP/1.1 ──┐
HTTP/2   ──┼──► swerver ──► kqueue/epoll/io_uring ──► your code
HTTP/3   ──┘      │
                  └── QUIC (RFC 9000-9002)

Alpha release. The public library API in src/lib.zig will change between alpha versions as it's iterated on. Breaking changes are announced in release notes. See Known limitations for what's in and out of scope for the current release.

📖 Documentation: docs.swerver.net: getting started, routing, handlers, middleware, configuration, the reverse proxy, and the PostgreSQL client.

What

Swerver is a bare-metal HTTP/1.1 + HTTP/2 + HTTP/3 server. It holds #1 on JSON-over-TLS on HttpArena's 64-core benchmark (1.95M req/s) and runs top-tier across baseline, pipelined, HTTP/2, and HTTP/3, carrying a full middleware chain, TLS termination, and routing.

Fixed-size buffer pools. Stack-allocated parsing. No garbage collection. No hidden allocations. No surprises.

Quick Start

# Build
zig build

# Build with TLS + HTTP/2 + HTTP/3
zig build -Denable-tls=true -Denable-http2=true -Denable-http3=true

# Run
zig build run

# Run with a config file
zig build run -- --config config.json

# Test
zig build test

# Test matrix (runs tests under multiple feature flag combinations)
zig build test-matrix

# Benchmark
zig build bench

The server listens on 0.0.0.0:8080 by default.

Install

Build from source

Requires Zig 0.16.0 (stable). OpenSSL 3.5+ for TLS/HTTP/2/HTTP/3.

git clone https://github.com/justinGrosvenor/swerver.git
cd swerver
zig build -Doptimize=ReleaseFast -Denable-tls=true -Denable-http2=true -Denable-http3=true
./zig-out/bin/swerver --config config.json

Pre-built binaries (GitHub Releases)

Tagged alpha releases publish cross-compiled binaries for linux-{x86_64, aarch64} and macos-{x86_64, aarch64} on the Releases page. Download, extract, and run:

curl -LO https://github.com/justinGrosvenor/swerver/releases/download/v0.1.0-alpha.23/swerver-v0.1.0-alpha.23-linux-x86_64.tar.gz
tar -xzf swerver-v0.1.0-alpha.23-linux-x86_64.tar.gz
./swerver-v0.1.0-alpha.23-linux-x86_64 --config config.json

Release binaries are built without TLS, HTTP/2, or HTTP/3. OpenSSL linking requires the host toolchain, so the cross-compiled binaries ship as HTTP/1.1-only. Build from source or use the Docker image for full protocol support.

Docker

The HttpArena submission includes a Dockerfile that builds a full-featured (TLS / HTTP/2 / HTTP/3) runtime image using debian:trixie (OpenSSL 3.5). See the HttpArena repo for the Dockerfile and docker-compose setup.

Use as a library

Swerver ships as a Zig package: depend on it from another Zig project and embed the server into your own binary.

In your downstream project's build.zig.zon:

.{
    .name = .my_app,
    .version = "0.1.0",
    .dependencies = .{
        .swerver = .{
            .url = "https://github.com/justinGrosvenor/swerver/archive/refs/tags/v0.1.0-alpha.23.tar.gz",
            // .hash will be filled in by `zig fetch --save`
        },
    },
    .paths = .{""},
}

In your build.zig:

const swerver_dep = b.dependency("swerver", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("swerver", swerver_dep.module("swerver"));

In your application code:

const std = @import("std");
const swerver = @import("swerver");

const AppState = struct { greeting: []const u8 };

fn hello(ctx: *swerver.router.HandlerContext) swerver.response.Response {
    const state = ctx.state(AppState);
    return ctx.text(200, state.greeting);
}

fn echo(ctx: *swerver.router.HandlerContext) swerver.response.Response {
    var builder = ctx.respond() catch return ctx.text(503, "No buffers available");
    defer ctx.releaseBuilder(&builder);
    return builder.json(200, ctx.request.body.sliceOrNull() orelse "") catch
        ctx.text(500, "Response buffer full");
}

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    defer _ = gpa.deinit();

    var state = AppState{ .greeting = "hello, galaxy" };

    var router = swerver.router.Router.init(.{ .require_payment = false, .payment_required_b64 = "" });
    var api = router.group("/api");
    try api.get("/hello", hello);
    try api.post("/echo", echo);

    var server = try swerver.ServerBuilder
        .configDefault()
        .router(router)
        .withState(&state)
        .build(gpa.allocator());
    defer server.deinit();

    try server.run(null);
}

See examples/embedded/ and examples/gateway/ for complete, compiling examples.

Architecture

src/
├── server/
│   ├── dispatch.zig       # Event loop dispatch (read/write/accept)
│   ├── accept.zig         # Connection accept path
│   ├── http1.zig          # HTTP/1.1 dispatch + body accumulation
│   ├── http2.zig          # HTTP/2 dispatch + response encoding
│   ├── http3.zig          # HTTP/3 dispatch + send path
│   ├── tls.zig            # TLS handshake + ciphertext pump
│   ├── preencoded.zig     # Pre-encoded response cache
│   └── write_queue.zig    # Write-queue + buffer-op helpers
├── runtime/
│   ├── buffer_pool.zig    # Fixed-size buffer management
│   ├── connection.zig     # Connection state machine
│   ├── clock.zig          # Monotonic clock, timers
│   ├── io.zig             # Event loop abstraction
│   └── backend/
│       ├── kqueue.zig     # macOS/BSD
│       ├── epoll.zig      # Linux
│       └── io_uring.zig   # Linux (io_uring native + poll emulation)
├── protocol/
│   ├── http1.zig          # HTTP/1.1 parser
│   ├── http2.zig          # HTTP/2 + HPACK
│   └── http3.zig          # HTTP/3 + QPACK
├── proxy/
│   ├── proxy.zig          # Reverse proxy + load balancing
│   ├── websocket.zig      # WebSocket tunnel relay
│   ├── cache.zig          # Response cache (LRU)
│   ├── consul.zig         # Consul service discovery
│   └── dns.zig            # DNS service discovery
├── admin/
│   └── admin.zig          # Runtime route/upstream management API
├── quic/
│   ├── connection.zig     # QUIC state machine
│   ├── stream.zig         # QUIC streams
│   ├── crypto.zig         # Packet protection
│   ├── recovery.zig       # Loss detection
│   └── congestion.zig     # Congestion control (NewReno)
├── middleware/
│   ├── auth.zig           # API key / JWT / forward-auth
│   ├── ratelimit.zig      # Token bucket rate limiting (IP + consumer)
│   ├── security.zig       # Security headers
│   ├── compress.zig       # Response compression (gzip/deflate)
│   ├── grpc.zig           # gRPC status mapping
│   ├── body_schema.zig    # Request body validation
│   ├── otel.zig           # OpenTelemetry trace export
│   ├── metrics_mw.zig     # Prometheus exporter
│   ├── health.zig         # Liveness/readiness probes
│   ├── access_log.zig     # Access logging (combined/JSON)
│   └── observability.zig  # Structured logging
├── config_file.zig        # JSON config parser
├── master.zig             # Multi-process fork manager
└── server.zig             # Main server loop

Zero-Copy Design

Request parsing happens directly on receive buffers. Headers are slices into the original packet data, not copies. The HTTP/2 HPACK and HTTP/3 QPACK decoders use fixed-size internal tables.

// Headers are views into the receive buffer
pub const Header = struct {
    name: []const u8,   // slice, not owned
    value: []const u8,  // slice, not owned
};

The buffer pool pre-allocates all memory at startup:

const cfg = BufferPoolConfig{
    .buffer_size = 64 * 1024,   // 64KB per buffer
    .buffer_count = 4096,        // 64MB total
};

Configuration

JSON config file

The config file schema is at version 1.0 (see SCHEMA_VERSION in src/config_file.zig). Core fields (server, timeouts, limits, buffer_pool, tls, quic, upstreams, routes) are stable for the v0.1.0-alpha.N series. Newer sub-schemas (access_log, metrics, rate_limit, x402) may move before 1.0; config files that set only the core fields will survive alpha version bumps.

zig build run -- --config config.json
{
  "server": {
    "port": 8080,
    "workers": 4,
    "max_connections": 4096,
    "static_root": "./public"
  },
  "timeouts": {
    "idle_ms": 60000,
    "header_ms": 10000,
    "body_ms": 30000,
    "write_ms": 30000
  },
  "limits": {
    "max_header_bytes": 32768,
    "max_body_bytes": 8388608
  },
  "upstreams": [
    {
      "name": "api_backend",
      "servers": ["127.0.0.1:3000", "127.0.0.1:3001"],
      "load_balancer": "round_robin"
    }
  ],
  "routes": [
    {
      "path_prefix": "/api",
      "upstream": "api_backend"
    }
  ]
}

Config is hot-reloaded on SIGHUP (timeouts, limits, and other value types).

Embedded (Zig API)

const cfg = ServerConfig{
    .address = "0.0.0.0",
    .port = 8080,
    .max_connections = 2048,
    .timeouts = .{
        .idle_ms = 60_000,
        .header_ms = 10_000,
        .body_ms = 30_000,
    },
    .limits = .{
        .max_header_bytes = 32 * 1024,
        .max_body_bytes = 8 * 1024 * 1024,
    },
    .quic = .{
        .enabled = true,
        .port = 443,
        .cert_path = "cert.pem",
        .key_path = "key.pem",
    },
};

QUIC Implementation

Full RFC 9000-9002 implementation:

  • Packet Protection: AES-128-GCM with header protection
  • Loss Detection: RTT estimation, PTO calculation
  • Congestion Control: NewReno with pacing
  • Flow Control: Connection and stream-level
  • Stream Multiplexing: Bidirectional and unidirectional

Middleware

Middleware runs in a chain with zero allocations:

// Middleware returns a decision (simplified; actual type has 5 variants)
pub const Decision = union(enum) {
    allow,                           // Continue to next middleware
    skip,                            // Skip remaining middleware
    reject: Response,                // Stop and return response
    modify: struct {                 // Add headers, continue
        response_headers: []Header,
        continue_chain: bool,
    },
    rate_limit_backpressure: u64,    // Apply backpressure (ms)
};

CLI Flags

Flag Description
--config <path> Load JSON configuration file
--workers <n> Number of worker processes (default: CPU count)
--static-root <path> Serve static files from directory
--run-for-ms <ms> Run for specified duration then exit (testing)

Build Options

Flag Default Description
-Denable-tls false TLS 1.3 support (requires OpenSSL)
-Denable-http2 false HTTP/2 support
-Denable-http3 false HTTP/3 over QUIC
-Denable-proxy false Reverse proxy
-Denable-io-uring false io_uring backend (Linux)
-Denable-compression true (native) Response compression (requires zlib); off when cross-compiling
-Denable-x402-crypto false x402 local signature verification (secp256k1)
-Doptimize=ReleaseFast Maximum performance

Requirements

  • Zig 0.16.0 (stable)
  • OpenSSL 3.5+ (for TLS / HTTP/2 / HTTP/3)
  • macOS or Linux

Benchmarks

HttpArena leaderboard (64-core dedicated hardware, same Docker-compose environment for every framework):

Test Conns Requests/sec
json-tls (TLS + JSON serialize) 4096 1,950,310 #1
baseline (HTTP/1.1 plaintext) 4096 3,664,110
pipelined (HTTP/1.1) 4096 24,907,762
limited-conn (connection churn) 4096 2,556,130
json (HTTP/1.1) 4096 2,366,920
static file serving 6800 1,197,205
baseline (HTTP/2) 1024 2,267,290
baseline (HTTP/3) 64 872,911

Zero errors at 64-core saturation. ~222us average latency on limited-conn.

A note on "fastest": on raw HTTP/1.1 microbenchmarks (plaintext baseline, pipelined, limited-conn), feature-stripped reference servers edge swerver by 1-2%. Neither implements TLS, HTTP/2, or HTTP/3, so neither appears on json-tls, baseline-h2, or baseline-h3. Swerver is the fastest server that does the full job: three protocols, TLS termination, routing, and a complete middleware chain.

Features

Feature Status
HTTP/1.1 with keep-alive
HTTP/2 with HPACK compression
HTTP/3 over QUIC
Zero-copy request parsing
Fixed-size buffer pools
Backpressure handling
kqueue (macOS/BSD)
epoll (Linux)
io_uring backend (Linux)
TLS 1.3 (via OpenSSL/BoringSSL)
SNI multi-certificate TLS
mTLS client certificate verification
Reverse proxy (load balancing, health checks)
WebSocket proxy
gRPC-aware proxy
Response caching (LRU)
Response compression (gzip/deflate)
Traffic splitting (canary/blue-green)
Traffic mirroring (shadow testing)
DNS / Consul service discovery
Admin API (runtime route management)
SIGHUP hot reload (routes + upstreams)
API key / JWT authentication
Forward-auth proxy
Request body validation (JSON Schema)
OpenTelemetry trace export
Multi-worker (fork + SO_REUSEPORT)
JSON config file (--config)
Access logging (combined/JSON)
Static file serving (sendfile)
Prometheus metrics (/metrics)
Health probes (/.healthz, /.ready)
Rate limiting (per-IP / per-consumer)
Security headers (HSTS, CSP, CORS)
x402 payment protocol

Known limitations

Forward-looking notes about API stability and feature scope. These are not known bugs: they're promises about what is and isn't in the current release.

  • API surface is not frozen. Public types in src/lib.zig may change between alpha versions while the library surface is iterated on. Breaking changes are announced in release notes. The API will be frozen at the 1.0 release.
  • HTTP/3 is a young stack. The RFC 9000-9002 + 9114 implementation is complete and handles real workloads (GET and POST/PUT both work end-to-end), but it hasn't seen the hardening that the HTTP/1.1 and HTTP/2 paths have. Treat it as production-capable but new.
  • Platform support is Linux and macOS only. Windows is cross-compile-only: no IOCP backend, no sendfile. On the long-term roadmap but not part of the alpha.
  • Full QUIC 0-RTT / early data is not implemented. The handshake works and post-handshake throughput is competitive; 0-RTT adds replay protection and per-session token storage that are deferred to a later release.

Roadmap

  • Alpha iteration: public API refinement based on feedback, HttpArena re-submissions across kernel/hardware variation, expanded examples.
  • Beta: API surface frozen, PostgreSQL-backed REST benchmark added to HttpArena, long-form operational documentation.
  • 1.0: WebSocket server support, full QUIC 0-RTT / early data, security audit, stable C ABI for embedding in non-Zig programs, Windows support.

Contributing

See CONTRIBUTING.md for the development workflow, build options, test matrix, and repo-specific style conventions. Security issues go through SECURITY.md - please don't open public issues for vulnerabilities.

License

MIT


Built with Zig. No allocators were harmed in the making of this server.