Static IP with ifupdown#
A server other devices are supposed to reach needs an address that does not change. DHCP hands it whatever is free — possibly a different one after the router reboots, and every configuration pointing at the old one breaks. For a DNS server in particular that is not an option: its address is what every client has hardcoded.
A Debian install without a desktop manages networking through ifupdown and
/etc/network/interfaces. NetworkManager and systemd-networkd solve the same problem but
are not in play on a netinst system.
Find the interface name#
ip -br linklo UNKNOWN 00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
enp1s0 UP a4:bb:6d:12:34:56 <BROADCAST,MULTICAST,UP,LOWER_UP>enp1s0 is not a random name: en for Ethernet, p1 for PCI bus 1, s0 for slot 0. These
predictable interface names stay stable across reboots and kernel updates — unlike the old
eth0, whose numbering depended on the order devices happened to be detected in.
The configuration#
The block for a fixed address in /etc/network/interfaces:
auto enp1s0
iface enp1s0 inet static
address 10.10.10.3/16
gateway 10.10.1.1
dns-nameservers 10.10.1.1 1.1.1.1| Line | Meaning |
|---|---|
auto enp1s0 | bring the interface up at boot. Without it the configuration exists but only applies on a manual ifup |
iface enp1s0 inet static | IPv4 (inet), address assigned statically instead of via DHCP. For IPv6 it would be inet6 |
address 10.10.10.3/16 | address in CIDR notation. /16 means network 10.10.0.0 with hosts from 10.10.0.1 to 10.10.255.254. Older guides write address plus netmask 255.255.0.0 — equivalent, just more work |
gateway 10.10.1.1 | default route for everything outside the local network. Must fall inside the network the mask spans |
dns-nameservers 10.10.1.1 1.1.1.1 | nameservers for resolution, separated by spaces. The second one is the fallback if the first does not answer |
The fixed address has to sit outside the router’s DHCP range. Otherwise the router will eventually hand the same address to another device and break both.
Two nameservers as a safety net#
A server without working name resolution cannot update packages, cannot sync time and cannot report anything outward. A second entry catches the failure of the first — here the router as the local resolver, a public one behind it.
How the list is worked through is decided by the C library’s resolver, not by the network:
- Strictly in order. The first entry is always asked first. There is no load balancing and no preference for whichever server is faster.
- Switching costs time. If the first does not answer, the timeout has to expire first (5 seconds by default) before the second is tried. While the first is down, that wait applies to every query.
- Only outages are routed around. A timeout or a
SERVFAILmoves on to the next server. AnNXDOMAIN, on the other hand, is a valid answer and is passed through — a nameserver that answers wrongly does not get skipped. - Three entries maximum. glibc ignores anything beyond that.
The behaviour can be tuned if the wait is a problem:
options timeout:2 attempts:1As soon as your own DNS server runs on the network — on this machine that is now Pi-hole — the public second entry becomes a hole in the filter: queries slip past it, unfiltered and unlogged. And if the entry points at a resolver that forwards back to your own DNS server, you get a loop. On the DNS server itself,
127.0.0.1belongs in the list, and the public resolver is configured as an upstream inside it rather than alongside it. Both done since: The DNS Server Asks Itself and Choosing an Upstream DNS.
Apply#
systemctl rebootA reboot is the honest test: it proves the configuration survives boot instead of only being set in the running system. It also works without one:
sudo ifdown enp1s0 && sudo ifup enp1s0Either way an existing SSH session drops — the address is changing, after all. On a headless box, know the new address beforehand, or the only way back in is a monitor.
Verify#
ip -br a # assigned address
ip route # default via 10.10.1.1 dev enp1s0
ping -c1 10.10.1.1 # gateway reachable
cat /etc/resolv.conf # which nameserver is actually in usePitfall: dns-nameservers without resolvconf#
ifupdown does not write the dns-nameservers line to /etc/resolv.conf itself. That is
done by a hook script under /etc/network/if-up.d/ which only arrives with the resolvconf
package (or openresolv). Without it the line is silently ignored — no error, no warning.
What remains is whatever nameserver the installer wrote.
dpkg -l resolvconf openresolv 2>/dev/null | grep '^ii'If that returns nothing, there are two ways forward:
sudo apt install resolvconf
sudo ifdown enp1s0 && sudo ifup enp1s0Configuration stays in one place — /etc/network/interfaces is the source of truth.
printf 'nameserver 10.10.1.1\nnameserver 1.1.1.1\n' | sudo tee /etc/resolv.confOne package less, but two files that have to agree. The dns-nameservers line then serves as
documentation only — change it later and forget the resolv.conf, and you will be debugging
in the wrong file.
The reverse also holds: with resolvconf installed, /etc/resolv.conf is generated and hand
edits to it vanish on the next ifup. The head of the file tells you which case you are in —
a generated one carries a DO NOT EDIT notice:
head -3 /etc/resolv.confOn this machine the answer is confusing at first:
# Generated by dhcpcdYet there is no dhcpcd service here at all — only the dhcpcd-base package is installed,
resolvconf and openresolv are absent as well, and the file still carries the date of the
installation:
systemctl is-active dhcpcd # inactive
systemctl is-enabled dhcpcd # not-found
ls -la /etc/resolv.conf # a real file, not a symlinkThe header is therefore a leftover from the installer, not evidence of a running generator. In practice that means the second way above applies: the file is maintained by hand, and hand edits survive every reboot. Trusting the comment means looking for the configuration in a place where nobody writes.
The DNS Server Asks Itself#
As long as the gateway’s address sat in that file, the thin client was the only machine in the household whose own queries bypassed the filter — a library whose keeper looks things up somewhere else. Package sources, blocklist updates, time synchronisation: all unfiltered and in no log.
Now that Pi-hole runs on the same machine, its own address belongs there:
sudo cp /etc/resolv.conf /etc/resolv.conf.bak
printf '# Maintained by hand: no resolvconf, no dhcpcd service on this machine.\nnameserver 127.0.0.1\n' | sudo tee /etc/resolv.conf127.0.0.1 rather than 10.10.10.3, even though both reach the same service: the
loopback address works regardless of whether the network interface is up, and it stays
correct when the machine later moves to a different segment. The query never enters the
network stack at all.
The line in /etc/network/interfaces follows suit:
sudo sed -i 's/^\(\s*dns-nameservers\).*/\1 127.0.0.1/' /etc/network/interfacesWithout resolvconf it has no effect — but it is the place where somebody will look for the
configuration. And installing that package one day would otherwise restore, on the next
ifup, exactly the state that was just cleaned up.
The server now has no second nameserver. If Pi-hole fails, it can resolve nothing — no
apt update, no blocklists. That is deliberate: a second entry would be precisely the hole described in the section above, on the very machine that runs the filter. When something breaks, add a fallback address by hand for the duration of the repair.
The counter-check, on the server itself:
dig +short example.com # a normal answer
dig +short doubleclick.net # 0.0.0.0 = the server now filters for itself tooFrom now on it shows up in the live log as a client of its own:
pihole -t