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

Uploaded Python 3Windows ARM64

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

Uploaded Python 3Windows x86-64

tls_switch-1.0.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.0.0b1-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.0b1-py3-none-manylinux_2_17_aarch64.whl (2.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

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

Uploaded Python 3macOS 11.0+ ARM64

tls_switch-1.0.0b1-py3-none-macosx_10_12_x86_64.whl (2.3 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: tls_switch-1.0.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.0.0b1-py3-none-win_arm64.whl
Algorithm Hash digest
SHA256 2f41fb99371ec2149ae26de05af8b7d5d55a646a5f8832bdcc815650d92568aa
MD5 0b75e94a1edd42c0b160f964b624840b
BLAKE2b-256 012e622c64657be5c680b705ea118ce69cd1e2d07babdb4be7439df795d9a840

See more details on using hashes here.

File details

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

File metadata

  • Download URL: tls_switch-1.0.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.0.0b1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 c9629bcdd3335a945e0731b67ec9178d2df918525aab16fca0116c59bea932ee
MD5 3c0e799e97f85ed0f12a0174271a835a
BLAKE2b-256 6ed22a21d801bdd165b5d6efbcdd66b285b4567951b01c49dfd8fd6b94974353

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b1-py3-none-manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 b210e56249ab47ddd338c49606b8ae2deabca671ece708d72b878a96dab77e62
MD5 332256dfe3a09ea76453a0045176264b
BLAKE2b-256 6be65afbbadb79d39b7a40a3a5af6f92b91cec34b9dba12900917dba7b7323aa

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 75f30de3478694ac060bc289810414273ebdc2766c6d9d6a55fd1f538d94c21e
MD5 2ea71cd3039e6c2e95f917b7cf384a66
BLAKE2b-256 b46ad3740bb584b50713e72ecd197cbcdac6f7537514b9f552890409f6fe7730

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b1-py3-none-manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 304c6f6e5505385b90dc3384bb8f16cd48a78c73433f7bfed6c05cc2c25091ce
MD5 58357893b74861d494b9c6fff28a6c26
BLAKE2b-256 9e0aab9b064839633ce49acc960fb5a591cab8bb5570162000d2a9ddd9485744

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1959a85720bac2b1b933f0af7ec6192b576aa0bf3dda1747ea800fac3502f854
MD5 93295e54b4099fa9797d248f7e79766b
BLAKE2b-256 8dfc89798389210ef9f546935047a78f69ab3c344ae6887456257a5813bd7b06

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for tls_switch-1.0.0b1-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 bd7466e278cf95ce2ab267b8023b51b98f760638096975b937a34772663f91a1
MD5 7a6a43fa02bb51bc57df3aa061458768
BLAKE2b-256 20a73497f33144f9c8e23b1478e3f00455a02078b2eb3396a9755681a8f28bc4

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