# How to Integrate Hysteria Core with Existing Go Applications

> Easily integrate Hysteria core into your Go applications. Import client and server packages, configure TLS and QUIC, and leverage net.Conn for seamless TCP/UDP proxying over QUIC.

- Repository: [Aperture Internet Laboratory/hysteria](https://github.com/apernet/hysteria)
- Tags: how-to-guide
- Published: 2026-05-13

---

**You can integrate Hysteria core into existing Go applications by importing the `core/v2/client` and `core/v2/server` packages, configuring the TLS and QUIC parameters via the `Config` structs, and instantiating `Client` or `Server` objects that expose standard `net.Conn` interfaces for TCP and UDP proxying over QUIC.**

Hysteria 2 is a high-performance, QUIC-based proxy protocol maintained in the `apernet/hysteria` repository. Beyond its standalone binary usage, the project exposes clean Go APIs that allow developers to embed fast, censorship-resistant proxy functionality directly into existing applications without managing external processes.

## Adding Hysteria as a Dependency

To begin integrating Hysteria core with existing applications, add the module to your Go project:

```bash
go get github.com/apernet/hysteria@latest

```

This imports the core packages (`core/v2/client`, `core/v2/server`) along with the `quic-go` transport layer. The library handles TLS negotiation, QUIC stream multiplexing, congestion control (BBR/Cubic), and UDP datagram forwarding internally.

## Embedding the Hysteria Client

The `core/v2/client` package provides the `Client` type, which creates QUIC connections to a Hysteria server and exposes methods for opening proxied TCP streams and UDP sessions.

### Configuring the Client

Create a `client.Config` struct defined in [`core/client/config.go`](https://github.com/apernet/hysteria/blob/main/core/client/config.go). The `verifyAndFill` function automatically applies sensible defaults for omitted fields:

```go
import (
    "crypto/tls"
    "net"
    "github.com/apernet/hysteria/core/v2/client"
    "github.com/apernet/hysteria/core/v2/internal/congestion"
)

cfg := &client.Config{
    ServerAddr: &net.UDPAddr{
        IP:   net.ParseIP("203.0.113.1"),
        Port: 443,
    },
    Auth: "my-secret-token",
    TLSConfig: client.TLSConfig{
        ServerName:         "hysteria.example.com",
        InsecureSkipVerify: false, // Enable only for testing
    },
    CongestionConfig: client.CongestionConfig{
        Type:       congestion.TypeBBR,
        BBRProfile: "default",
    },
}

```

### Instantiating the Client

Call `client.NewClient` to establish the QUIC connection and complete the Hysteria handshake:

```go
c, handshake, err := client.NewClient(cfg)
if err != nil {
    log.Fatalf("Hysteria client init failed: %v", err)
}
defer c.Close() // Always close to free QUIC resources

fmt.Printf("UDP enabled: %v, TX limit: %d\n", 
    handshake.UDPEnabled, handshake.Tx)

```

The `handshake` return value indicates whether the server supports UDP relay and the negotiated bandwidth limits.

### Proxying TCP Traffic

The `Client.TCP` method opens a new QUIC stream and returns a `net.Conn` compatible connection:

```go
conn, err := c.TCP("example.com:80")
if err != nil {
    log.Fatalf("TCP proxy error: %v", err)
}
defer conn.Close()

fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
io.Copy(os.Stdout, conn)

```

Under the hood, this encodes the request using `protocol.WriteTCPRequest` in `core/internal/protocol` and wraps the QUIC stream in a `tcpConn` struct.

### Handling UDP Sessions

For UDP proxying, use `Client.UDP` which leverages QUIC's unreliable datagram extension:

```go
udp, err := c.UDP()
if err != nil {
    log.Fatalf("UDP not available: %v", err)
}
defer udp.Close()

// Send to target address
if err := udp.Send([]byte("hello"), "8.8.8.8:53"); err != nil {
    log.Fatal(err)
}

// Receive response
payload, src, err := udp.Receive()
if err != nil {
    log.Fatal(err)
}

```

## Running the Hysteria Server in Your Application

The `core/v2/server` package allows embedding a Hysteria server that listens for QUIC connections, authenticates clients via the `Hysteria-Auth` header, and forwards traffic.

### Server Configuration

Configure the server using `server.Config` from [`core/server/config.go`](https://github.com/apernet/hysteria/blob/main/core/server/config.go). The `fill` method provides defaults for optional fields:

```go
import (
    "crypto/tls"
    "crypto/x509"
    "github.com/apernet/hysteria/core/v2/server"
    "github.com/apernet/hysteria/core/v2/internal/congestion"
)

cert, _ := tls.LoadX509KeyPair("cert.pem", "key.pem")

cfg := &server.Config{
    TLSConfig: server.TLSConfig{
        Certificates: []tls.Certificate{cert},
    },
    Conn: func() net.PacketConn {
        pc, _ := net.ListenPacket("udp", ":443")
        return pc
    }(),
    Authenticator: myAuth, // Implements server.Authenticator
    BandwidthConfig: server.BandwidthConfig{
        MaxTx: 10 * 1024 * 1024, // 10 MiB/s
        MaxRx: 10 * 1024 * 1024,
    },
    CongestionConfig: server.CongestionConfig{
        Type: congestion.TypeBBR,
    },
}

```

### Implementing Authentication

Provide a type implementing the `server.Authenticator` interface to validate tokens:

```go
type staticAuth struct{}

func (a *staticAuth) Authenticate(addr net.Addr, auth string, tx uint64) (bool, string) {
    if auth == "my-secret-token" {
        return true, "user-123"
    }
    return false, ""
}

```

The returned ID string propagates to traffic logs and can be used for per-user accounting.

### Custom Outbound Handling

Optionally implement `server.Outbound` to control how the server reaches target destinations (default uses `net.Dial` for TCP and `net.ListenUDP` for UDP as seen in `defaultOutbound` within [`server/config.go`](https://github.com/apernet/hysteria/blob/main/server/config.go)).

### Starting the Server

Instantiate and serve using `NewServer` and the blocking `Serve` method:

```go
sv, err := server.NewServer(cfg)
if err != nil {
    log.Fatalf("Server init failed: %v", err)
}
defer sv.Close()

if err := sv.Serve(); err != nil {
    log.Fatalf("Server stopped: %v", err)
}

```

The `Serve` method runs the HTTP/3 handler loop defined in [`server/server.go`](https://github.com/apernet/hysteria/blob/main/server/server.go), dispatching streams via `h3sHandler`.

## Complete Integration Example

Below is a minimal application running both client and server simultaneously:

```go
package main

import (
    "crypto/tls"
    "io"
    "log"
    "net"
    "os"

    "github.com/apernet/hysteria/core/v2/client"
    "github.com/apernet/hysteria/core/v2/server"
)

type staticAuth struct{}

func (a *staticAuth) Authenticate(addr net.Addr, auth string, tx uint64) (bool, string) {
    return auth == "secret-token", "test-user"
}

func main() {
    // Server setup
    cert, _ := tls.LoadX509KeyPair("cert.pem", "key.pem")
    srvCfg := &server.Config{
        TLSConfig: server.TLSConfig{Certificates: []tls.Certificate{cert}},
        Conn: func() net.PacketConn {
            pc, _ := net.ListenPacket("udp", ":8443")
            return pc
        }(),
        Authenticator: &staticAuth{},
    }
    sv, _ := server.NewServer(srvCfg)
    go sv.Serve()

    // Client setup
    cliCfg := &client.Config{
        ServerAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8443},
        Auth:       "secret-token",
        TLSConfig:  client.TLSConfig{ServerName: "localhost", InsecureSkipVerify: true},
    }
    cli, _, _ := client.NewClient(cliCfg)
    defer cli.Close()

    // Proxy a request
    conn, _ := cli.TCP("example.com:80")
    defer conn.Close()
    conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
    io.Copy(os.Stdout, conn)
}

```

## Advanced Integration Patterns

When you integrate Hysteria core with existing applications, consider these extension points from the source code:

- **Request Hooks**: Implement `server.RequestHook` (defined in [`server/config.go`](https://github.com/apernet/hysteria/blob/main/server/config.go)) to inspect or modify inbound TCP/UDP requests before forwarding.
- **Event Logging**: Provide `server.EventLogger` and `server.TrafficLogger` implementations to capture connection events and per-stream statistics for monitoring.
- **Obfuscation**: Wrap the transport with the "Salamander" obfuscator from [`extras/obfs/salamander.go`](https://github.com/apernet/hysteria/blob/main/extras/obfs/salamander.go) to disguise QUIC packets as random ciphertext.
- **Graceful Shutdown**: Always call `Client.Close()` and `Server.Close()` to release QUIC resources and terminate listeners cleanly.

## Summary

- **Import paths**: Use `core/v2/client` and `core/v2/server` from `github.com/apernet/hysteria` to access the library APIs.
- **Configuration**: Both client and server use struct-based configuration (`client.Config`, `server.Config`) with automatic default filling via `verifyAndFill` and `fill` functions.
- **Standard interfaces**: The client returns `net.Conn` compatible objects for TCP and custom UDP session handlers, while the server accepts standard `net.PacketConn` listeners.
- **Extensibility**: Integration points include `server.Authenticator` for custom auth, `server.Outbound` for routing control, and logger interfaces for observability.
- **Resource management**: Both `Client` and `Server` require explicit `Close()` calls to prevent resource leaks.

## Frequently Asked Questions

### How do I handle TLS certificate verification in the client?

Configure the `TLSConfig` field in `client.Config` with `ServerName` for SNI and set `InsecureSkipVerify: true` only for testing. In production, provide a proper `tls.Config` or use the default verification. The client supports pinning via `TLSConfig.PinnedCertFingerprint` as implemented in [`client/config.go`](https://github.com/apernet/hysteria/blob/main/client/config.go).

### Can I use Hysteria core without the command-line tools?

Yes. The `core/client` and `core/server` packages are designed for library use. You can build custom proxies, VPN clients, or tunneling solutions by directly using `NewClient` and `NewServer` without importing the `app` or `cmd` packages.

### How do I enable UDP support in the server?

Set `EnableDatagrams: true` in the server's QUIC configuration. The server automatically negotiates UDP support during the Hysteria handshake (see [`server/server.go`](https://github.com/apernet/hysteria/blob/main/server/server.go)). Clients can then call `Client.UDP()` to obtain a session for sending unreliable datagrams.

### What congestion control algorithms are available?

Both client and server support BBR and Cubic via the `CongestionConfig` struct. Set `Type` to `congestion.TypeBBR` or `congestion.TypeCubic`. BBR includes profiles like "default" and "conservative" for different network conditions, as defined in `core/internal/congestion`.