How to find every public subdomain of your domain, and what to do about forgotten ones

By José · Published

To find all subdomains of your domain, start with certificate transparency. Every publicly trusted TLS certificate is written to public logs, and you can search them at crt.sh or through the Cert Spotter API. Add a passive subdomain finder such as subfinder, then compare what you found against a DNS zone export from your provider. The export is the authoritative list of records. The public sources show what the rest of the internet can already see.

Then go through what you found. Look for three things: CNAME records that point at a service you've shut down (the setup behind subdomain takeover), staging or admin hosts anyone can reach, and preview deployments that are public or indexed. Everything below uses free tools, and none of it touches anyone else's systems.

Why do certificate logs list my subdomains?

Certificate Transparency (CT) is defined in RFC 9162, which obsoletes the original RFC 6962. Logs are append-only, so certificates can be added but never removed or edited. Anyone can query them. As certificate.transparency.dev explains, a CA submits a precertificate to the logs before issuing the real certificate, and Chrome and Safari require proof of logging, in the form of Signed Certificate Timestamps, before they trust a certificate.

So every hostname you ever got a public certificate for is in those logs: staging., admin., old-api., a demo for one customer. That includes hosts you shut down years ago. Logs are append-only, so the name stays there even after the host is gone.

How do I search certificate transparency for subdomains?

crt.sh

In a browser, open https://crt.sh/?q=%25.example.com. %25 is a URL-encoded %, which crt.sh treats as a wildcard. Add &output=json to get JSON. Each entry has a name_value field holding the certificate's names, separated by newlines. This one-liner prints the unique hostnames:

curl -s 'https://crt.sh/?q=%25.example.com&output=json' \
  | jq -r '.[].name_value' | sed 's/^\*\.//' | sort -u

crt.sh is free and often slow. If it times out, or jq reports a parse error, crt.sh probably sent back an error page and not JSON. Wait and try again. When we tested this command, the first request failed that way and the second worked.

Cert Spotter API (fallback)

SSLMate's CT Search API lists unexpired certificates for a domain. Set include_subdomains=true to cover subdomains at any depth, and expand=dns_names to get the hostnames in the response:

curl -s 'https://api.certspotter.com/v1/issuances?domain=example.com&include_subdomains=true&expand=dns_names' \
  | jq -r '.[].dns_names[]' | sort -u

According to the docs, you can make a limited number of unauthenticated queries per hour for personal or evaluation use. Results come in pages: pass the last id as after= to get the next page. This API only returns unexpired certificates, so it won't show a host whose certificate lapsed long ago. crt.sh still has those.

What doesn't CT show?

For those, you need a second source and your own zone file.

Which free subdomain finder should I use?

subfinder from ProjectDiscovery is built for passive subdomain enumeration. It queries public sources (crt.sh is one of them) and doesn't send traffic to your hosts. Per its current README:

go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
subfinder -d example.com -all -silent -o subdomains.txt

Where is the complete list?

In your DNS provider. Public sources only show what has leaked: certificates, crawls, old datasets. Your zone has every record, including ones that never got a certificate. Export it:

Then compare the two lists. A name in CT that isn't in your zone is either gone, which is fine, or lives in a zone you forgot about, such as an old registrar account or a second domain. A record in your zone that you can't explain is the one to check first.

What is a subdomain takeover and how do I check for one?

MDN describes the usual case. blog.example.com has a CNAME to a hosting provider. You delete the blog at the provider and leave the DNS record in place. Someone else then creates a site with the same provider, claims your hostname, and serves their content on your domain. Microsoft's guidance lists what can follow: phishing pages on a real company subdomain, stolen cookies scoped to *.example.com, and a valid TLS certificate for the hijacked name. The OWASP Testing Guide adds that A, MX and NS records can dangle too, and that a dangling NS record is the worst case because it can hand over a whole zone.

For your own domain you don't need to attack anything. Read your own records:

  1. List every CNAME in your zone export that points to a third-party service (*.herokuapp.com, *.s3.amazonaws.com, *.azurewebsites.net, *.github.io, *.netlify.app, *.vercel.app and so on).
  2. For each one, ask whether you still have that resource in your account. The provider's dashboard answers this faster than any scanner.
  3. Check that the target still resolves: dig +short CNAME staging.example.com, then dig staging.example.com. An NXDOMAIN status, or a CNAME whose target has no address, needs attention.
  4. Open the hostname in a browser. It's your host. The can-i-take-over-xyz project lists the error pages providers show for unclaimed names, for example "The specified bucket does not exist" (AWS S3), "No such app" (Heroku), "There isn't a GitHub Pages site here." (GitHub) and DEPLOYMENT_NOT_FOUND (Vercel). It also marks each provider as vulnerable, not vulnerable or an edge case, and its authors say it doesn't guarantee accuracy.

To fix a dangling record, delete it, or claim the resource back in your own account. MDN's ordering rule prevents a repeat: when you add something, create the resource first and the DNS record last. When you remove something, delete the DNS record first. Microsoft also recommends searching your code for references to the old hostname. If you think a subdomain was already taken over, remove the record first, then follow your incident process.

What should I do about exposed staging and admin hosts?

For each staging., dev., admin. or internal. name that resolves, answer two questions. Does it need to be on the public internet? If yes, does it require a login before it shows anything? Staging can run with test accounts, debug settings or a copy of production data, so a public staging host deserves the same care as production.

Options, roughly from least to most work: delete the DNS record if the host is dead. Shut down the server and then delete the record. Put it behind your platform's access control (below), a VPN or an identity-aware proxy. Or at minimum require a login on every route, not only on the home page.

Are my Vercel or Netlify preview deployments public?

Often, yes, unless you turned protection on.

Preview URLs on vercel.app or netlify.app won't appear in your own domain's CT results, because they sit on the platform's domain. Check those in each project's settings.

Where Mobius fits

The free check on the Mobius home page runs the passive part of this guide for you. Enter a domain and it looks the domain up in certificate transparency logs (via crt.sh), passive subdomain sources, DNS, and Shodan's InternetDB for public scan data on the resolved IPs. It drops shared-hosting and CDN results that can't be shown to belong to you, and suggests one next step. It never connects to or tests your systems. Your zone export and the takeover review above are still yours to do. Run the free check.

Sources