Posts in category “DNS”

Setting Up DNSCrypt-Proxy: Your Gateway to Secure DNS

In today's digital landscape, protecting your online privacy starts with securing your DNS queries. While you browse websites, your device constantly translates domain names into IP addresses through DNS requests—and by default, these requests travel in plaintext, making them vulnerable to eavesdropping and manipulation. Enter dnscrypt-proxy, a powerful tool that encrypts your DNS traffic and shields your browsing habits from prying eyes.

Why DNSCrypt-Proxy Matters

Traditional DNS queries are like sending postcards through the mail—anyone handling them can read the contents. DNSCrypt-proxy wraps these queries in encryption, supporting modern protocols like DNS-over-HTTPS (DoH) and DNSCrypt, effectively turning those postcards into sealed envelopes.

Key Benefits:

  • Enhanced Privacy: Your DNS queries remain hidden from ISPs and network administrators
  • Security: Protection against man-in-the-middle attacks and DNS spoofing
  • Flexibility: Support for multiple encrypted DNS protocols
  • Performance: Automatic server selection for optimal response times
  • Advanced Features: Built-in filtering, caching, and anonymization options

Installation Made Simple

Ubuntu/Debian Systems

sudo apt update
sudo apt install dnscrypt-proxy

Other Distributions

  • CentOS/RHEL: sudo yum install epel-release && sudo yum install dnscrypt-proxy
  • Arch Linux: sudo pacman -S dnscrypt-proxy
  • Fedora: sudo dnf install dnscrypt-proxy

Pro Tip: Ensure you're running version 2.1.2 or higher for full compatibility with modern resolver lists.

Configuration Essentials

The magic happens in /etc/dnscrypt-proxy/dnscrypt-proxy.toml. Here's a streamlined configuration to get you started:

# Listen on localhost (systemd socket activation)
listen_addresses = []

# Choose reliable DNS providers
server_names = ['cloudflare', 'quad9-dnscrypt-ip4-filter-pri']

# Enable protocol support
ipv4_servers = true
ipv6_servers = true
dnscrypt_servers = true
doh_servers = true

# Security preferences
require_nolog = true
require_nofilter = false

# Logging (optional)
[query_log]
file = '/var/log/dnscrypt-proxy/query.log'

# Sources for server lists
[sources]
[sources.'public-resolvers']
url = 'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md'
cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72

System Integration

Working with systemd-resolved

Modern Linux distributions often use systemd-resolved for DNS management. Here's how to integrate smoothly:

  1. Configure systemd-resolved in /etc/systemd/resolved.conf:
[Resolve]
DNS=127.0.0.1:53
FallbackDNS=
  1. Enable socket activation for dnscrypt-proxy:
sudo systemctl enable dnscrypt-proxy.socket
sudo systemctl start dnscrypt-proxy.socket
  1. Restart systemd-resolved:
sudo systemctl restart systemd-resolved

Alternative: Replace systemd-resolved

For complete control, you can disable systemd-resolved entirely:

sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved

# Edit /etc/resolv.conf directly
echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf

Advanced Configuration Tips

Using AdGuard DNS with Authentication

For enhanced filtering capabilities, you can configure AdGuard DNS with custom settings:

server_names = ['adguard-dns-doh']

[static]
[static.'adguard-dns-doh']
stamp = 'sdns://AgMAAAAAAAAADzEwNC4xNi4xOTkuMTcyIDUqU_yZB8_YEyiRZSW52XCF5Eg6wS8RdRfCi9E9q4S3HXB4kVfWB28QtYzlrCvIwXv0eSKIZTW8bZ-c'

Performance Optimization

# Reduce timeout for faster responses
timeout = 2500

# Enable keepalive for persistent connections
keepalive = 30

# Optimize for your network
max_clients = 250

Privacy-First Settings

# Only use no-log providers
require_nolog = true

# Enable DNSSEC validation
require_dnssec = true

# Block IPv6 if not needed
block_ipv6 = false

Verification and Testing

Once configured, verify your setup works correctly:

# Test resolution
sudo dnscrypt-proxy -resolve google.com

# Check with nslookup
nslookup google.com
# Should show: Server: 127.0.0.1

# Verify service status
sudo systemctl status dnscrypt-proxy

Troubleshooting Common Issues

Port Conflicts: If port 53 is already in use, configure dnscrypt-proxy on an alternative port like 5353, then update your system DNS settings accordingly.

Socket Activation Issues: Ensure listen_addresses = [] when using systemd socket activation, or set specific addresses when using the service directly.

Resolver List Updates: If you encounter Minisign key errors, update your configuration with the latest keys from the DNSCrypt project.

The Bottom Line

Setting up dnscrypt-proxy transforms your DNS queries from vulnerable plaintext into encrypted, authenticated communications. Whether you're concerned about privacy, security, or simply want more control over your DNS resolution, dnscrypt-proxy provides a robust, flexible solution that integrates seamlessly with modern Linux systems.

The investment in setup time pays dividends in enhanced privacy and security for all your online activities. Your browsing habits remain your business—exactly as it should be.


Ready to secure your DNS? Start with the basic configuration above, then customize based on your specific privacy and performance requirements. Your future self will thank you for taking this important step toward online privacy.

Building a Modern, Privacy-First Home DNS with Unbound

Over several months, we evolved from a simple DNS forwarder into a sophisticated, public-facing Unbound DNS setup with DoT/DoH, dnscrypt-proxy integration, EDNS tuning, and OISD blocklists. This post distills that journey into a practical guide: not just how to configure Unbound, but why we made each choice and what actually mattered.

Why Unbound?

We needed a fast, privacy-respecting, locally-controlled DNS resolver on FreeBSD/Ubuntu that could serve Android Private DNS clients and integrate with privacy providers like AdGuard and NextDNS.[1][2][39]

After testing dnscrypt-proxy + HAProxy and Caddy + Unbound, we found Unbound as the core was the best fit because it:

  • Acts as a validating resolver, forwarder, and cache in one.[39]
  • Supports native DoT/DoH when compiled properly.[11][17]
  • Integrates seamlessly with dnscrypt-proxy for upstream privacy.[12][30][35]

Getting Basics Right: Forwarding Only

Since we forward to privacy providers rather than run full recursion, we simplified Unbound substantially:

forward-zone:
    name: "."
    forward-first: no
    forward-addr: 127.0.0.1@5300   # e.g., dnscrypt-proxy

Key behaviors:[18][20]

  • name: "." forwards everything.
  • forward-first: no means pure forwarding—no recursive fallback on upstream failure.
  • With multiple forward-addr entries, Unbound tracks performance and picks the best upstream.[20]

Upstream Choices: Plain DNS vs DoT vs DoH

Plain DNS Forwarding

When dnscrypt-proxy only accepted plain DNS:

server:
    do-udp: yes
    do-tcp: yes
    tcp-upstream: no

forward-zone:
    name: "."
    forward-tls-upstream: no
    forward-addr: 127.0.0.1@5300

[16][30]

DNS-over-TLS Upstream

For resolvers like Quad9 or NextDNS over DoT:[6][7]

forward-zone:
    name: "."
    forward-tls-upstream: yes
    forward-addr: quad9.net@853

DNS-over-HTTPS via dnscrypt-proxy

We let dnscrypt-proxy speak DoH to AdGuard/NextDNS and Unbound send plain DNS to dnscrypt-proxy. This separation simplified debugging.[27][42][52]

Serving DoT for Android Private DNS

One central goal: "I want any Android Private DNS client to connect."[21][50]

server:
    interface: 0.0.0.0@853
    interface: ::0@853
    tls-port: 853
    tls-service-key: "/etc/letsencrypt/live/dot.example.com/privkey.pem"
    tls-service-pem: "/etc/letsencrypt/live/dot.example.com/fullchain.pem"
    access-control: 0.0.0.0/0 allow
    access-control: ::0/0 allow

Critical details:[6][9][38][50]

  • The certificate CN/SAN must match the hostname clients use.
  • Let's Encrypt privkey/fullchain work directly if file permissions are correct.
  • Test with: dig @your.hostname -p 853 +tls example.com[6][21]

Serving DoH (Optional)

To serve DoH over IPv6 on port 443, Unbound must be compiled with nghttp2:[11][17]

server:
    interface: [::0]@443
    tls-service-key: "/path/to/privkey.pem"
    tls-service-pem: "/path/to/cert.pem"
    https-port: 443

Test with: curl -X POST -d @query.bin https://dot.example.com/dns-query --http2[11]

Privacy & Performance: EDNS and ECS

EDNS(0) Basics

Unbound supports EDNS(0) by default—there is no single "enable EDNS" toggle.[14]

EDNS Client Subnet (ECS)

To forward client subnet info to upstreams (e.g., Quad9) for better CDN geolocation:[7][8]

  • Compile Unbound with --enable-subnet.
  • Enable in config: send-client-subnet: yes.
  • Optional: client-subnet-always-forward: yes to forward even if client didn't request it.[7][8]

Note: ECS only forwards a truncated subnet (e.g., /24), not the full client IP. Raw client IP forwarding is not possible in DNS standards.[8][33]

Hardening: Access Control & Rate Limiting

Broad Access Control for Public DoT

access-control: 0.0.0.0/0 allow
access-control: ::0/0 allow

But this requires abuse protection.[21][23]

Rate Limiting

On Unbound 1.23+, use:[23]

ratelimit-queries: 100      # per-IP queries/second
ratelimit-answers: 100
ratelimit-log: yes

On Unbound 1.19.x, use iptables/nftables instead or upgrade.[23][28]

Logging Best Practices

Keep normal operation quiet:

server:
    logfile: "/var/log/unbound.log"
    verbosity: 1

Use unbound -d -v for temporary debugging only.[13][24][29]

Tuning for Low-Traffic, Quick Response

Threads & File Descriptors

For light load with emphasis on latency:[24]

outgoing-range: 512
num-queries-per-thread: 256
num-threads: 1

(Default: outgoing-range: 4096, num-queries-per-thread: 2048.) Keep the ratio: num-queries-per-thread ≈ outgoing-range / 2.[10][24]

Cache Tuning

msg-cache-size: 8m
rrset-cache-size: 16m
cache-min-ttl: 600          # artificially extend TTLs
cache-max-ttl: 86400

This keeps small servers responsive while reducing upstream queries.[24][25]

DNS-Level Content Filtering: OISD Blocklist

Integrating OISD

  1. Download the Unbound-format OISD list:

    curl -o /etc/unbound/blocklist.conf \
        "https://oisd.nl/api/domainlist/unbound?list=basic"
    
  2. Include in unbound.conf:

    include: "/etc/unbound/blocklist.conf"
    
  3. Reload Unbound:

    unbound-control reload
    

[22]

Local-Zone Types

OISD uses always_null (returns 0.0.0.0/::) by default to avoid breaking some IoT devices. Other options:[22][51]

  • always_refuse: reply REFUSED (cleanest for debugging).
  • always_nxdomain: fake "domain doesn't exist" (privacy-pure).
  • always_nodata: return NOERROR with no records (rare).

IPv6, Interfaces & Debugging

IPv6 Listener Ignoring Forwarders?

If Unbound on IPv6 ignores forward-addr and falls back to recursion:[15]

  • Confirm do-ip6: yes in server block.
  • Check upstream IPv6 reachability: tcpdump -i any -n port 53 | grep IPv6.
  • Use unbound-control status to verify forwarder vs recursive mode.[15][24]

Debugging Tools

  • Check syntax: unbound-checkconf
  • Run in foreground: sudo unbound -d -v
  • Inspect cache: unbound-control dump_cache
  • Test DoT: dig @your.hostname -p 853 +tls example.com
  • Test DoH: curl -X POST -d @query.bin https://your.hostname/dns-query --http2

Avoiding Common Pitfalls

  1. PROXY protocol + TLS on same port doesn't work: Choose either PROXY on a separate port or TLS directly in Unbound, not both.[26][49]
  2. forward-first: yes enables recursive fallback: Use no for pure forwarding.[18][20]
  3. Unbound can't forward raw client IP: Only EDNS Client Subnet (ECS) is supported.[8][33]
  4. http took too long, dropped means HTTP upstream is misconfigured: Disable forward-http-upstream if you only want plain DNS or DoT.[16]
  5. Let's Encrypt permissions: Unbound needs read access; ensure correct chown/chmod.[6][9][50]

Final Architecture

Our converged setup:

  • Local cache & forwarder on UDP/TCP 53 with light tuning.
  • DoT endpoint on 853 using Let's Encrypt, open to any Android Private DNS client.
  • Optional DoH on IPv6:443, compiled with nghttp2.
  • dnscrypt-proxy upstream handling DoH to AdGuard/NextDNS.
  • EDNS/ECS tuned deliberately for balance between privacy and CDN performance.
  • OISD blocklist for DNS-level ad/malware filtering.
  • Access control & basic rate limiting for abuse protection.