Transparent dynamic reverse proxy with nginx

A while back I wrote about using Apache as a dynamic reverse proxy. Any­one who has done even min­i­mal research into web servers knows that Apache is the swiss army knife. It trys to be every­thing for every­one, and like a swiss army knife may not be as good as a more refined too at least as far as effi­ciency is concerned.

Here is the sit­u­a­tion. You have a sin­gle pin­hole into your pri­vate net­work. You have a sin­gle ip at your gate­way. You want to serve mul­ti­ple web­sites on your lan that may be run­ning on mul­ti­ple phys­i­cal servers. Rather than open­ing up mul­ti­ple ports and pin­holling to all the dif­fer­ent spots you want to serve, or get­ting more exter­nal ips and doing 1to1 NAT you can use a reverse proxy to be your sin­gle entrance point. The reverse proxy will fetch the con­tent from the back­end server and serve it up.

nginx is a HTTP server and mail proxy server. One of its fea­tures basic HTTP fea­tures is accel­er­ated reverse proxying.

nginx should be avail­able through your pack­age man­ager so just apti­tude (or what­ever your pack­age man­ager is yum, emerge, pac­man) install it.

The con­fig file paths shown are Debian spe­cific but the con­fig itself should work on any distro.

Edit /etc/nginx/sites-available/default and make it look like this

server {
     listen  :80;
     server_name  _;
     access_log  /var/log/nginx/proxy.access.log;

     location / {
     resolver        127.0.0.1;
     proxy_pass      http://$host$uri;
     proxy_redirect off;
     proxy_set_header        Host    $host;
     proxy_set_header        X-Real-IP $remote_addr;
     proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
     }

     error_page   500 502 503 504  /50x.html;
     location = /50x.html {
          root   /var/www/nginx-default;
     }
}

So this con­fig causes nginx to lis­ten on all interfaces/ips. server_name _; matches on any­thing so essen­tially this is a catchall now. You can tail proxy.access.log in order to see the requests are they come in and are served.

The loca­tion sec­tion is where the actual prox­y­ing hap­pens. Since this is a dynamic con­fig­u­ra­tion you need to set a resolver where the requested names can be looked up (and over­rid­den for the local lan address). dns­masq reads is dns con­fig­u­ra­tion right out of /etc/hosts. It’s easy to install and con­fig­ure so I rec­comend using it. We will install and con­fig­ure it shortly but for now just leave resolver as 127.0.0.1. proxy_pass does the request­ing of the page we are prox­y­ing. Since this is a trans­par­ent dynamic proxy we just have it request the same thing that was requested of the proxy. proxy_redirect should be set to off since we are just pass­ing on the same request. We need to set a few head­ers for log­files on the back­end servers as well as mak­ing sure that Host is set to the request­ing host in case your using name based vir­tual hosts on your back­end servers. I have left the error page in the default con­fig (at least on debian its default). This pro­vides a nice error mes­sage in case your proxy is work­ing but one of the back­end servers is not. It just serves the index.html that is located in /var/www/nginx-default. Feel free to change that path to some­thing else, mod­ify the index.html or omit the error_page and error page loca­tion sec­tion all together as they aren’t needed for this to work.

Now we need to get that local resolver (dns­masq) installed so we can take our reverse proxy for a spin. Go ahead and apti­tude (or what­ever) install dnsmasq.

At least on debian dns­masq comes out want­ing to serve dhcp. You prob­a­bly do not want this behav­ior. There is also the ques­tion of need­ing access to these same ser­vices by the same name on your LAN. If you need this you might need to do some slight adjust­ing of your dns. I might rec­comend point­ing your main dns to this dns­mask proxy or point­ing all of your clients at this dns­masq install since it will look up other requested names other than those in /etc/hosts. For this exam­ple I will assume you will be want­ing to access these same web ser­vices inter­nally with the same names and bypass the proxy. So I will assume you have either changed your pri­mary dns cacher/resolver (think soho router or what­not) to the address of the proxy server (since its run­ning dns­masq as well), or set all of your clients to point directly at the proxy server for dns. We need to edit the dns­masq con­fig to dis­able dhcp.

Edit /etc/dnsmasq.conf and add no-dhcp-interface=ethx. Do that for every inter­face on your sys­tem so that your not acci­den­tally serv­ing out dhcp to any­one. If somone has a more generic way to dis­able dhcp in dns­masq with­out spec­i­fy­ing each inter­face I would love to know but from read­ing the man this was the only way I could find. So you may have some­thing like the fol­low­ing in you /etc/dnsmasq.conf.

no-dhcp-interface=eth0
no-dhcp-interface=eth1

After mak­ing the change you should be ready to add entries to the proxy servers /etc/hosts for dns­masq to use and then test your reverse proxy.

Lets say you have www.test.com served off of a machine with the ip 192.168.1.2 and you have tickets.office.test.com served off of 192.168.1.3. Lets also assume that your world route­able ip is 123.123.123.123. You will need to make sure that your author­i­ta­tive dns (the real one that servs for test.com has A records for both www.test.com and tickets.office.test.com point­ing to 123.123.123.123. Now on the machine run­ning dns­masq (in this exam­ple also your proxy server) add the fol­low­ing entries to /etc/hosts.

192.168.1.2 www.test.com
192.168.1.3 tickets.office.test.com

Go ahead and restart dns­masq (from mak­ing changes to the con­fig, sub­se­quent changes to /etc/hosts should not require dns­masq restart to pick up changes) and nginx.

Now tail your proxy.access.log file and start mak­ing requests to www.test.com and tickets.office.test.com from both the inside of your lan as well as out­side against your world ip. It should all mag­i­cally serve up the same content.

This type of con­fig can be use­ful in many sit­u­a­tions. You have a small office and bud­get that reflects that not being able to afford mul­ti­ple ips but need­ing to pro­vide web ser­vices behind the fire­wall. You work in a large cor­po­ra­tion where some­one else man­ages the fire­wall and you would like to bring up more web ser­vices with­out wait­ing for the other per­son to make the nec­es­sary changes to the firewall.

One of the other ben­e­fits this pro­vides is being rel­a­tively self doc­u­ment­ing  with regard to what web ser­vices you host behind the fire­wall. (you should be able to see all of them in /etc/hosts since you have to over­ride the dns)

10 Comments

  • Jerad Windows XP Google Chrome 2.0.172.33 wrote:

    Heyo Nick, this is the same fel­low you helped by point­ing me here on linuxquestions.org a week or so ago.

    I’ve got this sorta-kinda work­ing, but I’ve also caught a snag. It’s redi­rect­ing to the cor­rect site at the moment, but it’s break­ing my css and func­tion­al­ity on a phpbb forum when­ever I access it out­side the local net­work (it works fine inside the local network).

    You can see what I’m talk­ing about at forum.iseekthegrail.com/forum/

    any ideas?

    (I also posted this ques­tion at lin­ux­ques­tions under the “soft­ware” ques­tions area.

  • med Mac OS X Firefox 3.0.13 wrote:

    hi,
    great tip, isn’t it pos­si­ble to add authet­i­ca­tion as it can be done with apache ?

    great thanks

    med

  • Hello i’m look­ing for an expe­ri­enced Sys­tem Admin­is­tra­tor to set-up Nginx on my VPS account (very traf­ficked word­press blog.) since i’m expe­ri­enc­ing prob­lems with Apache
    Con­tact me if you are inter­ested about the small job at

    sef­fig­noz at gmail d o t com

  • Hi, i want to trans­fer my web­site in a VPS but don’t have any experiece with this server. sould i choose a vps man­aged ser­vice of unman­aged?? is there any one how ca help me? i can pay you for your time. thank you.

  • Thanks for your very good tuto­r­ial.
    Just tell you some prob­lems I got.
    It works per­fectly if I replace the direc­tive
    proxy_pass http://$host$uri
    by
    proxy_pass http://10.0.0.100; #(IP of the orig­i­nal web server )
    But if I do so, there’s no dynamic any more.

    if I use
    proxy_pass http://$host$uri
    and add some­thing like
    10.0.0.100 example.com
    in the /etc/hosts file
    errors occur.

    I use Ubuntu 10.04 with NginX 0.8.54
    errors got:

    2011/04/05 14:41:21 [alert] 14219#0: worker process 14274 exited on sig­nal 11
    2011/04/05 14:41:22 [alert] 14219#0: worker process 14275 exited on sig­nal 11
    2011/04/05 14:41:25 [alert] 14219#0: worker process 14276 exited on sig­nal 11

  • Thats inter­est­ing, looks like pos­si­bly a bug in Nginx. It’s been so long since I have used a setup like this. You could try a sim­i­lar con­fig­u­ra­tion with apache. http://www.cmdln.org/2008/06/10/dynamic-reverse-proxy-with-apache-mod_rewrite-and-mod_proxy/

  • Hello,
    Your con­fig­u­ra­tion works per­fectly except from one thing. That is the trail­ing slash prob­lem.
    Detail: if url for folder not include the trail­ing slash “/” it will be redi­rect to the local­host auto­mat­i­cally.
    I found many post about that prob­lem, but no luck.
    Could you please fig­ure it out?

    Exam­ple: I have domain : example.com with folder images
    If user enters “http://example.com/images/”, it works well.
    But if user enter: “http://example.com/images”, the site get redi­rected to his/her IP address auto­mat­i­cally. I do not know the rea­son why and how to fix it.
    If you can do me a favour, thank you very much in advance.

    I try to use rewrite in nginx like
    rewrite ^([^.]*[^/])$ $1/ per­ma­nent;
    or use:
    server_name_in_redirect off;

    Noth­ing works at all.

  • It has been a long time since I have used a con­fig­u­ra­tion like this.

    Try fid­dling around with the regex match. Maybe ^(.*)$

  • I have prob­lem with this model.
    My VPS become free proxy for other to access. Do you know how to block query to exter­nal host. I mean only the host listed in /etc/hosts file canbe served, oth­ers should be ignored.
    I have tried to force dns­masq to use only local host file; how­erver, still not succeeded.

  • duckpond Windows other version Firefox 27.0 wrote:

    I used this approach, but added line for ref­erer:
    proxy_set_header referer “”;

    unfor­tu­nately, this line is ignored, now mat­ter what value i sup­ply for the ref­erer, my server always sees the true referer.

Leave a Reply

Your email is never shared.Required fields are marked *

To submit your comment, click the image below where it asks you to...
Clickcha - The One-Click Captcha