dnslookup, one binary for plain DNS, DoH, DoT, DoQ and DNSCrypt
Simple command line utility to make DNS lookups to the specified server
At a glance
- What is it?
- dnslookup is a Go command line tool that sends DNS queries to a chosen server over plain DNS, DNS-over-TCP, DoH, DoT, DoQ or DNSCrypt, with the transport selected by a URL prefix and the query options set through environment variables. The last push was on 2026-06-01 and the newest tag is v1.12.0.
- Who is it for?
- dnslookup suits operators who need to reproduce a specific resolver path from a script, such as confirming what a DoH endpoint returns, sending a DNSSEC DO bit query, or checking an EDNS option against a specific server, and it installs with a single go install or a Homebrew tap. It is the wrong tool for bulk zone work or for scripting against a rich DNS API, because the option surface is environment variables rather than arguments and the output format is fixed.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 146 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 October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The transport is chosen by a URL prefix, not a flag
dnslookup is a small Go program for sending a single DNS query to a server you name. Its distinguishing feature is the range of transports it speaks, listed in the README as plain DNS, plain DNS-over-TCP, DoH, DoT, DoQ and DNSCrypt, and the selection mechanism is uniform: the server argument is a URL whose scheme picks the protocol.
So a plain lookup against the system resolver needs no server at all, while the same lookup against a specific address uses the bare IP form, and every encrypted variant carries a scheme:
dnslookup example.org
dnslookup example.org 94.140.14.14
dnslookup example.org tcp://94.140.14.14
dnslookup example.org tls://dns.adguard-dns.com
dnslookup example.org quic://9.9.9.9The scheme is doing the work that a flag such as --protocol would do elsewhere, and one consequence is visible in the examples: a host name and an IP address can be combined, so the resolver you talk to and the address you reach it at are separate inputs. That is what makes it possible to test a specific resolver's behaviour without relying on the machine's own name resolution to find it.
The tool is also the rare case where a single query is the whole job. There is no zone file to load and no batch mode documented. The interface is one domain, one server, and a set of options.
Environment variables carry the query options
Everything beyond the transport is set through environment variables prefixed by name, and the README groups them by purpose. The query itself is controlled by RRTYPE, which defaults to A, and CLASS, which defaults to IN:
RRTYPE=AAAA dnslookup example.org tls://127.0.0.1
RRTYPE=HTTPS dnslookup example.org tls://127.0.0.1
CLASS=CH dnslookup example.org tls://127.0.0.1Then there is the EDNS layer. DNSSEC=1 sets the DO bit in the request's OPT record, SUBNET sets the EDNS client subnet, and PAD adds EDNS0 padding. EDNSOPT takes a code point and an optional hexadecimal payload in the form code:value, and the README gives a concrete example tied to dnsmasq's --add-cpe-id=12345678:
EDNSOPT=65074:3132333435363738 RRTYPE=TXT dnslookup o-o.myaddr.l.google.com tls://8.8.8.8Because these are environment variables, they compose by prefixing the command, which the README demonstrates by combining three at once:
RRTYPE=TXT SUBNET=1.1.1.1/24 PAD=1 dnslookup o-o.myaddr.l.google.com tls://8.8.8.8That is a genuine ergonomic difference from flag based tools, and it is also the main scripting hazard, since a variable left set in a shell profile silently changes every later query.
HTTP/3, basic auth and PTR lookups
Three features sit outside the basic transport list and are worth knowing about.
The first is HTTP/3 for DNS over HTTPS. The DoH examples use an https:// prefix, and there are two extra forms: an environment variable that lets the client choose the protocol version automatically, and an h3:// prefix that forces HTTP/3 only.
HTTP3=1 dnslookup example.org https://dns.google/dns-query
dnslookup example.org h3://dns.google/dns-queryThe second is basic authentication against a DoH endpoint, which the README notes is supported by AdGuard DNS:
dnslookup example.org https://username:[email protected]/dns-queryThe third is the PTR query, and here the tool does a little inference on your behalf. When no RRTYPE is given and the argument looks like an IP address, dnslookup sends a PTR query instead, for both address families:
dnslookup 8.8.8.8
dnslookup 2606:4700:4700::1111For debugging and for integration work, the PTR behaviour is the piece that saves the most typing, since reverse lookups are common in troubleshooting and rarely need any other option set.
Output for machines, and one switch that disables certificate checks
The tool has a machine readable mode selected by an environment variable, which is the piece that makes it usable from a script rather than only from a terminal:
JSON=1 dnslookup example.org 94.140.14.14A second environment variable affects transport security rather than output. VERIFY=0 disables certificate verification, and the README gives a local address as the example target, which suggests the intended use is a resolver running on loopback during development:
VERIFY=0 dnslookup example.org tls://127.0.0.1There is also a verbose mode for logging detail:
VERBOSE=1 dnslookup example.org tls://dns.adguard-dns.comThe DNSCrypt path is the most involved of the transports, and the README shows two ways to express it. One is a stamp, the compact base64-like form that encodes the resolver's public key along with its address:
dnslookup example.org sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20The other is the expanded form, where the resolver address, the provider name and the public key are given as separate arguments, with the key written as a colon separated hexadecimal string. The dependency list explains where that parsing comes from, since github.com/ameshkov/dnsstamps v1.0.3 is a direct dependency and github.com/ameshkov/dnscrypt/v2 v2.4.0 is pulled in indirectly.
Install paths and what the build produces
Four installation routes are documented. Homebrew comes first, through a tap the author maintains:
brew install ameshkov/tap/dnslookupInstalling from source with the Go toolchain is the second:
go install github.com/ameshkov/dnslookup@latestThe remaining two are a binary from the releases page and the Snap Store package. The Snap Store badge in the README header and the snapcraft.yaml file at the repository root confirm that route, and the Makefile drives the release artifacts.
The Makefile is small and reveals the build contract. The build target runs go build with a ldflags string that sets main.VersionString from the VERSION variable, so the version reported at runtime comes from the linker rather than from a source constant. The release target requires GOOS and GOARCH to be set, and it errors out if either is missing, which is a deliberate guard for cross compilation. It then copies LICENSE and README.md into the build directory, builds with CGO_ENABLED=0 so the binary is static, and packages it as a tar.gz on Unix platforms and a zip on Windows, choosing between the two with an ifeq on the OS.
The test target runs go test with the race detector, verbose output and benchmarks enabled, and there is a golangci configuration in the repository root, so linting is part of the workflow rather than an afterthought.
A Go client built on Adguard libraries, and where it stops
The dependency list explains most of the design. The core comes from github.com/AdguardTeam/dnsproxy v0.81.4, which is the proxy library that handles the transport protocols, plus github.com/AdguardTeam/golibs v0.35.13 for general utilities and github.com/miekg/dns v1.1.72 for the DNS message handling itself. On top of that sit quic-go for QUIC, including the qpack package for HTTP/3 header compression, golang.org/x/crypto and golang.org/x/net for the rest of the standard stack, and dnsstamps for the DNSCrypt stamp format. The module targets go 1.26.3, and the code is a single main.go at the repository root, which is why the whole tool is a single binary with no runtime configuration files.
That gives a clear boundary. dnslookup asks one question well and in many ways, and it is not a DNS administration tool. There is no AXFR, no zone editing, no delegation management and no recursive resolver mode, and the output format is fixed apart from the JSON switch. The alternative to compare it against is not another binary with a different flag style but the ordinary path of using the system resolver, which is one command with fewer options and no control over the transport.
Where dnslookup earns its place is the cases the system resolver cannot cover: verifying that a specific encrypted resolver answers as expected, sending a DNSSEC DO query, attaching an EDNS client subnet to see how a resolver echoes it, or testing a resolver on loopback with certificate checks disabled. For those jobs, the protocol matrix and the option set are the reason to install it. For everything else, the system resolver already does the job with less to configure.
Editorial conclusion
dnslookup suits operators who need to reproduce a specific resolver path from a script, such as confirming what a DoH endpoint returns, sending a DNSSEC DO bit query, or checking an EDNS option against a specific server, and it installs with a single go install or a Homebrew tap. It is the wrong tool for bulk zone work or for scripting against a rich DNS API, because the option surface is environment variables rather than arguments and the output format is fixed. Before adopting it in CI, check that the release binary or the go install path matches your Go version requirement, that your resolver accepts the transport you are testing, and that the JSON=1 output is the shape your parser expects.
Frequently asked questions
How do I install dnslookup?
The README lists four options: brew install ameshkov/tap/dnslookup for Homebrew, go install github.com/ameshkov/dnslookup@latest from source, a binary from the releases page, and the Snap Store package. The Go module targets go 1.26.3.
How do I query a DNS server over DoH or DoT with dnslookup?
The transport is selected by the scheme in the server argument, so tls://dns.adguard-dns.com sends over DNS-over-TLS and https://dns.adguard-dns.com/dns-query sends over DNS-over-HTTPS. Prepend HTTP3=1 to let the client pick the HTTP version, or use an h3:// prefix to force HTTP/3 only.
How do I set the query type and class in dnslookup?
Use environment variables in front of the command: RRTYPE=AAAA for the record type, which defaults to A, and CLASS=CH for the class, which defaults to IN. Several can be combined, for example RRTYPE=TXT SUBNET=1.1.1.1/24 PAD=1 dnslookup o-o.myaddr.l.google.com tls://8.8.8.8.
Can dnslookup output JSON for scripting?
Yes. Setting JSON=1 before the command switches the output to a machine readable format, as shown by JSON=1 dnslookup example.org 94.140.14.14. A VERBOSE=1 variable enables verbose logging and VERIFY=0 disables certificate verification.
Does dnslookup support reverse PTR lookups?
It does, and it recognises the address for you. When no RRTYPE is specified and the argument is an IP address, dnslookup sends a PTR query, for example dnslookup 8.8.8.8 or dnslookup 2606:4700:4700::1111 for IPv6.
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/ameshkov-dnslookup)Was this useful?