96 lines
3.0 KiB
Markdown
96 lines
3.0 KiB
Markdown
# Nginx Runtime DNS Resolution
|
|
|
|
## Problem
|
|
|
|
nginx resolves all `proxy_pass` hostnames at **startup time**. If DNS isn't available when nginx starts (common at boot, especially with systemd-resolved still initializing), it fails with:
|
|
|
|
```
|
|
host not found in upstream "api.deepseek.com" in /etc/nginx/sites-enabled/shopproquote:7
|
|
```
|
|
|
|
**One bad upstream takes down every site.** All services become unreachable until someone manually restarts nginx.
|
|
|
|
## Solution: Runtime DNS via variable
|
|
|
|
When `proxy_pass` uses a **variable**, nginx resolves at **request time** instead of startup. Three directives needed:
|
|
|
|
```nginx
|
|
location /deepseek/ {
|
|
resolver 127.0.0.53 valid=30s; # DNS server + cache TTL
|
|
set $deepseek_upstream api.deepseek.com; # variable holds hostname
|
|
proxy_pass https://$deepseek_upstream/; # resolves at request time
|
|
# ... rest of location block unchanged
|
|
}
|
|
```
|
|
|
|
**Key points:**
|
|
|
|
- `resolver` — use the local DNS stub (`127.0.0.53` on systemd-resolved systems, `8.8.8.8` as fallback). `valid=30s` caches resolved IPs for 30 seconds.
|
|
- `set $upstream_name` — the variable name is arbitrary but must be unique per location.
|
|
- `proxy_pass https://$upstream_name/` — the trailing `/` is critical (strips the location prefix from the proxied URI, same behavior as static `proxy_pass`).
|
|
|
|
## Worked example: ShopProQuote DeepSeek proxy
|
|
|
|
**Before** (static — fails at boot if DNS down):
|
|
|
|
```nginx
|
|
location /deepseek/ {
|
|
proxy_pass https://api.deepseek.com/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host api.deepseek.com;
|
|
proxy_set_header Authorization "Bearer sk-f73...b026";
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_ssl_server_name on;
|
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
|
proxy_read_timeout 120;
|
|
}
|
|
```
|
|
|
|
**After** (runtime DNS — safe at boot):
|
|
|
|
```nginx
|
|
location /deepseek/ {
|
|
resolver 127.0.0.53 valid=30s;
|
|
set $deepseek_upstream api.deepseek.com;
|
|
proxy_pass https://$deepseek_upstream/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host api.deepseek.com;
|
|
proxy_set_header Authorization "Bearer sk-f73...b026";
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_ssl_server_name on;
|
|
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
|
proxy_read_timeout 120;
|
|
}
|
|
```
|
|
|
|
Test: `nginx -t && systemctl reload nginx`
|
|
|
|
## When to apply
|
|
|
|
This pattern is needed for **any** `proxy_pass` to an external hostname (not a Tailscale IP or localhost). External hostnames that could be unresolvable at boot:
|
|
|
|
- API endpoints (`api.deepseek.com`, `api.openai.com`)
|
|
- CDN origins
|
|
- Third-party service backends
|
|
|
|
**NOT needed** for:
|
|
- Tailscale IPs (`http://100.93.253.36:11434/`) — always resolvable, no DNS involved
|
|
- `localhost` / `127.0.0.1`
|
|
- IP literals of any kind
|
|
|
|
## Diagnosing at runtime
|
|
|
|
```bash
|
|
# Is nginx running?
|
|
systemctl status nginx
|
|
|
|
# What error killed it?
|
|
journalctl -u nginx --since "5 min ago" --no-pager | grep -i "host not found"
|
|
|
|
# Does DNS work now?
|
|
nslookup api.deepseek.com
|
|
|
|
# If DNS works now but nginx is down, just restart:
|
|
systemctl restart nginx
|
|
```
|