Using Dynamic IP Addresses in firewalld
ipset is your friend
If you want to use dynamic IP addresses (DynDNS) in your firewalld rules, you can use ipsets.
ipset allows you to efficiently manage large or frequently changing lists of IP addresses and use them directly in firewall rules.
The following script creates one ipset per IP-protocol for configured DynDNS hostnames. It automatically creates or updates all ipset entries with the currently resolved IP addresses of the given hostnames.
You can then use the ipset names in your firewalld rules - see the comment in the script.
New: Supports multiple hostnames.
Tested on Rocky 10.
Works on RHEL, Rocky, Scientific, Fedora, CentOS
/usr/local/bin/firewall_ddns_update.sh:
#!/usr/bin/env bash
# Bash script to query for current IP address of given Dynamic-DNS
# hosts and create or update firewalld ipset(s).
# You are then able to use the ipset in firewalld rules.
# Obtain the current ipset(s) with:
# firewall-cmd --permanent --get-ipsets
#
# This script supports IPv4 and IPv6.
#
# This script is intended to be run from a cron job with root privileges.
# The script will only update the ipset if the IP address has changed.
#
# Example for using ipset in firewalld rules:
# firewall-cmd --permanent --zone=public --add-source ipset:xyz_V4
# List of DNS names to include
DNS_NAMES=("myexampledynhost1.example.com", "myexampledynhost2.example.com")
# Zone base-name. Will be prepended with IP-protocol type (_V4/_V6)
ZONE="my_ddns_ip_zone"
# Single ipset names (shared across all DNS names)
IPSET_V4="${ZONE}_4"
IPSET_V6="${ZONE}_6"
create_ipset() {
local setname=$1
local type=$2
if ! firewall-cmd --permanent --get-ipsets | grep -qw "$setname"; then
echo "Creating ipset: $setname ($type)"
firewall-cmd --permanent --new-ipset="$setname" --type=hash:ip --option=family=$type
firewall-cmd --permanent --zone=$ZONE --add-source=ipset:$setname
firewall-cmd --reload
fi
}
CHANGED=0
create_ipset "$IPSET_V4" "inet"
create_ipset "$IPSET_V6" "inet6"
# === resolve all IPs from all DNS names ===
IP_V4_ALL=()
IP_V6_ALL=()
for DNS_NAME in "${DNS_NAMES[@]}"; do
v4_out=$(dig +short A "$DNS_NAME")
if [ $? -eq 0 ]; then
IP_V4_ALL+=($v4_out)
fi
v6_out=$(dig +short AAAA "$DNS_NAME")
if [ $? -eq 0 ]; then
IP_V6_ALL+=($v6_out)
fi
done
# Remove duplicates and sort
IP_V4=$(printf "%s\n" "${IP_V4_ALL[@]}" | sort -u)
IP_V6=$(printf "%s\n" "${IP_V6_ALL[@]}" | sort -u)
# === IPv4 handling ===
CURRENT_V4=$(firewall-cmd --permanent --ipset="$IPSET_V4" --get-entries 2>/dev/null)
for ip in $CURRENT_V4; do
if ! grep -qw "$ip" <<<"$IP_V4"; then
echo "Removing outdated IPv4 from $IPSET_V4: $ip"
firewall-cmd --permanent --ipset="$IPSET_V4" --remove-entry="$ip" >/dev/null 2>&1
CHANGED=1
fi
done
for ip in $IP_V4; do
if ! grep -qw "$ip" <<<"$CURRENT_V4"; then
echo "Adding new IPv4 to $IPSET_V4: $ip"
firewall-cmd --permanent --ipset="$IPSET_V4" --add-entry="$ip" >/dev/null 2>&1
CHANGED=1
fi
done
# === IPv6 handling ===
CURRENT_V6=$(firewall-cmd --permanent --ipset="$IPSET_V6" --get-entries 2>/dev/null)
for ip in $CURRENT_V6; do
if ! grep -qw "$ip" <<<"$IP_V6"; then
echo "Removing outdated IPv6 from $IPSET_V6: $ip"
firewall-cmd --permanent --ipset="$IPSET_V6" --remove-entry="$ip" >/dev/null 2>&1
CHANGED=1
fi
done
for ip in $IP_V6; do
if ! grep -qw "$ip" <<<"$CURRENT_V6"; then
echo "Adding new IPv6 to $IPSET_V6: $ip"
firewall-cmd --permanent --ipset="$IPSET_V6" --add-entry="$ip" >/dev/null 2>&1
CHANGED=1
fi
done
if [ $CHANGED -eq 1 ]; then
echo "Reloading firewall..."
firewall-cmd --reload >/dev/null 2>&1
fi
# end of script.
Add to cron
Create /etc/cron.d/firewall_update_ddns:
# Update DDNS host IPs in firewall ipset(s)
*/1 * * * * root /usr/local/bin/firewall_ddns_update.sh
*/1 * * * * root /usr/local/bin/firewall_ddns_update.sh