Articles in the Infrastructure category

  1. Improve Privacy by Running a DNS Server Without Forwarder

    Wed 25 March 2026

    Disclaimer: no AI was used to write this article.

    If you run a DNS server at home or for an organisation, it is likely that you configured a public DNS server as a forwarder1.

    This is not required!

    Imagine we want to resolve the address 'wikipedia.org'.

    If no forwarder is configured, a DNS server will behave like this:

    1. Query the root server3 for an IP address of the .org top-level domain authoritative DNS server
    2. Query the .org DNS server for an IP address of the wikipedia.org authoritative DNS server
    3. Query the wikipedia.org DNS server for the IP address of 'wikipedia.org'

    This is what the forwarder does when you query a domain that is not cached by said forwarder.

    A forwarder responds faster?!

    Yes, that's probably true, but I think it doesn't matter.

    As a DNS forwarder caches DNS lookups, it will respond directly with the appropriate IP address. Only a single DNS query is required.

    Without a forwarder, a domain lookup that is not cached, will require at least two or three DNS queries, the first time this domain is resolved. After this first set of queries, the domain is cached for the duration of the TTL.

    I've performed a few manual lookups with 'dig' for domains that aren't cached, and most responses are <50ms. Depending on where you live and the quality of the network connection (in terms of latency) it may or may not be noticable, that's up to you to explore.

    So what is the privacy angle?

    You are exposing privacy-sensitive information to Google or other large companies.

    If a DNS forwarder is configured, the forwarder will 'see' all your DNS queries and will be able to build a nice profile about you as a person or organisation.

    It's up to you if you care about this 'risk'.

    Running Pi-hole and BIND9 together

    Pi-hole only supports forwarders?

    Because Pi-hole requires a DNS forwarder to be configured, I'm also running BIND as the actual DNS server that performs the outbound DNS queries.

    Both Pi-hole and BIND want to listen on UDP port 53, which won't work2. In my case, I've configured BIND9 to listen on UDP port 5353 and configured Pi-hole to use 'BIND listen ip address#5353' as a forwarder.

    This setup means that I benefit from the ad blocking by Pi-hole and won't send all my DNS requests to Google.

    Help, my local DNS authoritative '.internal' zone isn't working anymore!

    I'm running a local DNS domain 'xyz.internal' for servers and services in my network. I've also configured my DHCP server to tell clients the search domain is 'xyz.internal', so when I issue ssh server01 this will automatically resolve to server01.xyz.internal.

    So to summarise, my BIND DNS server is both the DNS server that respons to client DNS lookups via Pi-hole for public domains, and it's also authoritative for the 'xyz.internal' domain.

    However, there's a problem with this setup.

    Pi-hole does not forward queries for the '.internal' domain because it considers upstream forwarding DNS servers as potentially hostile. Forwarding queries for '.internal' domains would expose potentially sensitive information.

    This behaviour can be circumvented with the 'Conditional forwarding' option within Pi-hole. This allows DNS clients contacting the Pi-hole server to still query '.internal' domains. This is an example configuration line:

    true,10.10.10.0/24,10.10.10.1#5353,xyz.internal

    In this example, Pi-hole will forward DNS queries for the xyz.internal domain towards 10.10.10.1#5353 and everything will work as expected.

    Q & A

    How does the DNS server know how to contact the .org root server?

    The DNS server is accompanied by a 'root zone file' containing the IP addresses for the root servers. As these IP addresses are not static, they need to be updated periodically. In Debian Linux (for example), this file is automatically updated through APT with the 'dns-root-data' package.


    1. Probably an address from Google, Cloudflare or OpenDNS. 

    2. It's possible to make Pi-hole and BIND listen on different interfaces, thus allowing both of them to use port 53 but that's another story and may not be that handy. 

    3. A root server is not a single physical server but a group of physical servers, but I consider this an implementation detail. 

    If you have any comments email me, see the About page for contact details.
  2. Sharing Group_var Variables Across Ansible Inventories

    Sat 20 December 2025

    The problem

    Some time ago I worked with a customer that used Ansible to deploy a multitude of applications across their individual TAP1 environments.

    Unfortunately the Ansible inventory was setup in such a way that group_vars variables for a specific application were manually duplicated across each TAP environment.

    Ansible Inventory Hierarchy Example of inventory folder hierarchy

    Changing or adding a group_vars variable meant updating the group_vars file for each TAP environment. Making changes to group_vars in this environment is a cognitive burden: there is a high risk that one of the environments is missed by mistake.

    The different TAP environments share the vast majority of all group_var variables, except for just a few variables that are environment-specific. There should be no reason to duplicate group_vars variables for each environment.

    The solution

    A shared folder symlinked into group_vars

    Within the application inventory folder, We first create a folder called 000_shared that will contain all group_vars YAML files with variables that are shared across all TAP environments.

    Next, we symlink this folder within each group_vars folder of each environment. Each environment also contains environment-specific configuration in one or more separate files.

    Ansible Inventory Hierarchy

    With this example, any change within webserver.yaml will be applied to all TAP environments of the 'Alpha' application.

    Sharing group_vars variables across applications

    We can apply this trick one level higher: we can create a shared folder called 000_shared_global containing settings that are shared across all application environments.

    Ansible Inventory Hierarchy

    In this example, we could change the DNS configuration for all environments by changing the settings in one, single file. But the global shared group_vars can be used to assure a (security) baseline between unrelated applications that do share similar technology (All Python or PHP applications).

    Evaluation

    Implementing this solution in an existing Ansible 'code base' can be a significant undertaking, but the long-term benefits seem worth it.

    • It reduces the amount of work required for changes
    • It reduces the risk of human error
    • It reduces the cognitive load

    The good news is that you can migrate to this new style of inventory at your own pace, there is no need for a big-bang deployment. Changes can be made to one environment at a time.

    Q & A

    Why the leading 000 zeros in shared folder names?

    Ansible parses group_vars YAML files according to their order within a directory. This means that the shared folders should be named with leading zeros, to assure that they are always parsed first.

    This allows you to override shared group_vars settings for a particular environment, because these environment-specific files will be parsed later and their variable settings will be applied.

    Just use one inventory for DTAP!

    You can create a single inventory for test, acceptance and production, and separate the environments with groups. A shared group contains shared settings and environment-specific settings are put in the group_vars of the specific group. Unfortunately there is a risk:

    Ansible documentation:

    If you need to manage multiple environments, consider defining only the hosts of a single environment in each inventory. This way, it is harder to, for example, accidentally change the state of nodes inside the “test” environment when you wanted to update some “staging” servers.

    Therefore, I would recommend against this approach.

    Changes impact production?

    Changing shared group_vars variables means that all environments are changed, including production. This requires proper code review and deployment processes that assures that said changes are only deployed to production when test and acceptance environments have been changed first.


    1. Development, Test, Acceptance and Production 

    2. When an inventory contains test, acceptance and prod environments it's easy to target all three environments by mistake. 

    Tagged as : Ansible
    If you have any comments email me, see the About page for contact details.

Page 2 / 2