Files
hermes-config/skills/vps-reverse-proxy/references/nginx-runtime-dns.md
T
2026-07-12 10:17:17 -04:00

3.0 KiB

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:

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):

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):

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

# 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