Single-IP Multiple-Machine hosting using Apache and BIND =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2002/01/09 Written by Daniel Lucq 2002/01/17 Some modifications regarding access control and authentication by Daniel Lucq 0. Introduction Suppose you have a single routable IP address, and you want to host some web-sites via this address. This problem is readily solved using the virtual domains feature of the Apache web-server, assuming that all sites can be run from the same machine. However, assume now that this is not the case. E.g. some sites need to be run on their own machine for security reasons, or some sites need to run on a different platform. The remainder of this document will provide some details on how to solve this problem using the Apache web-server. This document is focussed on the OpenBSD platform, but should be easily adaptable to other, comparable UNIX-alikes or UNIXes. I assume you have a reasonable knowledge concerning the configuration of httpd, BIND, and OpenBSD in general. 1. Situation To recap, you have a single, routable IP address. This IP address is assigned to your firewall (which we assume is an OpenBSD 3.0 machine), and behind this firewall there is an internal network (with non-routable IP addresses) on which several web-servers reside. You want to provide access to these web-servers from the single, external IP address you have. +---(iIP1)- Server: www.domain1.com | ---(eIP)- Firewall -(iIP0)---+---(iIP2)- Server: www.domain2.com . . +---(iIPN)- Server: www.domainN.com Your firewall has external IP address ``eIP'' and internal address ``iIP0''. You have N machines on the internal network with addresses ``iIP`'' through ``iIPN''. Each of these machines is running a web-server hosting one or more domains; in the example only one domain is hosted on each server, but nothing prevents you from hosting more than one domain on a server using virtual domains. You could for instance have two servers: one for hosting your UNIX-based sites, and one for hosting your Windows- based sites. The firewall could be doing NAT for the internal network, but this is not required for the technique outlined below (in fact, it is more secure not to do NAT, since the internal machines will not be able to make direct connections to the Internet). However, you cannot use the pf(4) rdr feature to redirect port eIP:80 to the correct server (since this would allow only one server to be accessible from the Internet). 2. External configuration Externally, the name server for the different domains you are hosting (domain1.com through domainN.com) should point all these domains to your external IP address. I.e., www.domain1.com through www.domainN.com should all resolve to ``eIP'' when looked up from outside the firewall. Note that with the technique described in this document it is difficult to run this externally-accessible name server on the firewall (see below). Correctly setting up the external DNS is all that needs to be done here. 3. Web server configuration The web servers on your internal network should all be configured as if each of them where connected to the Internet using a routable IP address. Simply use the internal addresses intead. 4. Firewall configuration This is the gimmick you are waiting for ;-). 1. First of all, correctly set up firewalling rules on this machine. You will need to let in port 80 at least, of course. Also make sure that this machine correctly does NAT for your internal network. No rdr's need to be set-up. 2. Secondly, you will be running httpd (Apache) on the firewall. See below for details of this configuration. This httpd will act as transparent proxy, and redirect incoming requests to the correct internal web server (yes, you could also do some primitive load- balancing using this system). 3. Lastly, you will be also running a DNS on the firewall. This DNS should be accessible only from the internal network, and will carry ``fake'' zones for each of the domains you are hosting. See below for the configuration details. The firewall should also be instructed to use its own DNS for resolving addresses (i.e., /etc/resolv.conf should contain: "nameserver 127.0.0.1"). That's it. Incoming requests will be received by the httpd on the firewall. This web server will use mod_proxy to transparently pass the requests to the correct internal machines. The DNS on the firewall will be used to determine to which server the requests need to be sent. This technique is being used by us on production machines, and works as expected. Note that the internal servers will not be able to determine from what IP address the requests are originating, since all they see is requests coming from the firewall. This means that web statistics would need to be generated based on the httpd logs on the firewall (which is not a problem), but also that access control based on source IP address needs to be handled by the httpd on the firewall as well. This is also not a problem (see below). The next sections provide details on how the httpd (Apache) and the named (BIND) should be configured. 5. named (BIND) setup The named setup is pretty straight-forward. As I said above, this named should only be accessible from the internal network, since it will resolve internal addresses. For each domain you host, make sure that this DNS resolves the domain to the internal IP address of the server on which it is hosted. For example, for domain1.com, add the following to /var/named/named.boot: primary domain1.com domain1.com.zone and make sure that /var/named/namedb/domain1.com.zone contains the following: www IN A iIP1 where ``iIP1'' is the internal IP address of the server on which www.domain1.com is hosted. The net result should be that if you do "nslookup www.domain1.com" on the firewall, it should resolve to ``iIP1'' and not ``eIP''. 6. httpd (Apache) setup I assume you start from the Apache configuration as it is shipped with OpenBSD 3.0. 1. First, make sure that httpd will load mod_proxy. For this you have to add the following line to httpd.conf: LoadModule proxy_module /usr/lib/apache/modules/libproxy.so 2. Next, enable proxy server support. httpd.conf should minimally contain the following: ProxyRequests on ProxyVia Off Note that httpd.conf already contains these statements, albeit commented-out. Simply uncomment them. I switch off handling of "Via:" headers because I don't want any nasty surprises there (e.g. leakage of information concerning the internal network). Also, I don't want any caching going on, so I don't enable that. 3. Now you have to control the access to the transparent proxy. First of all, in the "" section, add the following: Order deny,allow Deny from all This disallows access to the proxy from everywhere. You really need this, to prevent you from being an open web proxy for everybody on the Internet! Below this, you will explicitly grant access for each domain you are hosting. For each domain, add a block like the following: Order deny,allow Allow from all This grants access from everywhere to transparent proxying of www.domainX.com. Note that you can also insert the regular access control mechanisms (e.g. you can only allow access from certain source IP ranges). Password stuff goes somewhere else, though (see below). 4. Enable the virtual domains feature. Add the line NameVirtualHost eIP where ``eIP'' is your external IP address. 5. Now, for each of the hosted domains, add a VirtualHost section like the following: ServerName www.domainX.com ProxyPass / http://www.domainX.com/ ProxyPassReverse / http://www.domainX.com/ CustomLog logs/www.domainX.com-access_log combined Note: the CustomLog line is optional, but doing it like this allows for easier logging on a per-domain basis. This block essentially tells mod_proxy to redirect all requests for www.domainX.com to www.domainX.com. If you want to add password protection to this host, you can either add it on the actual web server, or you can do it here by inserting the following block in the relevant VirtualHost section: AuthType Basic AuthName "your-realm" AuthUserFile your_password_file Require valid-user Other authentication options are possible, of course. That's it. This may seem weird (especially step 5 where www.domainX.com is proxied to www.domainX.com), but remember the DNS set-up on the firewall... When a request for e.g. www.domain1.com/index.html is received by httpd, the daemon treats this request according to the rules in the VirtualHost section for ``eIP'' and with ServerName www.domain1.com. There it is told that it should fetch /index.html from http://www.domain1.com/ (the "ProxyPass" statement). httpd performs a lookup of www.domain1.com, which resolves to ``iIP1'' on the firewall! Hence the request is sent to the internal web-server. You may wonder why we have to spoof the zones with the internal IP addresses, instead of just using different names, e.g. use VirtualHosts like the following: ServerName www.domain1.com ProxyPass / http://www.domain1.com.internal/ ProxyPassReverse / http://www.domain1.com.internal/ The reason is that mod_proxy uses the domain name in the ProxyPass statement to create a new "Host:" header. So if you used to VirtualHost block above, you would have to configure your internal server to server www.domain1.com.internal instead of www.domain1.com, which would reduce the transparency of the entire set-up (and which could also introduce problems relating to cookies, although a patch to mod_proxy cookies can be found here: http://www.wede.de/sw/mod_proxy/). 7. Epilogue If any of the above confuses you, read the appropriate documentation for the different components (this involves man-pages on your system, as well as web-sites like http://www.apache.org/). As I said in the introduction, the set-up described above is in use on our systems and works correctly.