initial commit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user