Noble TLS

February 22, 2026 ยท View on GitHub

Python 3.10

Noble TLS is an advanced HTTP library based on requests and tls-client. Fully async, with auto-updating JA3 fingerprints.

Installation

pip install noble-tls

Features

  • Auto-update TLS client libs from bogdanfinn/tls-client
  • Async support
  • Proxy support (HTTP, HTTPS, SOCKS4, SOCKS5)
  • Custom JA3 string
  • Custom H2 and H3 settings
  • Custom supported signature algorithms
  • Custom supported versions
  • Custom key share curves (including post-quantum)
  • Custom cert compression algorithm
  • Custom pseudo header order
  • Custom connection flow
  • Custom header order
  • 76 preset browser profiles (Chrome, Firefox, Opera, Safari, iOS, iPadOS, Android)
  • Random TLS extension order
  • HTTP/3 (QUIC) with protocol racing
  • Certificate pinning (HPKP)
  • ECH (Encrypted Client Hello) and ALPS
  • Session lifecycle management (close, get/add cookies from Go layer)
  • Response streaming to file
  • Protocol detection (response.used_protocol)
  • Millisecond-precision timeouts
  • IPv4/IPv6 control
  • SNI and Host header overrides
  • Rotating proxy support
  • requests-style history and allow_redirects

What's new

Session lifecycle

You can now explicitly destroy sessions and manage cookies at the Go layer:

session = noble_tls.Session(client=Client.CHROME_133)

# Do your work...
res = await session.get("https://example.com")

# Manage cookies in the Go session directly
cookies = await session.get_cookies("https://example.com")
await session.add_cookies("https://example.com", [
    {"name": "sid", "value": "abc", "domain": "example.com", "path": "/"},
])

# Free Go memory and connections when done
await session.close()

# Or nuke everything at once
await Session.close_all()

Protocol detection

Every response now tells you which protocol was actually negotiated. Using a Protocol enum:

from noble_tls import Protocol

res = await session.get("https://example.com")
print(res.used_protocol)  # Protocol.HTTP_2

if res.used_protocol == Protocol.HTTP_3:
    print("Running over QUIC")

HTTP/3 and protocol racing

HTTP/3 (QUIC) support, with the ability to race HTTP/2 against HTTP/3 the way Chrome does (300ms head start for H2, H3 can still win):

session = noble_tls.Session(
    ja3_string="...",
    protocol_racing=True,
    h3_settings={"QPACK_MAX_TABLE_CAPACITY": 4096},
    h3_settings_order=["QPACK_MAX_TABLE_CAPACITY"],
    h3_pseudo_header_order=[":method", ":authority", ":scheme", ":path"],
    h3_send_grease_frames=True,
)

You can also just disable H3 if it causes problems: disable_http3=True.

Network and security options

Per-session network and security settings:

session = noble_tls.Session(
    client=Client.CHROME_133_PSK,
    disable_ipv6=True,                # IPv4 only
    local_address="0.0.0.0:0",        # Bind to specific interface (host:port)
    server_name_overwrite="sni.com",   # Override TLS SNI
    is_rotating_proxy=True,            # Force new connection per request
    without_cookie_jar=True,           # Disable cookie jar entirely
    certificate_pinning={              # HPKP - reject if pin doesn't match
        "example.com": ["sha256_pin_base64"],
    },
)

# Per-request: millisecond timeout, host override, stream to file
res = await session.get(
    "https://example.com",
    timeout_milliseconds=2500,
    request_host_override="other.host.com",
    stream_output_path="/tmp/response.json",
    stream_output_block_size=4096,
)

ECH, ALPS, and post-quantum curves

Encrypted Client Hello, Application Layer Protocol Settings, and post-quantum key share curves for custom TLS configurations:

session = noble_tls.Session(
    ja3_string="...",
    alps_protocols=["h2"],
    ech_candidate_payloads=[256],
    ech_candidate_cipher_suites=[
        {"kdfId": "HKDF_SHA256", "aeadId": "AEAD_AES_128_GCM"},
    ],
    key_share_curves=["GREASE", "X25519MLKEM768", "X25519"],
    supported_signature_algorithms=[
        "ECDSAWithP256AndSHA256",
        "PSSWithSHA256",
        "Ed25519",
    ],
    record_size_limit=16384,
    allow_http=True,
    stream_id=1,
)

Updated browser profiles

76 profiles now, synced with the latest Go source. New additions:

BrowserNew profiles
Chrome130 PSK, 144, 144 PSK, 146 PSK
Firefox123, 133, 146 PSK, 147 PSK
SafariiOS 18.5, iOS 26.0

Removed CHROME_141 and CHROME_142

Default headers (multi-value)

Separate from the regular headers dict, default_headers uses a multi-value format and acts as a fallback when no headers are specified on a request:

session = noble_tls.Session(
    client=Client.CHROME_133,
    default_headers={"Accept": ["text/html", "application/json"]},
)

:shield: Need antibot bypass?

TLS fingerprinting alone won't get past modern bot protection. Hyper Solutions provides API endpoints that generate valid antibot tokens for:

Akamai | DataDome | Kasada | Incapsula

No browser automation. Simple API calls that return the cookies and headers these systems expect.

Get your API key | Docs | Discord

Examples

The syntax follows requests closely. Most things work the same way.

Example 1 -- Preset browser profile:

Available client identifiers (76 profiles)
ChromeSafariFirefoxOpera
CHROME_103SAFARI_15_6_1FIREFOX_102OPERA_89
CHROME_104SAFARI_16_0FIREFOX_104OPERA_90
CHROME_105SAFARI_IPAD_15_6FIREFOX_105OPERA_91
CHROME_106SAFARI_IOS_15_5FIREFOX_106
CHROME_107SAFARI_IOS_15_6FIREFOX_108
CHROME_108SAFARI_IOS_16_0FIREFOX_110
CHROME_109SAFARI_IOS_17_0FIREFOX_117
CHROME_110SAFARI_IOS_18_0FIREFOX_120
CHROME_111SAFARI_IOS_18_5FIREFOX_123
CHROME_112SAFARI_IOS_26_0FIREFOX_132
CHROME_116_PSKFIREFOX_133
CHROME_116_PSK_PQFIREFOX_135
CHROME_117FIREFOX_146_PSK
CHROME_120FIREFOX_147
CHROME_124FIREFOX_147_PSK
CHROME_130_PSK
CHROME_131
CHROME_131_PSK
CHROME_133
CHROME_133_PSK
CHROME_144
CHROME_144_PSK
CHROME_146
CHROME_146_PSK
Mobile / App
ZALANDO_ANDROID_MOBILE, ZALANDO_IOS_MOBILE
NIKE_IOS_MOBILE, NIKE_ANDROID_MOBILE
CLOUDSCRAPER
MMS_IOS, MMS_IOS_1, MMS_IOS_2, MMS_IOS_3
MESH_IOS, MESH_IOS_1, MESH_IOS_2
MESH_ANDROID, MESH_ANDROID_1, MESH_ANDROID_2
CONFIRMED_IOS, CONFIRMED_ANDROID
OKHTTP4_ANDROID_7 through OKHTTP4_ANDROID_13
import asyncio
import noble_tls
from noble_tls import Client

async def main():
    await noble_tls.update_if_necessary()
    session = noble_tls.Session(
        client=Client.CHROME_133,
        random_tls_extension_order=True
    )
    res = await session.get(
        "https://www.example.com/",
        headers={"key1": "value1"},
        proxy="http://user:password@host:port"
    )
    print(res.status_code)
    print(res.used_protocol)
    print(res.text)

    await session.close()

asyncio.run(main())

Example 2 -- Custom JA3 fingerprint:

import asyncio
import noble_tls

async def main():
    await noble_tls.update_if_necessary()

    session = noble_tls.Session(
        ja3_string="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
        h2_settings={
            "HEADER_TABLE_SIZE": 65536,
            "MAX_CONCURRENT_STREAMS": 1000,
            "INITIAL_WINDOW_SIZE": 6291456,
            "MAX_HEADER_LIST_SIZE": 262144
        },
        h2_settings_order=[
            "HEADER_TABLE_SIZE",
            "MAX_CONCURRENT_STREAMS",
            "INITIAL_WINDOW_SIZE",
            "MAX_HEADER_LIST_SIZE"
        ],
        supported_signature_algorithms=[
            "ECDSAWithP256AndSHA256",
            "PSSWithSHA256",
            "PKCS1WithSHA256",
            "ECDSAWithP384AndSHA384",
            "PSSWithSHA384",
            "PKCS1WithSHA384",
            "PSSWithSHA512",
            "PKCS1WithSHA512",
        ],
        supported_versions=["GREASE", "1.3", "1.2"],
        key_share_curves=["GREASE", "X25519"],
        cert_compression_algo="brotli",
        pseudo_header_order=[":method", ":authority", ":scheme", ":path"],
        connection_flow=15663105,
        header_order=["accept", "user-agent", "accept-encoding", "accept-language"]
    )

    res = await session.post(
        "https://www.example.com/",
        headers={"key1": "value1"},
        proxy="http://user:password@host:port"
    )
    print(res.text)

    await session.close()

asyncio.run(main())

More examples in the examples/ folder.

Pyinstaller / Pyarmor

If you want to pack the library with Pyinstaller or Pyarmor, add this to your command:

Linux - Ubuntu / x86:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'

Linux Alpine / AMD64:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'

MacOS M1 and older:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'

MacOS M2:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'

Windows:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-64.dll;tls_client/dependencies'

One final note

Package is named after Admiral Atticus Noble in Rebel Moon: Part One - A Child of Fire

Acknowledgements

Big shout out to Bogdanfinn for open sourcing his tls-client in Go, and FlorianREGAZ for the original Python wrapper.