Pattern-based DNS routing: Match incoming DNS requests against configurable regex patterns
CNAME-aware routing: Secondary pattern matching on CNAME responses for intelligent resolver selection
Multiple resolver support: Route to different DNS resolvers based on pattern matches
gRPC interface: Full DNS resolution capabilities via gRPC with runtime configuration updates
Prometheus metrics: Comprehensive metrics for monitoring
Health endpoints: Kubernetes-compatible health and readiness probes
Request Pattern Matching: When a DNS request arrives (via DNS or gRPC), it checks if the query name matches any configured request patterns.
Non-recursive Lookup to Explicit Resolver: If a pattern matches, performs a non-recursive DNS lookup to the configured "explicit resolver".
CNAME Pattern Matching: If the response contains a CNAME record, checks if the CNAME target matches any CNAME patterns.
Recursive Lookup: If a CNAME pattern matches, performs a recursive lookup to the "explicit resolver".
System Fallback: If no CNAME match occurs (but request pattern matched), uses the system resolver. If no request pattern matches at all, uses the system resolver.
docker run -p 5353:5353/udp -p 5353:5353/tcp -p 5354:5354 -p 8080:8080 \
ghcr.io/steigr/nameserver-switcher:latest| Flag | Description | Default |
|---|---|---|
| Newline-delimited regex patterns for matching incoming requests | "" |
| Newline-delimited regex patterns for matching CNAME responses | "" |
| DNS server for initial non-recursive lookups | "" |
| DNS server for recursive lookups when CNAME matches | "" |
| Address to listen for DNS requests | "0.0.0.0" |
| Port for DNS server | 5353 |
| Address to listen for gRPC requests | "0.0.0.0" |
| Port for gRPC server | 5354 |
| Address for HTTP health/metrics server | "0.0.0.0" |
| Port for HTTP server | 8080 |
| Enable debug logging | false |
| Log all DNS requests | true |
| Log all DNS responses | true |
| Log output format: | text |
| Variable | Description |
|---|---|
| Newline-delimited regex patterns for request matching |
| Newline-delimited regex patterns for CNAME matching |
| DNS server for non-recursive lookups |
| DNS server for recursive CNAME lookups |
| Address for DNS server |
| Port for DNS server |
| Address for gRPC server |
| Port for gRPC server |
| Address for HTTP server |
| Port for HTTP server |
| Enable debug logging ( |
| Log DNS requests ( |
| Log DNS responses ( |
| Log output format: |
The nameserver-switcher provides comprehensive logging capabilities with support for text and JSON output formats.
Text format (default): Human-readable logs with millisecond-precision timestamps.
JSON format: Structured logs for log aggregation systems. Each entry includes a timemillis field with Unix timestamp in milliseconds.
When enabled (default), logs all DNS requests and responses:
Text format:
2026/01/29 14:32:15.123 [INFO] DNS request received protocol=udp type=A name=example.com. from=127.0.0.1:54321
2026/01/29 14:32:15.135 [INFO] DNS response sent name=example.com. rcode=NOERROR answer_count=1 resolver=system duration_ms=12.345JSON format:
{"timemillis":1738161135123,"time":"2026-01-29T14:32:15.123Z","level":"INFO","message":"DNS request received","fields":{"protocol":"udp","type":"A","name":"example.com.","from":"127.0.0.1:54321"}}
{"timemillis":1738161135135,"time":"2026-01-29T14:32:15.135Z","level":"INFO","message":"DNS response sent","fields":{"name":"example.com.","rcode":"NOERROR","answer_count":1,"resolver":"system","duration_ms":12.345}}When debug mode is enabled, provides detailed information about pattern matching and resolution:
Text format:
2026/01/29 14:32:15.136 [DEBUG] REQUEST_PATTERN matched pattern=.*\.example\.com$ request=test.example.com
2026/01/29 14:32:15.137 [DEBUG] CNAME_PATTERN matched pattern=.*\.cdn\.* cname=test.cdn.provider.net
2026/01/29 14:32:15.138 [DEBUG] Queried nameserver resolver=explicitJSON format:
{"timemillis":1738161135136,"time":"2026-01-29T14:32:15.136Z","level":"DEBUG","message":"REQUEST_PATTERN matched","fields":{"pattern":".*\\.example\\.com$","request":"test.example.com"}}
{"timemillis":1738161135137,"time":"2026-01-29T14:32:15.137Z","level":"DEBUG","message":"CNAME_PATTERN matched","fields":{"pattern":".*\\.cdn\\..*","cname":"test.cdn.provider.net"}}Minimal logging (errors only):
nameserver-switcher --log-requests=false --log-responses=falseFull debug logging (text format):
nameserver-switcher --debug --log-requests --log-responsesProduction JSON logging:
nameserver-switcher --log-format=json --log-requests --log-responsesEnvironment variable configuration:
export DEBUG=true
export LOG_REQUESTS=true
export LOG_RESPONSES=true
export LOG_FORMAT=json
nameserver-switcherFor detailed logging documentation, see Logging Documentation.
nameserver-switcher \
--request-patterns=".*\.example\.com$" \
--cname-patterns=".*\.cdn\.provider\.net$" \
--request-resolver="8.8.8.8:53" \
--explicit-resolver="1.1.1.1:53"export REQUEST_PATTERNS=".*\.example\.com$.*\.test\.org$"export CNAME_PATTERNS=".*\.cdn\.com$"export REQUEST_RESOLVER="8.8.8.8:53"export EXPLICIT_RESOLVER="1.1.1.1:53"
nameserver-switcher# Install grpcurl if needed: brew install grpcurl# Resolve a domain
grpcurl -plaintext -d '{"name": "www.example.com", "type": "A"}' \
localhost:5354 api.v1.NameserverSwitcherService/Resolve
# Get current configuration
grpcurl -plaintext localhost:5354 api.v1.NameserverSwitcherService/GetConfig
# Update request patterns
grpcurl -plaintext -d '{"patterns": [".*\\.newdomain\\.com$"]}' \
localhost:5354 api.v1.NameserverSwitcherService/UpdateRequestPatterns
# Get statistics
grpcurl -plaintext localhost:5354 api.v1.NameserverSwitcherService/GetStats| Metric | Type | Description |
|---|---|---|
| Counter | Total DNS requests by protocol and type |
| Histogram | Request processing duration by resolver |
| Counter | Resolver usage count |
| Counter | Request pattern match count |
| Counter | CNAME pattern match count |
| Counter | Error count by type |
| Gauge | Current active connections |
| Counter | DNS response codes |
For more detailed documentation, see the Documentation folder: