Skip to main content

SNI-based TLS reverse proxy with termination and passthrough modes

Project description

tls-switch

A TLS reverse proxy that routes incoming TLS connections to backend servers based on the requested hostname using SNI (Server Name Indication). Supports both TLS termination and TLS passthrough on a per-host basis.

tls-switch sits in front of your services on port 443 and inspects the TLS ClientHello to determine which hostname the client is requesting. SNI is a TLS extension that allows the client to indicate which hostname it is trying to connect to before the TLS handshake completes — this is how tls-switch knows where to route the connection without needing a separate IP address per service. It then routes the connection to the appropriate backend, either terminating TLS and forwarding plaintext, or passing the encrypted stream through unmodified.

Features

  • SNI-based routing — route TLS connections to different backends based on hostname
  • TLS termination — terminate TLS with your certificates and forward plaintext to backends
  • TLS passthrough — forward the raw TLS stream to a backend that handles its own TLS
  • Hot reload — config and certificate changes take effect on new connections without interrupting existing ones
  • Zero buffering — data is forwarded immediately with no processing, filtering, or modification
  • PROXY protocol — optional v1/v2 header emission per host so backends can see the original client IP
  • Zero runtime dependencies — single statically-linked Go binary, no Python or libc required at runtime

Use Cases

  • Run multiple HTTPS services on a single IP address, each with its own certificate
  • Put a TLS-terminating proxy in front of plain HTTP services
  • Route some domains through to their own TLS servers while terminating others locally
  • Consolidate port 443 across multiple services without a full reverse proxy

Requirements

  • Python 3.12+ (only required to install from PyPI; the binary itself has no runtime dependencies)
  • Root/administrator privileges (if binding to port 443)

Installation

pip install tls-switch

Or run directly with uv:

uvx tls-switch

Quick Start

Create a config file (config.json):

{
  "listen": ":443",
  "hosts": {
    "app.example.com": {
      "mode": "terminate",
      "cert": "/etc/tls-switch/app.example.com.crt",
      "key": "/etc/tls-switch/app.example.com.key",
      "backend": "127.0.0.1:8080"
    },
    "legacy.example.com": {
      "mode": "passthrough",
      "backend": "10.0.0.5:443",
      "proxy_protocol": "v2"
    }
  }
}

Run the server:

tls-switch -c config.json

In this example:

  • Connections to app.example.com have TLS terminated by tls-switch, and plaintext HTTP is forwarded to 127.0.0.1:8080
  • Connections to legacy.example.com are forwarded as raw TLS to 10.0.0.5:443, which handles its own certificates

How It Works

  1. A client connects to port 443 and begins a TLS handshake
  2. tls-switch reads the TLS ClientHello message and extracts the SNI (Server Name Indication) hostname
  3. The hostname is looked up in the configuration
  4. Depending on the mode:
    • terminate: tls-switch completes the TLS handshake using the configured certificate and key, then opens a plaintext TCP connection to the backend and copies data bidirectionally with no buffering or processing
    • passthrough: tls-switch opens a TCP connection to the backend, replays the original ClientHello, and then copies data bidirectionally — the backend server handles the TLS handshake itself
  5. If the hostname is not found in the configuration, tls-switch completes a TLS handshake using any available configured certificate and returns an HTTP 421 Misdirected Request error page — browsers display a clear error rather than a cryptic "can't connect" message

Configuration

Config File

The config file is JSON with the following structure:

{
  "listen": ":443",
  "hosts": {
    "hostname": {
      "mode": "terminate|passthrough",
      "cert": "/path/to/cert.pem",
      "key": "/path/to/key.pem",
      "backend": "host:port",
      "proxy_protocol": "v1|v2"
    }
  }
}
Field Description
listen Address to listen on (e.g. :443, 0.0.0.0:8443)
hosts Map of hostname to route configuration
mode terminate (TLS termination) or passthrough (forward raw TLS)
cert Path to PEM certificate file (terminate mode only)
key Path to PEM private key file (terminate mode only)
backend Backend address as host:port
proxy_protocol Optional. v1 (text) or v2 (binary) to emit a PROXY protocol header to the backend so it sees the original client IP. Works in both modes. The backend must be configured to expect it, and only to trust PROXY headers from the tls-switch listener address — otherwise clients can spoof their source IP. Omit to disable (default).

Hot Reload

tls-switch watches the config file and certificate files for changes. When a change is detected:

  • The new config is validated
  • If valid, new connections use the updated config
  • Existing connections continue with their original config until they close naturally
  • If invalid, the change is rejected and the current config remains active

Development

# Set up development environment
make dev

# Cross-compile Go binaries
make go-build

# Run format check and lint
make check

# Auto-format code
make format

# Build wheel and docs
make build

Architecture

tls-switch is a single statically-linked Go binary with no runtime dependencies. It bundles:

  • TCP listener and accept loop
  • TLS ClientHello parsing and SNI extraction
  • TLS termination via the Go crypto/tls standard library
  • Raw passthrough using io.Copy for zero-copy forwarding where supported by the OS
  • Optional PROXY protocol v1/v2 header emission to the backend
  • Config + certificate file watching for hot reload
  • Coloured terminal logging

The binary is built with CGO_ENABLED=0 and works across macOS, Linux, and Windows on amd64 + arm64. It is distributed as platform-specific Python wheels for ease of installation via pip / uvx, but no Python is required to run it.

Licence

Released under the Unlicense — public domain.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

tls_switch-1.1.0b1-py3-none-win_arm64.whl (2.0 MB view details)

Uploaded Python 3Windows ARM64

tls_switch-1.1.0b1-py3-none-win_amd64.whl (2.3 MB view details)

Uploaded Python 3Windows x86-64

tls_switch-1.1.0b1-py3-none-manylinux_2_17_x86_64.whl (2.2 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

tls_switch-1.1.0b1-py3-none-manylinux_2_17_aarch64.whl (2.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

tls_switch-1.1.0b1-py3-none-macosx_11_0_arm64.whl (2.1 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

tls_switch-1.1.0b1-py3-none-macosx_10_9_x86_64.whl (2.3 MB view details)

Uploaded Python 3macOS 10.9+ x86-64

File details

Details for the file tls_switch-1.1.0b1-py3-none-win_arm64.whl.

File metadata

  • Download URL: tls_switch-1.1.0b1-py3-none-win_arm64.whl
  • Upload date:
  • Size: 2.0 MB
  • Tags: Python 3, Windows ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for tls_switch-1.1.0b1-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 511fdb1863bc206be227d7547212ecc2899312cf5f9699f93bf28a2cca6d8698
MD5 1cdd616fccd0bb44146e12a4e51ee726
BLAKE2b-256 059c237f832ddfd4015cb80cb59bbef80609644b47162e44f73f952e7fad033e

See more details on using hashes here.

File details

Details for the file tls_switch-1.1.0b1-py3-none-win_amd64.whl.

File metadata

  • Download URL: tls_switch-1.1.0b1-py3-none-win_amd64.whl
  • Upload date:
  • Size: 2.3 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for tls_switch-1.1.0b1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 39090b79c9f98a6542a0c3ad47dd3bd87359d0586b9056e58699316ce8e9ab99
MD5 70128826c54d17856bde94c8f3e3ef6c
BLAKE2b-256 10536fd664e9abd746529fabc4b7225df7ebef242e68a631d3411328a5141f00

See more details on using hashes here.

File details

Details for the file tls_switch-1.1.0b1-py3-none-manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for tls_switch-1.1.0b1-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 d65b643b45a65f1be44b1d721cac6f7a544437bc5dfcd01db098cc6bbade4827
MD5 6dc99f986c0f53f02ef5570c1b89b5b2
BLAKE2b-256 3f93e5df84ece7eca7de765a24b7e695d16e7efbad15a9f45e3dccfeb8aaeaf8

See more details on using hashes here.

File details

Details for the file tls_switch-1.1.0b1-py3-none-manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for tls_switch-1.1.0b1-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 455f305c71f5555185981d04744dc6caf452696e1fe1b6978727cb3d0e2368d8
MD5 53110e0d89d26f47cbb47b05d02bbe85
BLAKE2b-256 7bc70d8dd03156bca9c4095b89e12a0fff189137d0e325a7130d2e725fa467aa

See more details on using hashes here.

File details

Details for the file tls_switch-1.1.0b1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for tls_switch-1.1.0b1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 68f10ded4eae983fe9121643a53c882124e614ae662b7d8f27af7397318a7ce7
MD5 0cdb539720e2ce4198e241b041e6bf16
BLAKE2b-256 8e52dbcd099699b72e3dfd99f80c9f4e7e90ab9fe3fa474c1c692f836a374a5e

See more details on using hashes here.

File details

Details for the file tls_switch-1.1.0b1-py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for tls_switch-1.1.0b1-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 817b64d3a3bbd2ec6b64add92e47a5d82f2866c974e60cfacf6d2cf5738a2b13
MD5 4f59b517fd8163f88a2b85fb04552e3e
BLAKE2b-256 841339863cae3b54c1f8a990e44b0b353574b51f4cbca234b172fe66c48c08b1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page