# uTLS: a Go TLS fork that lets you rewrite the ClientHello

> uTLS keeps crypto/tls doing the handshake but hands you the ClientHello, so Go clients can stop advertising the same fingerprint everywhere. It is a niche tool with a narrow, well-defined job.

**refraction-networking/utls** —  Fork of the Go standard TLS library, providing low-level access to the ClientHello for mimicry purposes.

- Repository: https://github.com/refraction-networking/utls
- Stars: 2,588 · Forks: 377
- Language: Go
- License: BSD-3-Clause
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/refraction-networking-utls

## The Go ClientHello problem uTLS was written to fix

Every TLS client sends a ClientHello, and the contents of that message vary by implementation. Go's crypto/tls produces a distinctive one. The README states this directly: Golang's ClientHello has a very unique fingerprint, which especially sticks out on mobile clients, where Golang is not too popular yet. That matters when a network operator can block a specific fingerprint cheaply, because the collateral damage of blocking it is small.

uTLS is aimed at the anti-censorship community, which the README names as the group concerned about tools being blocked on ClientHello alone. The audience is narrow: Go developers building circumvention clients, measurement tools, or anything that must survive fingerprint-based filtering. It is not a general TLS replacement and it is not a proxy. The README is explicit that the handshake is still performed by crypto/tls, and that the library merely changes the ClientHello part of it and provides low-level access. If you want a working HTTPS client with better defaults, this is the wrong dependency.

## What uTLS actually controls: ClientHello, not the handshake

The architecture is a fork, not a wrapper. The repository carries the same file layout as crypto/tls, with handshake_client.go, handshake_client_tls13.go, handshake_server.go, key_schedule.go, ticket.go and the rest. On top of that sit u_alias.go, u_tls_extensions.go and u_clienthello_json.go, which are where the ClientHello manipulation lives. The README describes three capabilities: read and write access to all bits of the client hello message, read access to fields of ClientHandshakeState including ServerHello and MasterSecret, and read access to the keystream.

The way you select a fingerprint is a HelloID passed to UClient. The README shows tls.HelloRandomized for a randomized fingerprint, with helloRandomizedALPN and helloRandomizedNoALPN as variants that guarantee the presence or absence of the ALPN extension. There are also named parrots for specific browsers and platforms. That is the whole model: you hand the library a connection, a config, and a HelloID, and it builds the ClientHello accordingly while crypto/tls does the rest of the state machine.

One design point worth flagging. Randomized fingerprints use only ciphersuites and extensions that uTLS fully supports, which the README frames as a moving target without parrot-is-dead attack risks. The trade-off it admits is that a generated fingerprint has a small chance of not working, so the README suggests regenerating until one works and then reusing it, because constantly changing fingerprints is itself suspicious. utls.Roller automates that reuse.

## Installing uTLS and establishing a first connection

The module path is github.com/refraction-networking/utls, and the repository's go.mod declares go 1.26 while the README states a minimum Go version of 1.21. Fetch it the usual way:

```bash
go get github.com/refraction-networking/utls
```

A first connection follows the pattern in the README: wrap an existing net.Conn with tls.UClient, passing a config and a HelloID. The randomized hello is the simplest starting point.

```go
config := &tls.Config{ServerName: "example.com"}
uTlsConn := tls.UClient(tcpConn, config, tls.HelloRandomized)
```

If you want a specific browser's fingerprint instead, substitute a parrot HelloID for tls.HelloRandomized. The README's compatibility table lists Chrome 62, 70, 72 and 83, Firefox 56 and 65, and iOS 11.1 and 12.1, each with a TLS Fingerprint ID. Note the table's caveats before choosing: several parrots offer ciphersuites and signature algorithms that crypto/tls does not support, and the README warns this is only safe if you fully control the server and can turn those off server-side.

To avoid re-randomizing on every connection, reuse the HelloID from a connection that already worked, which the README demonstrates with oldConn.ClientHelloID. The README also points at a custom handshake path: pass tls.HelloCustom to UClient for an empty config, then fill UConn.Hello fields and add extensions yourself. The README adds that the documentation may lag behind the code and directs readers to godoc for the current API.

## Parrot compatibility is the real constraint, not the API

The most useful thing in the README is the compatibility table, because it tells you which parrots are risky rather than presenting them as interchangeable. Chrome 62 through 83 are marked as offering ciphersuites and signature algorithms that crypto/tls does not support, and as relying on unsupported extensions including ChannelID and, for the later versions, Encrypted Certs. Firefox 56 and 65 are marked very low risk on ciphersuites but Firefox 65 additionally relies on MaxRecordSize. iOS 11.1 and 12.1 are marked low risk, with the footnote that there is no risk if utls.EnableWeakCiphers() is called first.

The README's own framing of the risk is that these unsupported items can be echoed back by the server in the wild and visibly break the connection. That is a failure mode you will see as a handshake error, not a silent degradation, which at least makes it diagnosable. The second limitation is scope: there is no parroting beyond ClientHello. Once the handshake proceeds, whatever the rest of your stack does is still your problem. A client that parrots Chrome's ClientHello but then negotiates HTTP/2 differently, or sends a different header order, is only partly disguised.

The README is also honest that side channels exist, citing the parrot-is-dead line of work and asking anyone who finds a practical one to report it. Its counter-argument is that TLS is highly standardized, so there are not many subtle things that can differ between implementations. That is a reasonable position, but it is an argument about degree, not a guarantee.

## When uTLS is the wrong tool

If your goal is to look like a browser end to end, uTLS covers a minority of the surface. It changes the ClientHello and nothing after it. A fingerprinting system that also inspects HTTP header order, HPACK behavior, or TLS record timing will not be satisfied by a parrot HelloID alone.

If you are building a general-purpose HTTP client and simply want sane TLS defaults, crypto/tls is the better dependency. uTLS is a fork, which means you inherit its divergence from upstream, its own release cadence, and a retract list in go.mod covering v1.4.0 and v1.4.1 for a panic on saveSessionTicket. That retract block is a useful reminder that this codebase has had to withdraw versions.

Finally, if you need the server side to be flexible, the parrots are a poor fit. The README's advice for unsupported ciphersuites and extensions assumes you control the server and can disable them. Against third-party servers you do not control, you are relying on the compatibility table's risk estimates, which the README itself labels as a very rough guesstimate.

## Alternatives and how they differ in approach

The closest alternative is the Go standard library's crypto/tls, which uTLS forks. The difference is not quality but intent: crypto/tls hides the ClientHello and gives you a supported, security-maintained implementation with no fingerprint control. uTLS trades that safety margin for the ability to write arbitrary ClientHello bytes. Choosing uTLS means accepting that you are now responsible for the correctness of a handshake message that the standard library deliberately kept out of reach.

A second approach is to not use Go for the client at all. The README's own framing of the problem is that Go's fingerprint sticks out, and one response to that is to drive a real browser or a non-Go TLS stack instead of imitating one. That sidesteps the parrot-is-imperfect problem entirely, at the cost of a much heavier runtime. uTLS exists precisely because that cost is often unacceptable.

Within uTLS itself, the meaningful choice is between parrots and randomized fingerprints. Parrots aim at a specific, stable target and inherit that target's compatibility risks. Randomized fingerprints, per the README, only use ciphersuites and extensions uTLS supports, which removes the compatibility risk but gives up the familiarity of a known browser. The README recommends using multiple fingerprints including randomized ones rather than relying on a single one.

## Maintenance, licensing and upgrade cost

The last push to the repository was on 2026-09-24, four days before this article's reference point, and the repository is not archived. The most recent tagged release is v1.8.2, described as a security update, from 2026-01-13. Before that came v1.8.1, a bug fix release, on 2025-10-14, and v1.8.0 on 2025-07-22. The pattern is a roughly quarterly tagging cadence with security and bug fix releases in between.

The upgrade cost is dominated by the fork relationship. Because uTLS tracks crypto/tls, upstream changes to the TLS stack have to be carried across, and the go.mod retract block shows that version withdrawal is a real possibility. The README also warns that its own documentation may not keep up with the code and recommends godoc as the current reference, so an upgrade should be checked against godoc rather than the README alone. The go.mod requires golang.org/x/crypto v0.36.0, golang.org/x/net v0.38.0 and golang.org/x/sys v0.31.0, plus brotli and klauspost/compress, so dependency bumps in those modules reach you through this path.

On licensing: the repository is BSD-3-Clause. That is a permissive licence that permits redistribution and modification with the copyright notice and disclaimer retained, and it does not carry the patent grant that some other permissive licences include. Because uTLS is a fork of Go's crypto/tls, which is also BSD-3-Clause, the obligations are compatible. This is a description of the licence text, not legal advice; if you are redistributing uTLS inside a product, have counsel review the notice requirements.

## Conclusion

Adopt uTLS if you are writing a Go client that must not look like Go, and you can accept that only the ClientHello is under your control. Do not adopt it if you need a full browser emulation, a proxy, or a general HTTP client: uTLS is a TLS library, and the README says the handshake itself is still performed by crypto/tls. Before committing, verify two things in your own build: that your chosen parrot still completes against your target servers, since the README warns that unsupported ciphersuites and extensions can be echoed back and visibly break the connection, and that you are on Go 1.21 or newer, which the README lists as the minimum. Then check the parrot you picked against the compatibility table rather than assuming all of them are equally safe.

## FAQ

### What is uTLS?

uTLS is a fork of Go's crypto/tls that provides low-level access to the ClientHello for fingerprinting resistance, along with read access to handshake state and the keystream. The README states that the handshake is still performed by crypto/tls and the library only changes the ClientHello part.

### How do I install refraction-networking/utls in a Go project?

Add the module with go get github.com/refraction-networking/utls. The README lists Go 1.21 as the minimum version, while the repository's go.mod declares go 1.26.

### How do I use a randomized fingerprint in uTLS?

Pass tls.HelloRandomized as the HelloID to tls.UClient, or use helloRandomizedALPN or helloRandomizedNoALPN to control whether the ALPN extension is present. The README notes a small chance a generated fingerprint will not work, and recommends reusing a working one, which utls.Roller does automatically.

### Does uTLS make my Go client look exactly like Chrome?

The README does not claim exactness. It states that parroting could be imperfect and that there is no parroting beyond ClientHello, and its compatibility table flags several Chrome parrots as relying on ciphersuites, signature algorithms and extensions that crypto/tls does not support.

### What are the risks of using a parrot fingerprint in uTLS?

The README warns that unsupported ciphersuites and extensions can be echoed back by the server and visibly break the connection, which is why it recommends disabling them server-side when you control the server. The compatibility table rates each parrot's ciphersuite and signature risk separately.

## Sources

- [Issues](https://github.com/refraction-networking/utls/issues)
- [License: BSD-3-Clause](https://github.com/refraction-networking/utls/blob/master/LICENSE)
- [README](https://github.com/refraction-networking/utls/blob/master/README.md)
- [refraction-networking/utls on GitHub](https://github.com/refraction-networking/utls)
- [Releases](https://github.com/refraction-networking/utls/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/refraction-networking-utls
