How to Integrate Hysteria Core with Existing Go Applications
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:
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. The verifyAndFill function automatically applies sensible defaults for omitted fields:
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:
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:
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:
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. The fill method provides defaults for optional fields:
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:
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).
Starting the Server
Instantiate and serve using NewServer and the blocking Serve method:
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, dispatching streams via h3sHandler.
Complete Integration Example
Below is a minimal application running both client and server simultaneously:
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 inserver/config.go) to inspect or modify inbound TCP/UDP requests before forwarding. - Event Logging: Provide
server.EventLoggerandserver.TrafficLoggerimplementations to capture connection events and per-stream statistics for monitoring. - Obfuscation: Wrap the transport with the "Salamander" obfuscator from
extras/obfs/salamander.goto disguise QUIC packets as random ciphertext. - Graceful Shutdown: Always call
Client.Close()andServer.Close()to release QUIC resources and terminate listeners cleanly.
Summary
- Import paths: Use
core/v2/clientandcore/v2/serverfromgithub.com/apernet/hysteriato access the library APIs. - Configuration: Both client and server use struct-based configuration (
client.Config,server.Config) with automatic default filling viaverifyAndFillandfillfunctions. - Standard interfaces: The client returns
net.Conncompatible objects for TCP and custom UDP session handlers, while the server accepts standardnet.PacketConnlisteners. - Extensibility: Integration points include
server.Authenticatorfor custom auth,server.Outboundfor routing control, and logger interfaces for observability. - Resource management: Both
ClientandServerrequire explicitClose()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.
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). 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →