https_dns_proxy: a small DNS-over-HTTPS translator in C
A lightweight DNS-over-HTTPS proxy.
At a glance
- What is it?
- https_dns_proxy is a light-weight C proxy that receives ordinary DNS requests over UDP or TCP and reissues them over DNS-over-HTTPS as defined by RFC 8484, with a binary under 30 KiB and no caching of its own. It is aimed at routers and embedded systems that want encrypted DNS without giving up a local caching resolver.
- Who is it for?
- Adopt https_dns_proxy when you run a router or embedded device with dnsmasq or a similar resolver and you want queries to leave over HTTPS without rewriting the whole resolver stack. Skip it if you want a caching resolver, since this proxy caches nothing by design, or if your platform has no curl, c-ares and libev available.
- 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 5 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A translator, not a resolver
https_dns_proxy is a light-weight DNS to HTTPS, non-caching translation proxy for the RFC 8484 DNS-over-HTTPS standard. It receives regular DNS requests, over UDP or TCP, and issues them via DoH.
The word non-caching is the important qualifier, and it explains the whole design. A resolver that caches answers is a stateful service that has to manage TTLs, eviction and concurrency. This proxy does none of that. It takes a query, forwards it, and hands back the answer.
That makes it a companion to a caching resolver rather than a replacement for one. The README states that it is designed to sit in front of dnsmasq or similar caching resolver for transparent use. The division of labour is clean: dnsmasq keeps answering from its cache and only forwards the misses, and those forwards travel over HTTPS.
Two other design choices follow from the same logic. The server is single-threaded with a non-blocking select() server, which the README says is for use on resource-starved embedded systems. And it uses curl for HTTP/2 and pipelining, which keeps resolve latencies low. The binary is under 30 KiB, which is the number that tells you what class of device this is aimed at.
The practical audience is a router, a small NAS, or an embedded board that already runs dnsmasq and now needs its upstream queries to be encrypted.
Trust moves to the DoH provider, it does not disappear
The README is direct about the trade: using DNS over HTTPS makes eavesdropping and spoofing of DNS traffic between you and the HTTPS DNS provider, Google or Cloudflare, much less likely, and this of course only makes sense if you trust your DoH provider.
That sentence is the whole security argument, and it deserves to be read carefully rather than skimmed. The protection is against the network between you and the provider. It is not protection against the provider, and it is not protection against someone who can rewrite the path to the provider.
The second part is visible in the configuration. The `-b` option takes the comma-separated addresses of DNS servers whose job is to resolve the resolver host, for example dns.google. Those lookups are ordinary DNS, because there is no other way to find the address of an HTTPS endpoint the first time. The default list is public resolvers: 8.8.8.8, 1.1.1.1, 8.8.4.4, 1.0.0.1, 145.100.185.15, 145.100.185.16 and 185.49.141.37. So a bootstrap query for the provider's own hostname still goes out in the clear, and the privacy property only holds for the queries that matter.
If that trade is unacceptable, the option is to point `-b` at resolvers you control, or to the address of a resolver on your own network, at the cost of needing that name resolved some other way.
The command line is the configuration surface
There is no configuration file in the documentation. Everything is a flag, and the README prints the full usage block:
Usage: ./https_dns_proxy [-a <listen_addr>] [-p <listen_port>] [-T <tcp_client_limit>]
[-b <dns_servers>] [-i <polling_interval>] [-4]
[-r <resolver_url>] [-t <proxy_server>] [-S <source_addr>] [-x] [-q] [-C <ca_path>] [-c <dscp_codepoint>]
[--outbound-interface <interface>]
[-d] [-u <user>] [-g <group>]
[-v]+ [-l <logfile>] [-s <statistic_interval>] [-F <log_limit>] [-V] [-h]The defaults are the interesting part. It binds to 127.0.0.1 on port 5053. The resolver URL defaults to `https://dns.google/dns-query`, and Cloudflare's `https://cloudflare-dns.com/dns-query` works with what the README calls trivial command line flag changes:
# ./https_dns_proxy -u nobody -g nogroup -d -b 1.1.1.1,1.0.0.1 \
-r "https://cloudflare-dns.com/dns-query"The Google form is the same shape:
# ./https_dns_proxy -u nobody -g nogroup -d -b 8.8.8.8,8.8.4.4 \
-r "https://dns.google/dns-query"Other flags worth knowing about: `-d` daemonises, `-u` and `-g` set the user and group to drop privileges to, `-4` forces IPv4 hostnames for resolvers on non IPv6 networks, `-t` sets an optional HTTP proxy such as a socks5 URL, `-C` points at a CA path for certificate verification, `-l` sets a log file, `-s` sets a statistic interval and `-F` a log limit.
The TCP client limit defaults to 20 and can be disabled with 0, and the polling interval for DNS servers defaults to 120 seconds with a range from 5 to 3600.
Building needs c-ares, libcurl and libev
The build dependencies are specific and modest. The project depends on c-ares at version 1.11.0 or later, libcurl at 7.66.0 or later, and libev at 4.25 or later. On Debian-derived systems those are libc-ares-dev, libcurl4-{openssl,nss,gnutls}-dev and libev-dev, and on Red Hat derived systems they are c-ares-devel, libcurl-devel and libev-devel. On systems with systemd it is recommended to have the libsystemd development package installed.
On Ubuntu the README lists the whole set:
apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev libsystemd-dev build-essentialmacOS has its own wrinkle, because the system curl headers can be a problem. The README notes that others have had success when first installing curl with brew:
brew install curl --with-openssl --with-c-ares --with-libssh2 --with-nghttp2 --with-gssapi --with-libmetalink
brew link curl --forceOnce the prerequisites are in place, the build is two commands:
$ cmake .
$ makeHTTP/3 support depends on your libcurl. If the system libcurl supports it by default, nothing else is needed. If a custom build of libcurl with HTTP/3 is installed elsewhere, cmake can be pointed at it with `-D CUSTOM_LIBCURL_INSTALL_PATH=/absolute/path/to/custom/libcurl/install .`. There is also a `development_build_with_http3.sh` script in the repository for testing HTTP/3 during development, which the README says simply takes a long time to run.
The repository layout backs this up: a `CMakeLists.txt` at the root, `src/` for the C sources, `tests/`, a `munin/` directory for the monitoring plugin, a `https_dns_proxy.service.in` systemd unit template, and a `development_build_with_http3.sh` script.
OpenWrt is the deployment the README is really about
The OpenWrt section is the most specific part of the documentation, and it shows the intended deployment.
There is a package in the OpenWRT packages repository, and the install is four commands:
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install https-dns-proxy
root@OpenWrt:~# /etc/init.d/https-dns-proxy enable
root@OpenWrt:~# /etc/init.d/https-dns-proxy startThe init script does something that matters. As the README describes it, OpenWrt's init script automatically updates the dnsmasq config to include only DoH servers on its start, and restores the old settings on stop. So the wiring between the local caching resolver and the DoH proxy is handled for you on the router distribution where it matters most.
If you use a different resolver on the router, you have to do that replacement yourself, pointing it at an entry like `127.0.0.1#5053`, which is the proxy's default port. The README also suggests preventing your resolver from using the DNS servers in /etc/resolv.conf, leaving only the proxy server, otherwise the resolver can bypass the proxy entirely.
There is also a WebUI package for OpenWrt, `luci-app-https-dns-proxy`, which contains the list of supported and tested DoH providers. That list is worth consulting before choosing a provider, since it is the difference between a tested combination and one you are guessing at.
systemd, munin and a community Docker image
On a Linux system with systemd, installation after the build is a single command that installs the binary, the systemd service and the munin plugin if munin is already present:
$ sudo make installThe munin plugin is then activated by restarting the munin services:
$ sudo systemctl restart munin munin-nodeTo change the service options, `sudo systemctl edit https_dns_proxy.service` is used and the ExecStart is redefined:
[Service]
ExecStart=
ExecStart=/usr/local/bin/https_dns_proxy \
-u nobody -g nogroup -r https://doh.opendns.com/dns-queryThe Docker route uses an externally maintained image rather than one from this repository. The README links the image at hub.docker.com/r/bwmoran/https-dns-proxy and its Dockerfile and entrypoint script in the moranbw/https-dns-proxy-docker repository, with an example run:
### points towards AdGuard DNS, only use IPv4, increase logging ###
docker run --name "https-dns-proxy" -p 5053:5053/udp \
-e DNS_SERVERS="94.140.14.14,94.140.15.15" \
-e RESOLVER_URL="https://dns.adguard.com/dns-query" \
-d bwmoran/https-dns-proxy \
-4 -vvvThe environment variables there, `DNS_SERVERS` and `RESOLVER_URL`, map onto the `-b` and `-r` flags, and `-vvv` raises the verbosity. The port mapping is 5053, which is the proxy's own default, so the container publishes the same port the daemon uses by default.
For Arch Linux there is an externally maintained AUR package for the latest git version, installed with `yay -S https-dns-proxy-git`.
The pattern across all three is consistent: the project ships the C program, and the packaging for each platform lives with the people who run that platform.
MIT terms, no releases, and a repository that ships the binary
The licence field on the repository says MIT, which is permissive and lets you redistribute the binary inside a router image or an appliance without obligations beyond keeping the notice. For a project that gets built into firmware and appliance images, that matters.
The maintenance facts are narrow but current. The repository has no GitHub releases, and the last push was on 2026-10-19. There is no release history to read, so the commit log and the distribution packaging, the OpenWrt package, the AUR package and the Docker image, are where you find out which version you are running.
That arrangement has a consequence for an operator. When a security issue is fixed upstream, whether you receive it depends on the packaging chain rather than on a tag. The OpenWrt package, the AUR package and the Docker image are all maintained outside this repository, and each has its own update cadence. A router that pulls from opkg gets updates when the OpenWrt package maintainers refresh it.
The other thing worth knowing before deploying is the failure mode. Because the proxy is the only path from your resolver to the upstream, a TLS failure, an expired certificate or an unreachable provider stops name resolution for every client behind it, unless the resolver in front has cached entries to fall back on. The `-C ca_path` option exists precisely because certificate trust stores differ between the distributions this runs on, and getting it right is the difference between working and not.
For a tool this small, under 30 KiB with three well known dependencies, the README's advice of putting it in front of a caching resolver is also the safest failure posture: keep dnsmasq as the cache, and treat the proxy as a replaceable upstream.
Editorial conclusion
Adopt https_dns_proxy when you run a router or embedded device with dnsmasq or a similar resolver and you want queries to leave over HTTPS without rewriting the whole resolver stack. Skip it if you want a caching resolver, since this proxy caches nothing by design, or if your platform has no curl, c-ares and libev available. Verify first that the TLS trust path works in your environment with the `-C ca_path` option, and decide deliberately which DoH provider you trust, because the proxy still needs plain DNS to resolve the provider's own hostname on first use. The licence field on the repository says MIT, the repository has no GitHub releases, and the last push was on 2026-10-19.
Frequently asked questions
What does https_dns_proxy do?
It is a light-weight DNS to HTTPS, non-caching translation proxy for the RFC 8484 DNS-over-HTTPS standard. It receives regular DNS requests over UDP or TCP and reissues them over DoH, and it is designed to sit in front of dnsmasq or a similar caching resolver.
How do I build https_dns_proxy from source?
The build depends on c-ares 1.11.0 or later, libcurl 7.66.0 or later and libev 4.25 or later. On Ubuntu the README lists `apt-get install cmake libc-ares-dev libcurl4-openssl-dev libev-dev libsystemd-dev build-essential`, and the build itself is `cmake .` followed by `make`.
How do I install https_dns_proxy on OpenWrt?
A package is available in the OpenWRT packages repository. The README gives `opkg update`, `opkg install https-dns-proxy`, then enable and start the service with `/etc/init.d/https-dns-proxy enable` and `/etc/init.d/https-dns-proxy start`. The init script updates the dnsmasq config to include only DoH servers on start and restores the old settings on stop.
Which DoH providers does https_dns_proxy support?
Google's DNS-over-HTTPS service is the default with the resolver URL `https://dns.google/dns-query`, and Cloudflare's service works with trivial command line flag changes using `https://cloudflare-dns.com/dns-query`. A WebUI package for OpenWrt, `luci-app-https-dns-proxy`, contains the list of supported and tested DoH providers.
Does https_dns_proxy cache DNS responses?
No. It is described as a non-caching translation proxy, and it is designed to sit in front of dnsmasq or a similar caching resolver. The README notes that if you use another resolver you must manually replace the previous servers with an entry like `127.0.0.1#5053`.
What licence does https_dns_proxy use?
The licence field on the repository says MIT. The repository has no GitHub releases, and the last push was on 2026-10-19, with packaging for OpenWrt, Arch Linux and Docker maintained outside the repository.
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/aarond10-https-dns-proxy)Was this useful?