RealiTLScanner reads TLS handshakes to find REALITY-capable hosts
A TLS server scanner for Reality
At a glance
- What is it?
- A small Go program that opens TLS connections, records the certificate domain and country, and writes a CSV census. Useful for building your own target list, and silent on whether a host will actually serve you.
- Who is it for?
- Adopt RealiTLScanner if you are assembling your own target list for a REALITY client and you can run the scan from your own machine rather than from a VPS that might get flagged. Skip it if you need per-host verification or a written verdict about each address, because the tool records that a handshake completed and says nothing about whether the peer behind it is a REALITY endpoint.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 169 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- XTLS/RealiTLScanner4,652 ★ · +246
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| XTLS/RealiTLScanner | 4.4k | 4.7k |
246 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The census it actually produces is a certificate list
RealiTLScanner answers one narrow question: given a set of hosts, which of them complete a TLS handshake, and what name does the certificate carry? The sample output file makes the shape obvious. Every row is an IP, an origin field, a certificate domain, a certificate issuer and a country code, and the sample rows are overwhelmingly Linux distribution mirrors: ntc.net.np, mirror.marwan.ma, mirror.i3d.net, nl.mirrors.clouvider.net, mirror.2degrees.nz, ubuntu.hi.no, ftp.rnl.tecnico.ulisboa.pt. That pattern is not an accident of the sample. Mirrors are publicly listed, so a crawler pointed at one archive page, such as the Launchpad Ubuntu mirror list in the README's -url example, picks up many targets.
For an operator running a REALITY client, that list has a specific use. REALITY relays a real site's certificate to the visitor, so a candidate host is only worth trying if its handshake looks ordinary. RealiTLScanner is the filter in front of that trial: it tells you which addresses answer at all, and under which name. The repository description is the shortest version of the same claim, a TLS server scanner for Reality. The rest of the tool follows from that.
A target counts as feasible when the handshake connects
The console line carries the whole decision. The sample run logs one entry per accepted target with the host, the negotiated TLS version, the ALPN value, the certificate domain and the issuer:
2024/02/08 20:51:10 INFO Connected to target feasible=true host=107.172.103.9 tls=1.3 alpn=h2 domain=rocky-linux.tk issuer="Let's Encrypt"The unit of output is the completed handshake, not a verdict. The tls and alpn fields tell you what the server negotiated, which is the part that matters when you want a client whose traffic looks like ordinary browser use. The issuer comes along for free, and the sample rows are Let's Encrypt, GlobalSign nv-sa and Sectigo Limited, all three of them what a relayed handshake to a live site would show.
What the feasible flag does not say is whether the peer implements REALITY. Every field around it is transport-level. A mirror serving its own genuine certificate scores identically to a REALITY endpoint in this output, because from the client side of a single handshake the two are the same event.
Four input paths, and the one that needs care
Targets arrive through three flags, and each one changes how much work the scan does. A single address, a CIDR block or a domain name go through the -addr flag. A newline-delimited file is what the -in flag takes, one target per line. The -url flag crawls a page and scans the domains it finds, as in the README's example with the Launchpad Ubuntu archive mirror list.
# Scan a specific IP, IP CIDR or domain:
./RealiTLScanner -addr 1.2.3.4
# Note: infinity mode will be enabled automatically if `addr` is an IP or domain
# Scan a list of targets from a file (targets should be divided by line break):
./RealiTLScanner -in in.txtThat infinity mode note deserves a second reading. A bare IP or domain sets the scan running without a bound, while a CIDR is finite and stops when the block is spent. Hand the tool a /16 and it will keep going on its own. The file input is the one to reach for when you want the blast radius decided before the scan starts.
Port 443 is the default and -v is how you see the misses
Everything runs against port 443 unless you say otherwise, which matters when the candidates are not web servers:
# Specify a port to scan, default: 443
./RealiTLScanner -addr 1.1.1.1 -port 443
# Show verbose output, including failed scans and infeasible targets:
./RealiTLScanner -addr 1.2.3.0/24 -vA default run prints successes only, so a target that refuses the connection or never finishes a handshake leaves no trace. That silence is a measurement problem, because an infeasible host and a host you never reached look identical in the output. The verbose flag prints failed scans and infeasible targets, and it is what to add when a CIDR returns fewer rows than the block size suggested.
The same page also warns that the scanner is meant to run locally, since running it from a VPS may get that VPS flagged. Treat it as a statement about where the binary lives, not as a description of how it avoids notice.
go.mod asks for Go 1.26 while the README still says 1.21+
The build step is a single command, and the requirement line above it is wrong in a way that costs an afternoon if you skip it:
go buildThe README states a requirement of Go 1.21+. The module file states something else:
module github.com/xtls/RealiTLScanner
go 1.26A toolchain older than 1.26 refuses the module no matter what the README says. The Dockerfile sides with go.mod rather than the README, since it builds on golang:1.26-alpine. Two of the three sources in the repository point at 1.26, so that is the floor to work from.
One dependency matters for the output rather than the build. github.com/oschwald/geoip2-golang v1.13.0 is the only direct requirement, and it is what fills the GEO_CODE column. With no database file present it has nothing to look up.
The container path skips the Go toolchain but not the certificate store
The Dockerfile is a two-stage build, and the second stage is the part worth reading. The final image is alpine:latest with one package installed, ca-certificates, and the compiled binary copied to /app under an entrypoint of ./RealiTLScanner.
docker build -t realitlscanner .
# show help
docker run --rm realitlscanner
# scan
docker run --rm realitlscanner -addr 1.1.1.1Flags go after the image name rather than before it, because the entrypoint is already the scanner. Running the image with no arguments prints the same help as the bare binary, which is the quickest confirmation that the build worked. Geo IP stays a host-folder problem in this layout: the database sits next to the binary, so it needs a bind mount into the container rather than a copy baked into the image.
Country.mmdb is the only thing you supply by hand
Geo IP starts out off. Turning it on means placing a MaxMind GeoLite2 or GeoIP2 Country Database in the executing folder under the exact name Country.mmdb, and the project links a public download for that file. Nothing else in the documentation is configurable from outside the binary.
The sample CSV shows what the field is actually good for. The NP, MA, JP, NL, NZ and NO codes all belong to mirror rows, so the column is geographic sorting for a host list rather than a statement about a server's identity. Two sample rows also share the IP 202.36.220.86 and differ only in the origin field, one naming ubuntu.mirrors.theom.nz and the other mirror.2degrees.nz, with the same certificate domain and the same country code. One address serving several names produces several lines, which means row counts are not host counts. Size a target list from that file alone and you will overcount it.
What the scanner leaves to the client
The gap is structural. RealiTLScanner opens a connection, records what came back in the handshake and writes it out. It never attempts a REALITY handshake of its own, never compares the returned certificate against the site a target is meant to impersonate, and reports nothing that separates a real mirror from a proxy endpoint. The feasible flag is a reachability result.
A different approach exists in the same space. Shodan indexes the TLS service on hosts it has already seen and returns certificate and protocol details per host, so a lookup answers questions about one address without you driving the probe. The direction of the work is the real difference: Shodan reports what its own crawling found, while RealiTLScanner makes the connection on your schedule from your machine, which is what you need when the candidate list is new and no index holds it yet. Neither fingerprints a certificate to confirm REALITY, so the per-host verdict stays with the client.
The repository carries an MPL-2.0 licence and its last push was on 2026-05-30. The documentation is silent on rollback, on retries against a flaky target and on what happens when one DNS name resolves to several addresses.
Editorial conclusion
Adopt RealiTLScanner if you are assembling your own target list for a REALITY client and you can run the scan from your own machine rather than from a VPS that might get flagged. Skip it if you need per-host verification or a written verdict about each address, because the tool records that a handshake completed and says nothing about whether the peer behind it is a REALITY endpoint. Before anything else, check your Go toolchain, since go.mod declares 1.26 while the README still advertises Go 1.21+.
Frequently asked questions
What language and version do I need to build RealiTLScanner?
go.mod declares a Go 1.26 module, and the Dockerfile builds on golang:1.26-alpine. The README still states a requirement of Go 1.21+, which is lower than what the module file asks for.
How do I run RealiTLScanner in Docker?
Build the image with docker build -t realitlscanner . and run it with flags after the image name, for example docker run --rm realitlscanner -addr 1.1.1.1, because the image entrypoint is already the scanner.
How do I get country codes in the RealiTLScanner output?
Place a MaxMind GeoLite2 or GeoIP2 Country Database in the executing folder with the exact name Country.mmdb. The lookup is done through the geoip2-golang v1.13.0 dependency.
Can I feed RealiTLScanner a whole IP range?
The -addr flag accepts an IP, an IP CIDR or a domain, and a CIDR is finite so it needs no separate limit. Passing a bare IP or domain switches on infinity mode automatically, which makes a file of targets through -in the more controlled input.
Does RealiTLScanner verify that a host is a REALITY endpoint?
No. Its output records that a TLS handshake completed, along with the negotiated version, the ALPN value, the certificate domain and the issuer, and the feasible flag reflects reachability rather than REALITY support.
Which port does RealiTLScanner scan?
Port 443 by default, overridable with the -port flag. The sample console output and the CSV census are both built around standard web TLS.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/xtls-realitlscanner)Star history chart for your README
[](https://hysenlabs.com/projects/xtls-realitlscanner)Was this useful?