What this keeps,
and what it does.
Counts, and nothing that points back to anybody. Not the hostname you asked about, not your address, not the time. Verdicts are counted — how many scans came back strong — and nothing ties one to who asked or what they asked about. If you found this page because a scan from here reached your server, the section on what a scan does is the one you want.
What is kept
Counts of scans, and counts of refusals. Every figure below is served in
full at /api/v1/stats, so the list can be checked against
the thing itself rather than believed, and they are written to a small
file so a restart does not reset them.
Per scan, one number goes up, and one of four more: strong, weak, insecure, or ungraded — which means nothing was measured rather than nothing was wrong. A separate figure holds the day’s total, with the date it belongs to so that a restart can tell whether it is still today, and one date says when counting began. That is everything this service knows about time.
Each check keeps its own block of those figures, naming the rules that graded them. There are two checks now — the TLS handshake, and how a site is reached over HTTP — and they answer different questions over different evidence, so one number adding them together would describe nothing anybody could check.
Per refusal, how many requests were turned away and for which reason, from a fixed list. The reasons name causes and never requesters: an address asking too often, a name this service will not scan, a target that would not parse. Nothing about who asked or what they asked about is in any of them. They exist because an operator who cannot see a change in the shape of what arrives is asking you to trust somebody who is not watching, and that would be its own kind of carelessness.
That is the whole record.
Two scans a second apart and two a month apart leave the same trace.
Nothing distinguishes one from another, and nothing connects any of them
to a person. The figures are published at /api/v1/stats,
because a number nobody can trace back demonstrates the claim better
than the claim does.
The figures served there stand still for a minute at a time. A counter holds no clock, but a counter anyone can read repeatedly becomes one — checking it every second would reveal the moment each scan happened, which somebody could line up against their own records. Freezing it removes that.
Your address
Held in memory to enforce a rate limit, and dropped about three minutes after your last request. Never written to disk, never in a response. For IPv6 the limit applies to the network block rather than the exact address, because one subscriber is normally given a whole block.
The hostname you ask about
Sent in the request body, never in the address bar. A hostname in a URL is written to your browser history, to the referrer of anything the page loads next, and to the log of every proxy between you and here. Promising not to record it while putting it in a URL would hand the record to everyone else instead.
A mail address may be typed where a name is asked for. Everything before
the last @ is dropped by this page before anything is sent,
and dropped again by the service if it arrives: the part before it is a
person, and every question a check asks is about the domain.
There is a limit on how often any one server is checked, which has to recognise a repeat without keeping the name. The hostname is hashed with a key created when the service starts and never written down, then cut to sixteen bits: any one result fits many thousands of the names anybody would think to try, and only the last half minute of them exists at all. Whoever takes this machine finds a handful of numbers and no way to enumerate a name from one.
Being exact about what that protects: it defends against reading a name out of a number. It was never a defence against somebody testing one name they already suspect, and this page would rather say so than let the shorter claim stand.
There is a second edge to the same limit, and it is the one worth explaining. A limit answers a question, and an answer is something anybody can ask for — check a host, be refused, and you have learnt that somebody else checked it. The way that is closed here is not a trick but a threshold: the limit only speaks after a host has been checked eight times in the same short window, and this service handles single figures of checks a day. Ordinary use never reaches it, so the answer to anybody probing is "go ahead", which tells them nothing. To make the limit speak they would have to push it there themselves, from several addresses, which is the very thing they were trying to detect. The measurement destroys what it would measure.
What is left, stated rather than left to be found: if one host really is being checked eight times inside that window, somebody probing learns that it is busy. That is a fact about a host under load, not about a person, and it is a fact its own administrator can already read in their logs — which is why this service publishes a reverse name pointing back here. The threshold itself is also varied per host, from a key that exists only in memory and is thrown away when the service restarts, so even that edge is not a fixed line to aim at.
And the crack that remains, because a page like this is worth nothing if it only lists the parts that came out well. Varying the threshold hides how many times a host was checked; it cannot hide the refusal itself. Anybody who does get refused has learnt one thing for certain: somebody has checked that host at least eight times in the last few minutes. Closing even that would mean raising the threshold again, and the threshold is not free — every point of it is peak load the scanned server absorbs. Spending somebody else’s bandwidth to buy a stranger’s privacy is a trade we are not willing to make quietly, so it is written here instead.
Nothing from anyone else
No analytics, no fonts from elsewhere, no content delivery network, no tag of any kind. A page loads one stylesheet and at most two small scripts, all from this server. Nothing here can report your visit to a third party, which you can confirm in your browser's network panel.
No cookies. The only thing stored in your browser is whether you chose the light or the dark colour scheme, if you did, and it never leaves it.
Logs
There is no record of what was scanned, by whom, or when. This is enforced by there being no code that could write one, and a test fails if any appears.
Being exact about this matters more than sounding absolute. The service prints three lines in its lifetime: that it started, that it stopped, and a failure to save the counter. None describes a request, and the system collects them as it collects anything else a program prints. Below that, the operating system keeps its own records — the firewall counts packets, administrative access is logged, and the kernel notes packets arriving with a forged source address. That last one holds addresses, and it is kept, because a service that could not see an attack on itself would be asking you to trust an operator who is not watching.
None of it can say which host you asked about. That is only ever known inside a process that does not write it down.
Where this runs
On a server rented from Hetzner Online GmbH, in Germany. As with any hosted service, the network provider carries the traffic and can see that connections are made, and the machine runs on hardware they own. That is true of every scanner anywhere and is not something this project can remove. Naming them rather than saying "a rented server" costs nothing: anybody could read it from the address this answers on. What this project can do is add nothing to it: whoever took this machine tomorrow would find a program, a certificate, and a file containing a number.
What a scan does
To find out how a server negotiates TLS, this opens connections and completes handshakes — the same exchange every browser performs before loading a page. A modern server takes fewer than twenty over a few seconds; one that still accepts every obsolete protocol version and cipher suite takes rather more, because each one has to be offered before it can be reported. One per protocol version, one per suite the server turns out to accept, two more to see whether it imposes its own ordering, and one to ask whether it will negotiate a key exchange that resists a future quantum computer — that last one only where the question exists, which is a server speaking TLS 1.3. That last one adds exactly one connection and is not asked of a server that could not answer it. Each connection is closed as soon as its handshake finishes.
A report can be saved from the page it is shown on, and saving it is between you and your own disk: the bytes are already in your browser, nothing is asked of this service a second time, and nothing about it is recorded here. It is offered as JSON and in no other format, because every name in a report is chosen by the server that was scanned — a spreadsheet would read one beginning with an equals sign as a formula, and a terminal would read an escape sequence in one as an instruction.
Nothing happens beyond the handshake. Not a single byte of application data is sent. No page is requested, no path is tried, no header is sent.
- No authentication is attempted; there are no credentials here to try.
- No vulnerability is tested for and no exploit is sent — only a standard client hello.
- No administrative path is probed, because no request of any kind is made.
- No malformed packets are constructed.
- Only ports that speak TLS from the first byte: 443, 8443, 465, 636, 990, 993, 995 and 5061. This cannot be used as a port scanner.
- No private or internal addresses. Every connection resolves the name, checks the address it was given, and then connects to that address rather than to the name — so a name that answers differently a moment later cannot redirect a connection that was already checked. A scan opens many connections, so the resolver is asked many times rather than once.
One thing does happen away from the handshake. The same resolver that turns the hostname into an address is also asked which certificate authorities that name allows to issue for it — between one and four further questions, about a name it has already been asked about, because the answer is inherited from parent domains and the search stops at the first one that carries it. The scanned host is not involved and learns nothing from them.
Everything a report contains is what any client receives on connecting. None of it required asking for anything.
Nothing is asked of third parties either
No certificate authority is asked anything. Enquiring whether a certificate is still valid would tell that authority somebody is looking at it, and transparency logs are not queried for the same reason. A certificate is described from what the server sent and from nothing else, and every report says so.
That does not mean revocation goes unexamined. What a server sends can include a status response the authority signed earlier and handed to it — the point of stapling is that the authority learns nothing about who is visiting — and that response is read: it has to describe this certificate, be current, and carry the authority's signature before a report says anything it claims. Reading bytes already in hand asks nobody anything, which is why it belongs on this page rather than against it.
A copy you run yourself does one thing this deployment does not. Most certificates issued now name no responder at all — the CA/Browser Forum made that machinery optional and revocation lists mandatory — so there is nothing for a server to staple and nothing here to read, and revocation goes unestablished for most of the web. A self-hosted copy downloads the list instead. Doing so tells the authority that somebody fetched a list; it does not say which certificate was being looked at, because one list covers thousands. That is a different question from the one this section refuses, and it is a question about a certificate the operator usually owns.
Neither is done here, and the calls that would do them are compiled out of the build this deployment runs. A self-hosted copy can also ask a public monitor which certificates exist for a name, which finds one somebody obtained without the owner knowing — the earliest warning there is, and a question that does name the domain, unlike a revocation list. The sentences above are kept true by there being nothing to switch on rather than by a setting left off, which is the same reason the list of hosts this deployment will connect to is compiled in.
Why more than one connection
A server does not announce what it supports; it answers what it is offered. Finding out whether an obsolete protocol version is still accepted means offering it and seeing what comes back. That is why the answer can be trusted, and also why the check is not a single connection.
Load on the server being checked
Each host has its own budget, independent of who asks. Once a host has been checked several times in a short window — the exact number is varied per host and is described under what is kept — further requests for it are refused for a while, even from different people. That limit exists so this cannot be aimed at one server in bulk, by accident or otherwise.
Stopping a scan
Write to abuse@denyfirst.dev
from an address at the domain, or from one listed in its WHOIS or
security.txt, and it will be excluded. No explanation
needed. A reply within two working days, and the exclusion applied
before the reply.
The same address reaches a person for anything else: an unexpected pattern, a question about what was sent, or a request for exact timings to match against your own records.
Connections come from addresses that resolve to
scanner.denyfirst.dev in reverse DNS. A scanner that hides
is a scanner nobody can ask about.
What is not promised
A report describes what a server negotiated at one moment, graded against a published rule set. It is not an audit, not a certification, and not a statement that anything is safe. Every report names the limits of what it covered — read that part, because a short list of findings can mean a well-configured server or a scan that could not see very far.
This is offered as it is, without warranty of any kind. It may be wrong. It may be unavailable. It may stop existing. What you do with a result is yours to decide.
That is not a formality. Claiming otherwise would be the same kind of overstatement this tool exists to identify.
None of this needs to be taken on trust
The source is public, every guarantee is listed with the test that protects it, and the whole thing runs on a machine you control if you would rather not rely on any of the above.
The source, the guarantees and their tests, and how to report a vulnerability