SuhaibServer is a reverse proxy designed to simplify and enhance TLS and mTLS (Mutual TLS) management beyond what is typically possible in solutions like NGINX or Apache. By leveraging SNI (Server Name Indication), SuhaibServer allows you to define highly granular routing and security policies per domain—right down to specific URLs and query parameters.
- Route incoming connections based on SNI immediately.
- Simplify your configuration by grouping all TLS/mTLS rules according to the hostname.
- TCP Pass-Through: Let the origin server handle TLS/mTLS completely.
- TLS Termination: Terminate TLS at SuhaibServer, and pass requests on to the origin via HTTP or HTTPS.
- Granular mTLS: Enforce mTLS on a per-path or per-query-parameter basis, or allow requests through without client certificates.
- Configure different root CAs for each hostname or route.
- Control certificate validation behavior for one service without affecting another.
- Easily combine criteria such as routes, query parameters, SNI, and more.
- Use logical conditions to decide whether to perform mTLS validation or simply forward traffic.
- Explicitly define origin servers for each route.
- Combine SuhaibServer’s advanced SNI logic with your existing setups for maximum compatibility.
- Fine-Grained mTLS Control: Most reverse proxies only allow mTLS to be toggled on or off globally or at best at the server block level. SuhaibServer’s per-route customization puts you in full control.
- SNI-First Logic: Routing is decided purely on the SNI before any additional overhead—perfect for multi-tenant or complex architectures.
- Clear, Modular Configuration: Each domain or path can have a dedicated set of TLS rules, making your configuration easier to maintain and reason about.
- Seamless Integration: Insert SuhaibServer into your stack without disrupting existing workflows, whether you need pass-through or termination for TLS.
-
Clone the repository:
git clone https://github.com/Suhaibinator/SuhaibServer.git cd SuhaibServer -
Build the binary:
go build -o suhaibserver ./cmd/main.go
(Requires Go 1.24+ or newer.)
-
Run the binary with a config file:
./suhaibserver /path/to/config.yaml
SuhaibServer exposes an SDK in sdk/hooks so you can register request and completion hooks in your own code. Hooks are matched by host, path prefix, and HTTP method from YAML, and are executed in priority order.
import "github.com/Suhaibinator/SuhaibServer/sdk/hooks"
func init() {
hooks.Register(hooks.Registration{
Name: "audit",
Kind: hooks.OnRequestReceived,
Handler: hooks.RequestHook(func(ctx context.Context, rc hooks.RequestCtx) error {
log.Printf("trace=%s host=%s path=%s", rc.TraceID, rc.Host, rc.Path)
return nil
}),
})
hooks.Register(hooks.Registration{
Name: "metrics",
Kind: hooks.OnRequestCompleted,
Handler: hooks.CompletionHook(func(ctx context.Context, rc hooks.ResponseCtx) error {
fmt.Printf("status=%d latency=%s err=%v\n", rc.Status, rc.Latency, rc.Err)
return nil
}),
Priority: 10,
})
}Map those names into config.yaml:
Hooks:
- name: audit
kind: on_request_received
match:
host: example.com
path_prefix: /api
methods: [GET, POST]
timeout: 500ms
- name: metrics
kind: on_request_completed
match:
host: example.com
Backends:
- Hostname: example.com
# ...TLS/origin config...
Hooks:
on_request_received: [audit]
on_request_completed: [metrics]If the client presents a certificate, request hooks receive it in RequestCtx.ClientCert (including a SHA-256 fingerprint) so you can identify the caller.
If you prefer to embed SuhaibServer in your own binary, the public server package
exposes a blocking runner you can call from main.go:
package main
import (
"context"
"log"
"github.com/Suhaibinator/SuhaibServer/server"
)
func main() {
// The call blocks until the process receives a shutdown signal or an error occurs.
if err := server.RunWithConfigFile(context.Background(), "./config.yaml"); err != nil {
log.Fatalf("server failed: %v", err)
}
}Build and run it just like the bundled CLI:
go build -o suhaibserver ./cmd/main.go
./suhaibserver ./config.yamlWe publish a Docker image that can be used directly, or you can build it yourself.
docker pull suhaibinator/suhaibserver:latest
docker run -d \
-p 443:443 \
-v /host/certs/:/etc/certs/:ro \
-v /host/config.yaml:/etc/suhaib/config.yaml:ro \
suhaibinator/suhaibserver:latest \
/etc/suhaib/config.yaml-v /host/certs/:/etc/certs/:romakes certificates available to the container at/etc/certs/.-v /host/config.yaml:/etc/suhaib/config.yaml:romounts your config file inside the container.- The last argument (
/etc/suhaib/config.yaml) tells SuhaibServer which config file to load.
If you have cloned the repo, run:
docker build -t my-suhaibserver .
docker run -d \
-p 443:443 \
-v /host/certs/:/etc/certs/:ro \
-v /host/config.yaml:/etc/suhaib/config.yaml:ro \
my-suhaibserver:latest \
/etc/suhaib/config.yamlYou can also use docker compose to manage your SuhaibServer instance, and provide the configuration file as a Docker Config inline:
version: '3.8'
services:
suhaibserver:
image: ghcr.io/suhaibinator/suhaibserver:latest
ports:
- "443:443"
configs:
- source: suhaibserver_config
target: /etc/suhaibserver/config.yaml
volumes:
- /etc/nginx/ssl/:/etc/certs/:ro
restart: always
configs:
suhaibserver_config:
content: |
# Example SuhaibServer Configuration
# SniSniffer controls how we read the first few bytes of an incoming connection
# to detect the SNI (Server Name Indication).
SniSniffer:
# MaxReadSize is the maximum number of bytes to peek from the client
# to extract the SNI. If the SNI can't be found within this range,
# the connection is closed or handled as an error.
MaxReadSize: 4096
# Timeout is how long (e.g. 5s, 500ms, etc.) we wait for TLS handshake data
# when sniffing for SNI. If this timeout is exceeded, we fail the connection.
Timeout: 5s
# Backends is a list of per-hostname configurations, each describing
# how to handle connections to a particular SNI.
Backends:
- hostname: example.com
# If mTLS is enabled, we consult MTLSPolicy to see which paths or
# queries require (or exclude) client certificates.
MTLSEnabled: true
# The MTLSPolicy below means: by default, do NOT require mTLS (Default=false),
# but invert that default if the path starts with `/admin` or if the query param
# "token" is present—those routes *will* require mTLS.
MTLSPolicy:
Default: false
Paths:
- /admin
Queries:
- token
# TerminateTLS=true means SuhaibServer will handle TLS termination locally
# (i.e., present cert/key) and then forward plain HTTP (or HTTPS) to the Origin.
TerminateTLS: true
TLSCertFile: example.com.crt
TLSKeyFile: example.com.key
# RootCAFile is used to verify client certificates if mTLS is triggered.
# If you leave it blank, partial mTLS still works (the handshake won't *force*
# a client cert). This is just an example path.
RootCAFile: ca.crt
# OriginScheme indicates whether to connect to the origin via HTTP or HTTPS.
OriginScheme: http
# OriginServer / OriginPort describe where to forward traffic after TLS termination.
# For example, maybe your app is running on localhost:8080 inside the container.
OriginServer: 127.0.0.1
OriginPort: "8080"
- hostname: foo.bar
# In this example, we do NOT enable mTLS or TLS termination for foo.bar,
# which means we do a raw TCP pass-through to the origin.
MTLSEnabled: false
TerminateTLS: false
# Because we are not terminating TLS, these fields are optional or unused:
# TLSCertFile, TLSKeyFile, RootCAFile
# They can be omitted or left blank in YAML if you wish.
# The origin is presumably a TLS-enabled service on 192.168.0.10:443.
OriginScheme: https
OriginServer: 192.168.0.10
OriginPort: "443"
volumes:
certs-volume:
driver: local
driver_opts:
type: none
o: bind
device: /etc/nginx/ssl/Below is an example YAML configuration (config.example.yaml). It shows how to:
- Configure SNI sniffing (max read size and handshake timeout).
- Define one or more Backends (one per hostname).
- Control whether to terminate TLS, enable mTLS, or pass-through raw TCP.
# SniSniffer controls how we read the initial bytes of an incoming TLS connection
# to detect the SNI.
SniSniffer:
MaxReadSize: 4096
Timeout: 5s
Backends:
- hostname: example.com
MTLSEnabled: true
MTLSPolicy:
# Default=false => do NOT require mTLS by default
# But invert the default if the path starts with /admin or the query param "token" is present
default: false
paths:
- /admin
queries:
- token
TerminateTLS: true
TLSCertFile: example.com.crt
TLSKeyFile: example.com.key
RootCAFile: ca.crt
OriginScheme: http
OriginServer: 127.0.0.1
OriginPort: "8080"
- hostname: foo.bar
MTLSEnabled: false # no mTLS for foo.bar
TerminateTLS: false # pass-through raw TCP
OriginScheme: https
OriginServer: 192.168.0.10
OriginPort: "443"- If a path in the config is not absolute (e.g.
"example.com.crt"), SuhaibServer can automatically prepend/etc/certs/internally (depending on your setup). - In Docker, you’d likely mount your certs into the container at
/etc/certs/.
suhaibserver <config-file><config-file>can be YAML or JSON.- The config must define
SniSnifferparameters and at least oneBackend. - Each
Backendincludes:Hostname- matches the SNI requested by the client.TerminateTLS(true/false).MTLSEnabled(true/false) and optionalMTLSPolicyfor partial or conditional mTLS.TLSCertFile,TLSKeyFile,RootCAFile(paths to certificates/keys).OriginScheme(httporhttps), plusOriginServerandOriginPort(where traffic is forwarded).
Use MTLSEnabled: true to enable partial or full mTLS. Then define MTLSPolicy to choose whether mTLS is the default or an exception:
default: true: all paths/queries require a client cert unless they match the listed paths or queries (which become “no-mTLS” exceptions).default: false: no paths/queries require a client cert unless they match the listed paths or queries (which become “require-mTLS” exceptions).
Examples:
MTLSPolicy:
default: false
paths:
- /admin # routes starting with /admin require mTLS
queries:
- token # any request containing ?token=... in the query string requires mTLS- Fork this repository and create a branch for your feature or bugfix.
- Test your changes with
go test ./.... - Submit a Pull Request and fill out the template.
SuhaibServer is licensed under the MIT License.