Skip to content

Instantly share code, notes, and snippets.

@michele-tn
Last active January 17, 2026 15:26
Show Gist options
  • Select an option

  • Save michele-tn/3a6eac5b927486d8cb9405f2ce66a370 to your computer and use it in GitHub Desktop.

Select an option

Save michele-tn/3a6eac5b927486d8cb9405f2ce66a370 to your computer and use it in GitHub Desktop.

IONOS VPS DNS Hardening Script v3

This project provides a hardened Bash script to enforce persistent, secure DNS configuration on Ubuntu VPS systems hosted on IONOS.

Key improvement vs v2: DHCP is still used for IP/gateway, but DHCP-provided DNS (IONOS 212.227.x.x) is ignoredsee technical discussion.


Features

  • Disables cloud-init network management (network config)
  • Disables netplan configuration generated by cloud-init
  • Enables systemd-networkd
  • Keeps DHCP for IP on ens6 but rejects DHCP DNS
  • Forces Cloudflare DNS on interface ens6
  • Enables DNS-over-TLS using systemd-resolved
  • Adds Quad9 as fallback DNS provider
  • Makes DNS configuration persistent across reboots

DNS Providers Used

Primary

  • 1.1.1.1
  • 1.0.0.1

Fallback

  • 9.9.9.9
  • 149.112.112.112

All DNS queries are encrypted using DNS-over-TLS.


Why IONOS DNS still appeared before (and how v3 fixes it)

IONOS typically injects DNS via DHCP. Even if you set DNS= in [Network], systemd-networkd may still accept and expose the DHCP DNS unless you explicitly disable it.

v3 adds:

[DHCPv4]
UseDNS=no

[DHCPv6]
UseDNS=no

This keeps DHCP for addressing but prevents the resolver stack from learning/using the IONOS DNS servers.


Requirements

  • Ubuntu 20.04 / 22.04 / 24.04
  • VPS network interface name: ens6
  • Root privileges

Installation

Download and run:

chmod +x setup-dns-ionos-v3.sh
sudo ./setup-dns-ionos-v3.sh
sudo reboot

Verification

After reboot:

resolvectl status ens6
resolvectl status

Expected:

  • On link ens6: DNS Servers show 1.1.1.1 1.0.0.1
  • DNSOverTLS: yes
  • IONOS 212.227.x.x must not appear as active DNS servers

Note: depending on Ubuntu/systemd version, DHCP DNS might still be visible under “DHCP Server” info, but it must not be selected as “DNS Servers” for the link nor as the “Current DNS Server”.


Files Created

  • /etc/systemd/network/10-ens6.network
  • /etc/systemd/resolved.conf.d/dns-hardened.conf
  • /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

Rollback

sudo rm /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
sudo rm /etc/systemd/network/10-ens6.network
sudo rm /etc/systemd/resolved.conf.d/dns-hardened.conf
sudo mv /etc/netplan/50-cloud-init.yaml.bak /etc/netplan/50-cloud-init.yaml
sudo reboot

If /etc/resolv.conf was replaced and you want the previous one back, restore the backup created by the script: /etc/resolv.conf.bak.<timestamp>.


Download

Sources (technical references)

@michele-tn
Copy link
Author

michele-tn commented Jan 16, 2026

IONOS VPS DNS Enterprise Hardening (v5.2)

This repository documents the enterprise DNS hardening configuration for an IONOS VPS running Ubuntu,
based on systemd-networkd and systemd-resolved with DNS-over-TLS enforcement.


🔐 Security Goals

  • Force encrypted DNS (DNS-over-TLS only)
  • Block plaintext DNS leaks (IPv4 and IPv6)
  • Enforce DNSSEC validation
  • Provide automatic resolver failover
  • Prevent resolver hijacking
  • Support IPv6 safely
  • Forensic-level auditing
  • VPN-safe: private subnets are excluded from DNS blocking

✅ Verified Runtime Output

  • Data was acquired via local or encrypted transport: yes
  • DoT TLS test: OK
  • udp dport 53 drop
  • tcp dport 53 drop
  • tcp dport 853 accept
  • ip6 tcp dport 853 accept

This confirms:

  • DNS traffic is encrypted
  • Plain DNS is blocked by firewall
  • IPv6 DNS leaks are prevented
  • VPN/private subnets can still resolve
  • DoT connectivity is functional

🔄 Differences from v3

Original v5.1 features:

  • Cloud-init disable
  • Netplan removal
  • systemd-networkd activation
  • DHCP IP with DNS override
  • Cloudflare DNS
  • Quad9 fallback
  • DNS-over-TLS
  • Stub resolver enforcement

v5.2 adds:

  1. VPN / private subnet exclusion (10.x.x.x, 172.16.x.x, 192.168.x.x)
  2. Improved nftables DNS egress filtering
  3. IPv6 secure DNS filtering
  4. Leak-proof design for VPN users
  5. DNS-over-TLS and fallback watchdog maintained
  6. WireGuard split-DNS template included

🔥 DNS Firewall Policy

Only the following traffic is allowed:

Protocol Port Destination
TCP 853 Cloudflare / Quad9
UDP 53 Private/VPN subnets only
TCP 53 Private/VPN subnets only

All other DNS traffic is blocked.


🔁 Automatic Fallback

A systemd timer checks every 5 minutes:

  • If Cloudflare fails → Quad9 is used
  • If Cloudflare recovers → system switches back automatically

🔄 Rollback

A full rollback script rollback-dns-ionos-v5.2.sh restores:

  • cloud-init networking
  • netplan
  • systemd-networkd config
  • systemd-resolved config
  • nftables DNS rules
  • resolv.conf
  • DNS watchdog

▶️ Rollback Usage

chmod +x rollback-dns-ionos-v5.2.sh
sudo ./rollback-dns-ionos-v5.2.sh
sudo reboot

🔍 Verification Commands

resolvectl status
resolvectl query google.com
openssl s_client -connect 1.1.1.1:853 -servername cloudflare-dns.com
nft list table inet dnsfilter

🌐 VPN / Cloudflare DNS Check

To verify that your DNS hardening works correctly when connected via VPN, use Cloudflare's test page:

Step 1: Connect to your VPN

Ensure you are connected to your private VPN network, so that private subnet traffic is routed correctly.

Step 2: Check DNS resolution via the terminal

# Query a public domain
resolvectl query google.com

Verify DoT (DNS-over-TLS) connectivity

echo | timeout 5 openssl s_client -connect 1.1.1.1:853 -servername cloudflare-dns.com

Check VPN/private subnet DNS is still allowed

resolvectl status ens6

Step 3: Use Cloudflare's diagnostic page

Open in your browser:
https://one.one.one.one/help/

  • Verify that it detects Connected to 1.1.1.1: Yes
  • DNS over HTTPS (DoH) / DNS over TLS (DoT) are correctly reported
  • No leaks occur to non-allowed DNS servers

Step 4: Optional automated check via curl

curl -s https://one.one.one.one/cdn-cgi/trace | grep -E 'warp|ip'

This shows your current public IP and whether WARP (Cloudflare) is active.

Notes:

  • Your VPN subnet (10.x.x.x / 172.16.x.x / 192.168.x.x) should still resolve DNS normally.
  • Internet traffic outside the VPN uses hardened DNS rules (Cloudflare + Quad9).
  • This ensures your DNS setup is VPN-safe and leak-proof.

⚠️ Disclaimer

Intended for VPS and hardened server environments only.
Not recommended for desktop systems.

🚀 Status

SOC / ISP grade DNS security, VPN-safe, leak-proof, IPv6 compliant.

File Link
setup-dns-ionos-plus.sh.xz Download
setup-dns-ionos-plus.sh.xz.sha256 Download
rollback-dns-ionos.sh.xz Download
rollback-dns-ionos.sh.xz.sha256 Download

📦Bash script

#!/usr/bin/env bash
set -e

echo "======================================================"
echo " IONOS VPS DNS ENTERPRISE HARDENING SAFE v6.0"
echo "======================================================"

IFACE="ens6"

DNS_PRIMARY="1.1.1.1 1.0.0.1"
DNS_FALLBACK="9.9.9.9 149.112.112.112"

# Root check
if [ "$(id -u)" -ne 0 ]; then
  echo "Run as root: sudo $0"
  exit 1
fi

echo "[1/7] Disabling cloud-init network configuration..."
mkdir -p /etc/cloud/cloud.cfg.d
cat <<'EOF' >/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
network: {config: disabled}
EOF

echo "[2/7] Disabling cloud-init netplan file..."
if [ -f /etc/netplan/50-cloud-init.yaml ]; then
  mv /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak
fi

echo "[3/7] Enabling systemd-networkd..."
systemctl enable systemd-networkd
systemctl restart systemd-networkd

echo "[4/7] Creating systemd-networkd config for ${IFACE}..."

mkdir -p /etc/systemd/network

cat <<EOF >/etc/systemd/network/10-${IFACE}.network
[Match]
Name=${IFACE}

[Network]
DHCP=yes
DNS=1.1.1.1
DNS=1.0.0.1
Domains=~.

[DHCPv4]
UseDNS=no

[DHCPv6]
UseDNS=no
EOF

echo "[5/7] Configuring systemd-resolved with DNS-over-TLS..."

mkdir -p /etc/systemd/resolved.conf.d

cat <<'EOF' >/etc/systemd/resolved.conf.d/dns-enterprise.conf
[Resolve]
DNS=1.1.1.1 1.0.0.1
FallbackDNS=9.9.9.9 149.112.112.112
DNSOverTLS=yes
DNSSEC=allow-downgrade
Cache=yes
EOF

echo "[6/7] Linking resolv.conf to systemd stub..."

if [ -e /etc/resolv.conf ] && [ ! -L /etc/resolv.conf ]; then
  mv /etc/resolv.conf /etc/resolv.conf.bak.$(date +%s)
fi
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

echo "[7/7] Restarting resolver services..."
systemctl restart systemd-resolved
systemctl restart systemd-networkd

echo "======================================================"
echo " DNS ENTERPRISE HARDENING APPLIED SAFELY"
echo " NO FIREWALL DNS BLOCKING"
echo " VPS ACCESS SAFE"
echo "======================================================"
echo "Reboot recommended:"
echo " sudo reboot"
echo "======================================================"

@michele-tn
Copy link
Author

📦Bash script - rollback-dns-ionos.sh

#!/usr/bin/env bash
set -e

echo "========================================"
echo " DNS HARDENING ROLLBACK SCRIPT"
echo "========================================"

if [ "$(id -u)" -ne 0 ]; then
  echo "Run as root."
  exit 1
fi

echo "[1] Restoring netplan..."
if [ -f /etc/netplan/50-cloud-init.yaml.bak ]; then
  mv /etc/netplan/50-cloud-init.yaml.bak /etc/netplan/50-cloud-init.yaml
fi

echo "[2] Re-enabling cloud-init networking..."
rm -f /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

echo "[3] Removing systemd-networkd config..."
rm -f /etc/systemd/network/10-ens6.network

echo "[4] Removing systemd-resolved custom config..."
rm -f /etc/systemd/resolved.conf.d/dns-enterprise.conf
rm -f /etc/systemd/resolved.conf.d/dns-hardened.conf

echo "[5] Restoring resolv.conf..."
if [ -f /etc/resolv.conf.bak* ]; then
  latest=$(ls -t /etc/resolv.conf.bak* | head -n1)
  mv "$latest" /etc/resolv.conf
fi

echo "[6] Removing nftables DNS firewall..."
rm -f /etc/nftables.d/dns-firewall.nft
sed -i '/dns-firewall.nft/d' /etc/nftables.conf || true

echo "[7] Disabling DNS fallback watchdog..."
systemctl disable dns-fallback-watchdog.timer || true
systemctl stop dns-fallback-watchdog.timer || true
rm -f /etc/systemd/system/dns-fallback-watchdog.*
rm -f /usr/local/bin/dns-fallback-watchdog.sh

echo "[8] Restarting services..."
systemctl restart systemd-resolved || true
systemctl restart systemd-networkd || true
systemctl restart nftables || true

echo "========================================"
echo " ROLLBACK COMPLETED"
echo "========================================"
echo "Reboot recommended:"
echo " sudo reboot"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment