Skip to content

I noticed that many people can be confused about how nginx proxy works, so this repository was emerged

Notifications You must be signed in to change notification settings

clrech/nginx-proxy-pitfalls

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 

Repository files navigation

nginx proxy_pass resolver pitfalls

If you're using proxy_pass and your endpoint's IPs can vary in time, please read it to avoid misunderstandings about how nginx works.

location /api/ {
    proxy_pass http://api.com/;
}

In this case nginx will resolve api.com only once at nginx startup. You should keep in mind some cases when your endpoint can be resolved to the any IP, e.g. you're using load balancer which doing magic failover via DNS mapping. If api.com will point to another IP your proxying will fail in the example above.

You can check official nginx docs and find resolver directive.

location /api/ {
    resolver 8.8.8.8;
    proxy_pass https://api.com/;
}

No, it will not work. Even this will not work:

location /api/ {
    resolver 8.8.8.8 valid=1s;
    proxy_pass https://api.com/;
}

It's because of nginx doesn't respect resolver directive in this case. It will resolve api.com only at startup by system resolver (/etc/resolv.com) anyway, even if TTL of api.com is 1s.

You can google a bit and find that nginx only tries to resolve proxy endpoint if it uses variables. Also official doc for proxy_pass notices this too. Hmmm.

Let's try:

location /api/ {
    set $endpoint api.com;
    resolver 8.8.8.8 valid=1s;
    proxy_pass https://$endpoint/;
}

Works as expected. These configurations will works:

set $endpoint api.com;
location /api/ {
    resolver 8.8.8.8 valid=60s;
    proxy_pass https://$endpoint/;
}
location ~ ^/(?<dest_proxy>[\w-]+)(/(?<path_proxy>.*))? {
    resolver 8.8.8.8 ipv6=off valid=60s;
    proxy_pass https://${dest_proxy}.example.com${path_proxy}$is_args$args;
}

Notice that without resolver directive in these cases nginx will start, but will fail with 502 at runtime, because "no resolver defined to resolve".

Imagine configuration:

location /api_version/ {
    proxy_pass https://api.com/stats/dev1/;
}

location /api/ {
    set $endpoint api.com;
    resolver 8.8.8.8 valid=60s;
    proxy_pass https://$endpoint/;
}

In this case nginx will resolve api.com once at startup with system resolver and then never do re-resolve.

Use variables everywhere to make it work as expected:

location /api_version/ {
    set $endpoint api.com;
    proxy_pass https://$endpoint/stats/dev1/;
}

location /api/ {
    set $endpoint api.com;
    resolver 8.8.8.8 valid=60s;
    proxy_pass https://$endpoint/;
}

If you're using nginx plus, you can use resolve parameter, check out docs. I assumes it will be efficient, because documentation says "monitors", while solutions listed above will query DNS on request. But if you're using open source nginx, no honey is available for you. No money - no honey.

But there is interesting behaviour. Imagine:

server {
  listen      80;
  server_name fillo.me;

  location = /test_version/ {
     proxy_pass https://test.example.com/stats/dev1/;
  }

  location ~ ^/(?<dest_proxy>[\w-]+)(/(?<path_proxy>.*))? {
      resolver 8.8.8.8 valid=60s;
      proxy_pass https://${dest_proxy}.example.com${path_proxy}$is_args$args;
  }
}

This configuration proxies http://fillo.me/[name]/[something]/[else]/ to the https://[name].example.com/[something]/[else]/. Also it proxites http://fillo.me/test_version/ to the https://test.example.com/stats/dev1/.

If you open http://fillo.me/test_vesrion then no resolve will be done, because of nginx resolved it at startup. If you open http://fillo.me/test/version then NO resolve will be done, because of nginx resolved it at startup. If you open http://fillo.me/test_xxx/version then it will work as expected.

But with:

If you open http://fillo.me/test_vesrion then no resolve will be done, because of nginx resolved it at startup. If you open http://fillo.me/test/version then it will work as expected. If you open http://fillo.me/test_xxx/version then it will work as expected.

So very interesting this can be done with upstream:

upstream api {
  server api.com:443;
}

server {
    listen 80;
    server_name fillo.me;

    location /api-with-resolve/ {
       set $endpoint api.com;
       resolver 8.8.8.8 valid=1s;
       proxy_pass https://$endpoint/;
    }

    location /api-without-resolve/ {
       proxy_pass https://api/;
       proxy_set_header Host api.com;
    }
}

TODO (anycast) TODO (http) TODO (upstream) write about resolve

About

I noticed that many people can be confused about how nginx proxy works, so this repository was emerged

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published