Reference
System Architecture
A deep technical dive into the NexoralDNS internal design, the 7-layer query engine, and its performance optimizations.
System overview
▸Sub-5ms query response with Redis caching
▸Domain rerouting (e.g. google.com → ankan.site)
▸Domain blocking for ads, malware and custom rules
▸User plan management with feature limits
▸Analytics & logging for query monitoring
▸Multi-client support with client-specific rules
7-layer query processing
Every query flows through the same pipeline regardless of transport (UDP, TCP, TLS).
Redis Cache — 0.5–1ms
Check Redis for previously resolved queries. Sub-millisecond responses for 80%+ of traffic.
Service Status — 0.5ms
Verify DNS service is active. If disabled, return NXDOMAIN immediately.
Block List — 0.5ms
Check global or client-specific blocks. Blocked domains return NXDOMAIN and are logged.
Rewrite Rules — 1ms
Check domain rerouting rules, resolve the target domain, return its IP.
DNS Record Lookup — 2ms
Query MongoDB for custom records like myapp.local.
User Plan Validation — 0.5ms
For custom domains, verify the subscription plan is active. Expired plans return NXDOMAIN.
Upstream DNS — 10–50ms
If no match, forward to 8.8.8.8 / 1.1.1.1 and cache the response.
Component breakdown
🖥️
Client Layer
Web UI (Next.js), mobile app, CLI client, and any device using the DNS server.
⚡
API Server (Fastify, :4000)
REST endpoints, controllers, services, and JWT authentication.
🌐
DNS Server (:53 UDP/TCP, :853 TLS)
A Go binary. server/udp.go, tcp.go and dot.go share the dnsio.Handler contract and dispatch into one rules pipeline.
🛒
Caching Layer (Redis)
Full responses, records, service status, rewrites, block lists and user plans.
🗄️
Database (MongoDB)
dns_records, dns_rewrites, dns_blocks, user_plans, dns_query_logs (30-day TTL), domains, service.
🔁
Global Forwarder
Upstream DNS resolution with single-flight inflight de-duplication.
Performance targets
Per-stage budgets the design aims for. These are goals — see the measured load test below for numbers that were actually recorded.
StageTarget
Redis Cache Hit0.5–1ms (80%+ hit rate)
Block Check0.5ms (Redis SET lookup)
Rewrite Check1ms (Redis + DB fallback)
DNS Record DB2–3ms (Redis + MongoDB)
Upstream DNS10–50ms (uncached only)
Total (Cached)🎯 <2ms
Total (Uncached)🎯 <5ms
Measured load test
dnsperf against the Go engine over UDP:53 — 49 domains, warm cache. Everything, including the load generator, ran on one machine.
Load shapeQPSAvg latencyLost
5 clients, 50 in flight12,7463.8ms0
8 threads, 2000 in flight10,396189ms95 (0.03%)
CPUAMD Ryzen 5 5500U — 6 cores / 12 threads, x86_64
RAM7.1 GiB
OSLinux 6.8 · Docker host networking · no container CPU limit
TransportUDP:53 over loopback — no NIC in the path
Co-locatedMongoDB, Redis, RabbitMQ and dnsperf itself
💡Why the gentler run wins
At 2000 in flight the load generator competes with the server for the same 12 threads, and the deep queue adds wait time Little’s Law predicts almost exactly (2000 ÷ 10,396 ≈ 192ms) — that run measures the queue, not the server. Because the generator is co-located, 12,746 is a floor rather than the engine’s ceiling.
Redis cache key structure
service:status → 'active' EX 60
dns:google.com → {"value":"1.2.3.4","ttl":300} EX 300
rewrite:google.com:ip → {"target":"ankan.site"} EX 300
block:global (SADD) → ads.google.com
plan:userId:507f… → {"status":"active"} EX 300Technology stack
DNS EngineGo (single process, SO_REUSEPORT listeners)
API / DHCPNode.js (TypeScript, strict, CommonJS)
DNS ServerNative UDP/TCP/TLS (net, crypto/tls)
API ServerFastify
DatabaseMongoDB (3-node replica set)
CacheRedis (master-replica)
Web UINext.js + React
DeploymentDocker + Docker Compose; PM2 for the Node services, the DNS binary runs from the entrypoint
Security
✓Redis password auth, localhost binding, TLS for remote
✓Per-IP query rate limiting and DDoS protection
✓Sanitized domain input prevents DNS amplification
✓JWT authentication with role-based access control