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.0b12-py3-none-win_arm64.whl (2.0 MB view details)

Uploaded Python 3Windows ARM64

tls_switch-1.0.0b12-py3-none-win_amd64.whl (2.3 MB view details)

Uploaded Python 3Windows x86-64

tls_switch-1.0.0b12-py3-none-manylinux_2_17_x86_64.whl (2.2 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

tls_switch-1.0.0b12-py3-none-manylinux_2_17_aarch64.whl (2.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

tls_switch-1.0.0b12-py3-none-macosx_11_0_arm64.whl (2.1 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

tls_switch-1.0.0b12-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.0.0b12-py3-none-win_arm64.whl.

File metadata

File hashes

Hashes for tls_switch-1.0.0b12-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 3b031c9dc7d07119083cf2f79b5fce8bc17567d1d6e1cd9424f4445280153e13
MD5 ce79d075469fad2de7346a8b4d2e1dda
BLAKE2b-256 55470d6a7a0991589f7f614b2aeb218d1cb6d2cc50887dc50dc7b67ef87e019d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b12-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 b409e6f9b86f0ba4fd2a2f37ec93adb001834b28eca6e651a8d4f300eed5ea02
MD5 fe83de704774b4b9fec146ad6d5e0c0c
BLAKE2b-256 4de869972a42ef4c38c35149ec824435d61e9c4afd28c209f0ffc3308fa26990

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b12-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 8395838ef6bf2b5dbd5596fd56111086078f10e2512de5894032526ffda2bc7a
MD5 d04a417dc6eafeb034848b55f7a7d489
BLAKE2b-256 91525bc09a6d68a1ae23688695778eae3a25cc0e3dad9f7b68c044df2e86e09b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b12-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 fbcf1809088ce63657d784d36e149e8f6b128d5949b45009e97f64ce1414cf4c
MD5 5c5a3db6dd7ff1a300cd4bfcee915fe4
BLAKE2b-256 fbe2ddf712a2ab2f4b03198c3057e9a831b27eae5c5d89e8854f3fe0b6d4a679

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b12-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a9b6abc76fa9842e1475ba28019ade7d4a1ca80d5bea9c128a53eb88a20d5615
MD5 f49bda15403af48d38e7bd04559c980b
BLAKE2b-256 701dbcc4e7b683b7784c40500525319b06fd29ac2e06f8c64ea3e6b61de06e5b

See more details on using hashes here.

File details

Details for the file tls_switch-1.0.0b12-py3-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for tls_switch-1.0.0b12-py3-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 2658ef8336be5aaad8cc884b1f18142db3f580edf9c6f0f34bbeedbeeecfe969
MD5 26b57298d897f08e5e975b310a82c394
BLAKE2b-256 87592bbfe43e38c55cb9ef1191eef8e4c7ddfb537a2bb6a7344c68c4cb91d810

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