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
  • Efficient — Go networking engine with zero-copy forwarding, Python CLI for configuration and management
  • Zero runtime dependencies — Python 3.12+ stdlib only, Go binary is statically linked

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+
  • 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"
    }
  }
}

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"
    }
  }
}
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

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 linting and type checking
make check

# Auto-format code
make format

# Build wheel and docs
make build

Architecture

tls-switch is a Python+Go hybrid:

  • Go handles all networking — TCP listener, TLS handshakes, SNI extraction, and bidirectional data forwarding. It runs as a persistent subprocess communicating with Python via JSON Lines over stdin/stdout.
  • Python handles everything user-facing — CLI, config file parsing and validation, certificate validation, file watching, and error reporting.

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.0.0b3-py3-none-win_arm64.whl (24.8 kB view details)

Uploaded Python 3Windows ARM64

tls_switch-1.0.0b3-py3-none-win_amd64.whl (24.8 kB view details)

Uploaded Python 3Windows x86-64

tls_switch-1.0.0b3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.3 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

tls_switch-1.0.0b3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (24.8 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

tls_switch-1.0.0b3-py3-none-macosx_11_0_arm64.whl (24.8 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

tls_switch-1.0.0b3-py3-none-macosx_10_12_x86_64.whl (24.8 kB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file tls_switch-1.0.0b3-py3-none-win_arm64.whl.

File metadata

  • Download URL: tls_switch-1.0.0b3-py3-none-win_arm64.whl
  • Upload date:
  • Size: 24.8 kB
  • 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.0.0b3-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 ff613b84787116ad0616650bbcd56bf52886b4a5a295a2c9d8894fd8278f7f99
MD5 7e9ef9265287a9768fabefa73c073f17
BLAKE2b-256 f6aa8f4e6620b92d96c4969b5e71e00e486f9ea37c37bc6c301daae5c07126fa

See more details on using hashes here.

File details

Details for the file tls_switch-1.0.0b3-py3-none-win_amd64.whl.

File metadata

  • Download URL: tls_switch-1.0.0b3-py3-none-win_amd64.whl
  • Upload date:
  • Size: 24.8 kB
  • 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.0.0b3-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 0913c8ae7b0c23c8a1c5d26a633162d320da8337abb2660eb8fbf7908c08a34a
MD5 502405271fd8926437082acb15581074
BLAKE2b-256 dc062408a37cf29325e51b96ba0db64a99d4bd9f73935f694c52c16e52fccc14

See more details on using hashes here.

File details

Details for the file tls_switch-1.0.0b3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for tls_switch-1.0.0b3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6db58c3515ad5e29966ee3a35bedf0328c1eae462cf75dc46cb36b41d0b4cbce
MD5 6aa24191fc4ded7854c66ecce71e2694
BLAKE2b-256 97a2879dea27b87aefef27613fc28b45217cf387d7e3cc4337f74b4611aafb1b

See more details on using hashes here.

File details

Details for the file tls_switch-1.0.0b3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for tls_switch-1.0.0b3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 5f4c6c6aa46a5657afc5275eba1bde223c9596cae161c74a0c8365bae3b9286e
MD5 d9f674fc9eab98973eaa8658f41df3e3
BLAKE2b-256 ee8484ee8e61b5a86258637bbdef67d2fab8f6894f24f8aedad6f5f868d2bfd8

See more details on using hashes here.

File details

Details for the file tls_switch-1.0.0b3-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for tls_switch-1.0.0b3-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c3d8c6678de4d74abe629924cdd5b1fcbf2a3c3158b2162076aaf6ea1bbc1f03
MD5 a6ef528600aadb5e7fcc4c45982b0459
BLAKE2b-256 555b6483a9bac546d2ac768b4282b0ee88eecc9f0654d4b4205764cb5f6e3d44

See more details on using hashes here.

File details

Details for the file tls_switch-1.0.0b3-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for tls_switch-1.0.0b3-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 59daaeacff1a2539fee29e7b3559bcb4a2727dfbfeb01bd7640997b6bab6c4f4
MD5 7d63fcf4b3024e5e95d2a20d8e1f5cf6
BLAKE2b-256 b35493f8e5d5dd0109e3eda49c9ffbb9477105a6fe18bee540482e27b9cef937

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