Why Resolver Configuration Matters
The NGINX resolver directive specifies DNS servers for resolving hostnames in proxy_pass, upstream, and other directives. Using external or untrusted DNS servers can allow attackers to redirect your traffic.
DNS Poisoning Attack
An attacker who can poison DNS responses can:
- Redirect proxy traffic - Send requests to malicious servers
- Steal credentials - Intercept authentication requests
- Inject malicious content - Modify responses to users
- Access internal services - DNS rebinding attacks
When NGINX Needs a Resolver
NGINX requires a resolver when hostnames need to be resolved at runtime:
- Dynamic proxy_pass - Using variables in the destination
- OCSP Stapling - Fetching certificate status
- Upstream with variables - Dynamic backend selection
- SSL certificate verification - Resolving OCSP responders
# These require a resolver:
proxy_pass http://$backend;
proxy_pass https://api.$region.example.com;
# This does NOT need a resolver (resolved at startup):
proxy_pass http://static-backend.example.com;
Vulnerable Patterns
Using Public DNS Servers
# External DNS servers can be poisoned
resolver 8.8.8.8 8.8.4.4;
location / {
proxy_pass http://$backend_host;
}
# An attacker could poison 8.8.8.8's response
# to redirect $backend_host to their server
Using ISP DNS
# ISP DNS is often insecure and can be intercepted
resolver 192.168.1.1;
# Many ISPs don't implement DNSSEC
# Man-in-the-middle attacks are easier
No Validation
# Missing valid parameter allows stale/poisoned records
resolver 8.8.8.8;
# Without valid=Xs, cached records may persist too long
Secure Configuration
Use Local/Internal DNS
# Use your internal DNS infrastructure
resolver 127.0.0.1 valid=30s;
# Or your organization's internal DNS servers
resolver 10.0.0.53 10.0.0.54 valid=30s;
Use systemd-resolved (Local)
# systemd-resolved provides local caching
resolver 127.0.0.53 valid=30s;
# This uses the system's configured DNS with validation
Use Kubernetes DNS (In Clusters)
# In Kubernetes, use the cluster DNS
resolver kube-dns.kube-system.svc.cluster.local valid=30s;
# Or the CoreDNS service IP
resolver 10.96.0.10 valid=30s;
Configure Validation
# Always set valid parameter
resolver 127.0.0.1 valid=30s ipv6=off;
# valid=30s - Re-resolve every 30 seconds
# ipv6=off - Disable if not using IPv6 (reduces attack surface)
Best Practices
1. Avoid Dynamic Resolution When Possible
# Instead of dynamic resolution
# BAD:
set $backend "api.internal.example.com";
proxy_pass http://$backend;
# GOOD: Use upstream blocks (resolved at startup)
upstream api_backend {
server api.internal.example.com:80;
}
location / {
proxy_pass http://api_backend;
}
2. Use IP Addresses for Critical Backends
# Direct IP avoids DNS entirely
upstream auth_backend {
server 10.0.1.100:8080;
server 10.0.1.101:8080;
}
# No DNS resolution needed = no DNS attacks possible
3. Short TTL for Dynamic Backends
# Short valid time reduces poisoning window
resolver 127.0.0.1 valid=10s;
# Balance between security and performance
# Too short = high DNS load
# Too long = stale records / longer poisoning window
4. Use Multiple Resolvers
# Multiple resolvers provide redundancy
resolver 10.0.0.53 10.0.0.54 valid=30s;
# NGINX will round-robin between them
Cloud Environments: In AWS, use the VPC DNS (169.254.169.253). In GCP, use the metadata server DNS. These are trusted internal resolvers.
OCSP Stapling Resolver
OCSP stapling requires a resolver to fetch certificate status. This is a common place where external resolvers get configured.
# Safe OCSP configuration
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.pem;
# Use internal resolver for OCSP
resolver 127.0.0.1 valid=300s;
resolver_timeout 5s;
Detecting with Gixy
Gixy detects external resolver usage:
$ gixy /etc/nginx/nginx.conf
==================== Results ====================
[resolver_external] Do not use external nameservers for "resolver".
Severity: HIGH
Description: External DNS servers allow DNS poisoning attacks
to redirect traffic to malicious servers.
Reason: Using external resolver "8.8.8.8".
Pseudo config:
http {
resolver 8.8.8.8;
}
File: /etc/nginx/nginx.conf
Line: 15
==================== Summary ====================
Total issues: 1 (High: 1)
Static vs Dynamic Resolution
| Configuration | Resolution Time | Needs Resolver? |
|---|---|---|
proxy_pass http://backend.local; |
Startup | No |
proxy_pass http://10.0.0.1; |
Never | No |
upstream { server backend.local; } |
Startup | No |
proxy_pass http://$variable; |
Runtime | Yes |
ssl_stapling on; |
Runtime | Yes |
Security Checklist
- Never use public DNS (8.8.8.8, 1.1.1.1) for resolver
- Use local or internal DNS servers
- Always set the
validparameter - Prefer static hostnames or IPs over dynamic resolution
- Use upstream blocks instead of variables when possible
- Consider
ipv6=offif not using IPv6 - Run Gixy to detect external resolver usage