sniproxy

command module
v0.0.0-...-a431a55 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 25, 2025 License: GPL-3.0 Imports: 15 Imported by: 0

README

SNI Proxy

A high-performance SNI (Server Name Indication) proxy implemented in Go that intelligently routes TLS connections to different backend servers based on the SNI hostname.

Features

  • 🚀 SNI-based Routing - Route TLS connections to different backends based on the SNI hostname
  • 🔄 Proxy Protocol Support - Preserve original client IP addresses using PROXY protocol v1/v2
  • 🎯 Wildcard Matching - Support for wildcard rules (e.g., *.example.com)
  • ⚙️ Port-based Rules - Define routing rules per listening port
  • 📝 YAML Configuration - Easy-to-read configuration format
  • 🔒 TLS Transparent - Works with any TLS version (1.0-1.3)
  • 🛡️ Graceful Fallback - Handle connections without SNI or with parsing errors
  • Zero-downtime - Concurrent connection handling with goroutines

How It Works

  1. Client initiates a TLS connection to the proxy
  2. Proxy reads the TLS ClientHello message
  3. Extracts the SNI (Server Name Indication) field
  4. Matches SNI against configured routing rules
  5. Forwards the connection to the appropriate backend server
  6. Optionally adds PROXY protocol header to preserve client IP

Installation

From Source
go install github.com/zamibd/sniproxy@latest
Build Locally
git clone https://github.com/zamibd/sniproxy.git
cd sniproxy
go build -o sniproxy .
Using Docker

Pull the pre-built image from Docker Hub:

# Pull the latest image
docker pull imzami/sniproxy:latest

# Run the container
docker run -d \
  --name sniproxy \
  -p 443:443 \
  -v $(pwd)/config.yaml:/etc/sniproxy/config.yaml:ro \
  imzami/sniproxy:latest

Or build locally:

docker build -t imzami/sniproxy .
docker run -v $(pwd)/config.yaml:/etc/sniproxy/config.yaml -p 443:443 imzami/sniproxy

Configuration

Create a config.yaml file:

# Default destination when no SNI match is found
# or TLS parsing fails
default: 127.0.0.1:8443

# Ports to listen on
# Can specify multiple ports
listen:
    - 443
    - 9999

# Forward rules
# Supports exact match and wildcard patterns
forward_rules:
    # Exact match with PROXY protocol v2
    www.example.com: 127.0.0.1:8443 proxy-v2
    
    # Exact match with PROXY protocol v1
    api.example.com: 10.0.0.10:8443 proxy-v1
    
    # Exact match without PROXY protocol
    blog.example.com: 10.0.0.20:443
    
    # Wildcard match - forward based on SNI
    # Example: SNI "app.example.com" from port 9999 → app.example.com:443
    "*:9999": "*:443"
    
    # Wildcard with specific backend
    "*.internal.example.com": 192.168.1.100:443 proxy-v2

Usage

Basic Usage
# Use default config file (config.yaml)
./sniproxy

# Specify config file
./sniproxy -c /path/to/config.yaml

# With logging
./sniproxy -c config.yaml -log_level DEBUG -log_file /var/log/sniproxy.log
Command-line Options
-c string
    Path to config file (default "config.yaml")
    
-log_file string
    Path to log file (default: stdout)
    
-log_level string
    Log level: DEBUG, INFO, WARN, ERROR (default "INFO")

Examples

Example 1: Multi-tenant HTTPS Routing

Route multiple domains to different backend servers:

default: 127.0.0.1:8080

listen:
    - 443

forward_rules:
    app1.example.com: 10.0.1.10:443
    app2.example.com: 10.0.1.20:443
    app3.example.com: 10.0.1.30:443
Example 2: Load Balancer with Client IP Preservation

Forward to backend with PROXY protocol to preserve client IPs:

default: 127.0.0.1:8443

listen:
    - 443

forward_rules:
    # Backend servers that support PROXY protocol
    website.com: 10.0.0.10:443 proxy-v2
    api.website.com: 10.0.0.20:443 proxy-v2
Example 3: Dynamic Wildcard Routing

Route based on subdomain patterns:

default: 127.0.0.1:8443

listen:
    - 443
    - 9999

forward_rules:
    # Forward *.apps.example.com to the same hostname on port 8443
    "*.apps.example.com": "*:8443"
    
    # Port-specific wildcard: anything on port 9999 goes to port 443
    "*:9999": "*:443"
Example 4: Kubernetes Ingress Alternative

Use as a simple TLS router for Kubernetes services:

default: default-backend.default.svc.cluster.local:443

listen:
    - 443

forward_rules:
    service1.example.com: service1.namespace1.svc.cluster.local:443 proxy-v2
    service2.example.com: service2.namespace2.svc.cluster.local:443 proxy-v2
    "*.staging.example.com": "*-staging.default.svc.cluster.local:443"

PROXY Protocol

SNI Proxy supports PROXY protocol v1 and v2 to preserve the original client IP address when forwarding to backend servers.

Backend Configuration

Your backend servers need to support PROXY protocol:

nginx:

server {
    listen 443 ssl proxy_protocol;
    
    real_ip_header proxy_protocol;
    set_real_ip_from 10.0.0.0/8;
    
    # ...
}

HAProxy:

frontend https
    bind :443 ssl crt /path/to/cert.pem accept-proxy

Go (stdlib):

import "github.com/pires/go-proxyproto"

listener, _ := net.Listen("tcp", ":443")
proxyListener := &proxyproto.Listener{Listener: listener}

Testing

Run the test suite:

# Run all tests
go test -v ./...

# Run specific test
go test -v -run TestProxyProto

# Run tests with coverage
go test -v -cover ./...

Performance

  • Concurrent Connections: Handles thousands of concurrent connections using goroutines
  • Low Latency: Minimal overhead - only reads TLS ClientHello
  • Memory Efficient: Streaming proxying with configurable buffer sizes
  • Zero Configuration SSL: No SSL certificates needed on the proxy itself

Deployment

Systemd Service

Create /etc/systemd/system/sniproxy.service:

[Unit]
Description=SNI Proxy
After=network.target

[Service]
Type=simple
User=sniproxy
ExecStart=/usr/local/bin/sniproxy -c /etc/sniproxy/config.yaml -log_level INFO
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Enable and start:

sudo systemctl enable sniproxy
sudo systemctl start sniproxy
Docker
Pre-built Image

The official Docker image is available on Docker Hub:

docker pull imzami/sniproxy:latest

Available tags:

  • latest - Latest stable release from master branch
  • v1.0.0, v1.0, v1 - Semantic version tags
  • main-<sha> - Specific commit from main branch
Building the Image

Build the optimized Docker image (uses multi-stage build with Go 1.24):

# Build with default tag
docker build -t imzami/sniproxy:latest .

# Build with specific version tag
docker build -t imzami/sniproxy:v1.0.0 .

# Build for multiple architectures
docker buildx build --platform linux/amd64,linux/arm64 -t imzami/sniproxy:latest .
Running with Docker

Basic usage:

docker run -d \
  --name sniproxy \
  -p 443:443 \
  -v $(pwd)/config.yaml:/etc/sniproxy/config.yaml:ro \
  imzami/sniproxy:latest

With custom config and multiple ports:

docker run -d \
  --name sniproxy \
  -p 443:443 \
  -p 9999:9999 \
  -v $(pwd)/config.yaml:/etc/sniproxy/config.yaml:ro \
  -v $(pwd)/logs:/var/log/sniproxy \
  imzami/sniproxy:latest -log_level INFO -log_file /var/log/sniproxy/sniproxy.log

View logs:

docker logs -f sniproxy

Stop and remove:

docker stop sniproxy
docker rm sniproxy
Docker Compose

Create a docker-compose.yml file for easy orchestration:

Basic setup:

version: '3.8'

services:
  sniproxy:
    image: imzami/sniproxy:latest
    container_name: sniproxy
    ports:
      - "443:443"
      - "9999:9999"
    volumes:
      - ./config.yaml:/etc/sniproxy/config.yaml:ro
    restart: unless-stopped
    networks:
      - proxy-net

networks:
  proxy-net:
    driver: bridge

Production setup with health checks and logging:


services:
  sniproxy:
    image: imzami/sniproxy:latest
    container_name: sniproxy
    ports:
      - "443:443"
      - "9999:9999"
    volumes:
      - ./config.yaml:/etc/sniproxy/config.yaml:ro
      - ./logs:/var/log/sniproxy
    restart: always
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - proxy-net
    # Health check (adjust port as needed)
    healthcheck:
      test: ["CMD", "nc", "-z", "localhost", "443"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

networks:
  proxy-net:
    driver: bridge

Start services:

# Start in foreground
docker compose up

# Start in background
docker compose up -d

# View logs
docker compose logs -f

# Stop services
docker compose down

# Rebuild and restart
docker compose up -d --build
Docker Networking

Bridge network (default):

# Backend services on same Docker network
forward_rules:
    app.example.com: backend-service:443
    api.example.com: api-service:8443

Host network (better performance):

docker run -d \
  --name sniproxy \
  --network host \
  -v $(pwd)/config.yaml:/etc/sniproxy/config.yaml:ro \
  imzami/sniproxy:latest

Custom network:

# Create network
docker network create --driver bridge sni-network

# Run with custom network
docker run -d \
  --name sniproxy \
  --network sni-network \
  -p 443:443 \
  -v $(pwd)/config.yaml:/etc/sniproxy/config.yaml:ro \
  imzami/sniproxy:latest
Docker Image Details

The optimized Dockerfile provides:

  • Minimal Size: ~2MB final image using FROM scratch
  • Security: Runs as non-root user (UID 65534)
  • Static Binary: No external dependencies required
  • Multi-stage Build: Separates build and runtime environments
  • Layer Caching: Optimized for fast rebuilds
  • Multi-architecture: Supports both amd64 and arm64

Official Image: imzami/sniproxy:latest

Image structure:

imzami/sniproxy:latest
├── /bin/sniproxy              # Static binary (~2MB)
├── /etc/ssl/certs/            # CA certificates
├── /usr/share/zoneinfo/       # Timezone data
└── /etc/sniproxy/config.yaml  # Default config (override with volume)
Kubernetes Deployment

Deploy SNI Proxy in Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sniproxy
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sniproxy
  template:
    metadata:
      labels:
        app: sniproxy
    spec:
      containers:
      - name: sniproxy
        image: imzami/sniproxy:latest
        ports:
        - containerPort: 443
          protocol: TCP
        volumeMounts:
        - name: config
          mountPath: /etc/sniproxy
          readOnly: true
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
      volumes:
      - name: config
        configMap:
          name: sniproxy-config
---
apiVersion: v1
kind: Service
metadata:
  name: sniproxy
  namespace: default
spec:
  type: LoadBalancer
  ports:
  - port: 443
    targetPort: 443
    protocol: TCP
  selector:
    app: sniproxy
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: sniproxy-config
  namespace: default
data:
  config.yaml: |
    default: 127.0.0.1:8443
    listen:
      - 443
    forward_rules:
      service1.example.com: service1.default.svc.cluster.local:443
      service2.example.com: service2.default.svc.cluster.local:443

Troubleshooting

Enable Debug Logging
./sniproxy -c config.yaml -log_level DEBUG
Common Issues

Connection refused:

  • Check if backend servers are accessible
  • Verify firewall rules allow connections

SNI not extracted:

  • Ensure clients send SNI in TLS handshake
  • Check TLS version compatibility (some old clients don't support SNI)

PROXY protocol errors:

  • Verify backend supports PROXY protocol
  • Check PROXY protocol version (v1 vs v2)
  • Ensure proxy-v1 or proxy-v2 is specified in config

Architecture

Client → SNI Proxy → Backend Server
         ↓
    1. Read TLS ClientHello
    2. Extract SNI
    3. Match routing rules
    4. Add PROXY header (optional)
    5. Forward connection

Security Considerations

  • SNI Proxy does not decrypt TLS traffic - it only reads the unencrypted ClientHello
  • Ensure backend servers validate TLS certificates
  • Use PROXY protocol only on trusted internal networks
  • Consider rate limiting at firewall level
  • Monitor for unusual traffic patterns

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the terms specified in the LICENSE file.

Credits

Based on the original sniproxy implementation with improvements for stability and features.

Support

For bugs and feature requests, please create an issue on the GitHub repository.

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL