Getting pihole to work with nginx, apache and bind all on the same machine


I have one Debian linux box in my house. This box does everything. NFS, SMB, NTP, DNS, Programs my X10 CM11A for exterior lighting schedules, runs my irrigation automation, serves up a couple websites and a piwigo site and is also where the logitech media server runs for my squeeze boxes. I wanted to put this out there for anyone else trying to get pihole (actually the annoying package called dnsmasq) to work on a non dedicated box/VM and play nice with other services.

Before Pihole: The box sits behind a router in a private net The router has a static IP and I do reverse NAT to externally serve web pages form my linux host. Fronting those web pages for SSL termination and HTTP to HTTPS redirects is Nginx (I love Nginx BTW). Behind NGinx is apache and a few other things that have web pages (subsonic, logitech media server) not served by apache but redirected by Nginx. Lastly my router does DHCP. Yes I could have had dnsmasq do it but, I liked the fact I could change the dns entries in the DHCP scope and flip between bind9 and pihole with ease for all clients on my net

So reading the docs they say to alias an IP on an interface. I got so annoyed by dnsmasq trying to help me (i.e. no matter what I did it opened a listener on everything) that I decided to just use another interface. My linux box had four actual H/W nic’s in it and I was only using one. So the first thing I did was to bring up that interface with a IP in the same net (and connect a cable).

The next thing I did was disable IPv6. Not needed but, I wanted to simplify my life and not worry about that. Then I sorted all of the ports my current services were listening on. Nginx listened on 80 and 443, Apache was already set to port 81 as Nginx proxied everything to it. All the other webpages were on odd ports. I went through each service (with the exception of Bind9 which I’ll get too later on) and made sure they were only listening to the .20 interface on eth1 on those ports. A handy dandy bash one liner to find out whats on what is “lsof -i -n -P | grep LISTEN”

I run bind9 internally in my private network for one real reason. Because I do reverse NAT I have my domain names hosted externally at a DNS provider pointing to my routers static IP. Internally I have to run bind9 so when my wife copies and pastes a link to an internal site it works fine externally. So what I want is clients internally to hit pihole first, pihole then goes to my Bind9 dns server, which then goes out to the google DNS server for further look ups (and caches stuff). So I made sure bind9 listened on the eth1 .20 IP and the loopback IP ( I wanted the loopback because I wanted pihole to send queries over the lookback to bind9. Which seemed cleaner than sending packets out one interface across the switch and back in another interface and then the response doing that in reverse.

Now to get this all to work I had to find and parse the dnsmask man page. Because if you tell dnsmasq to listen on an interface i.e. eth2 it will automatically listen on the loopback address even if you exclude it in the dnsmasq config. Gee thanks dnsmasq for being so helpful. The trick is to tell it to listen on an IP address only. In my case on eth1. Heres the relevant bit

a, --listen-address=
Listen on the given IP address(es). Both --interface and --listen-address options may be given, in which case the set of both interfaces and addresses is used. Note that if no --interface option is given, but --listen-address is, dnsmasq will not automatically listen on the loopback interface. To achieve this, its IP address,, must be explicitly given as a --listen-address option.

I also put the bind-interfaces option in there

-z, --bind-interfaces
On systems which support it, dnsmasq binds the wildcard address, even when it is listening on only some interfaces. It then discards requests that it shouldn’t reply to. This has the advantage of working even when interfaces come and go and change address. This option forces dnsmasq to really bind only the interfaces it is listening on. About the only time when this is useful is when running another nameserver (or another instance of dnsmasq) on the same machine. Setting this option also enables multiple instances of dnsmasq which provide DHCP service to run in the same machine.

Note do not just change it in the /etc/dnsmasq/dnsmask.conf but, also in the specific dnsmask.d/pihole.conf file as well. (that also was annoying).

Another Note: If your on Debian there was a bug where the bind9 init scripts were not looking at it’s /etc/default/bind9 options file which really didn’t help me as it too kept trying to wildcard listen

Lastly if do not be thrown off by bind9 listening to port 953 on It’s not serving DNS requests, it uses that for a control channel

Pihole specific stuff:
I commented out the lighttpd.conf line for IPv6. It’s not mandatory I just didn’t want to deal with it. Also I configured a pihole custom DNS entry to look at for my inhouse bind9 DNS server.

So on eth1 I had
Nginx port 80 —redirects to 443 which terminates SSL then forwards to port 81
bind9 port 53
apache port 81 serves up webpages

on eth2
dnsmask (pihole) port 53
lighttpd port 80 (pihole admin)

Bind9 port 53

The last thing I did was add an DNS entry with my external DNS provide for and expand my SAN SSL Cert from Lets Encrypt to include a host and install that cert into Nginx. I then made a conf block in Nginx that terminates an SSL tunnel and forwards web traffic to the pihole admin webserver running on eth2 So now from anywhere I can get an SSL tunnel to the pihole admin page (it has a pwd on it).

I hope this helps anyone else as I banged my head against this for a week after work every night till I got it going


Do you know if putting bind9 in an interface and dnsmasq listening in a virtual one (for example eth0:1) would do the trick?
It is possible to change the dnsmasq port it is listening in?


You know i believe I tried that but, ran into problems. I cant remember if it was an actual problem with virtual ints or something else that made me think there was a prob woth that. Incidentally I filed a bug with pihole and I think they fixed it so it only binds to the int you specify now