- Python 78%
- Shell 22%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
pmxcfs ignores chmod, so scripts in /etc/pve cannot be made executable. Document running them via python3 explicitly, and add a /usr/local/sbin install variant for those who prefer an executable script per node. |
||
| bash | ||
| .gitignore | ||
| LICENSE | ||
| manage_ipset.conf | ||
| manage_ipset.py | ||
| README.md | ||
manage-proxmox-ipset
Keep Proxmox cluster firewall IPSets in sync with DNS.
Hostnames are resolved repeatedly (A and AAAA) until no new IP addresses show up, so that all IPs behind a round-robin or anycast DNS record are collected. Afterwards the IPSet is synchronized: IPs that are gone are deleted, new IPs are added. Only the difference is written — no flush-and-refill.
One script, one config file. Python 3 standard library only — no pip
installs needed. Requires pvesh (from pve-manager) and dig
(from dnsutils) on the host.
Installation
Copy the script and the config to a Proxmox host (or any host with
pvesh access to the cluster).
Note: /etc/pve is Proxmox's cluster filesystem (pmxcfs). It syncs
its files to every node, but does not support chmod — scripts
there cannot be made executable. Two options:
a) In /etc/pve — cluster-wide, run via python3:
cp manage_ipset.py manage_ipset.conf /etc/pve/
python3 /etc/pve/manage_ipset.py
b) Local and executable — e.g. /usr/local/sbin, per node:
install -m 755 manage_ipset.py /usr/local/sbin/
install -m 644 manage_ipset.conf /etc/
/usr/local/sbin/manage_ipset.py -c /etc/manage_ipset.conf
manage_ipset.conf is looked up next to the script unless -c is
given.
Usage
# sync every IPSet from the config
python3 manage_ipset.py
# use a different config file
python3 manage_ipset.py -c /path/to/manage_ipset.conf
# sync a single IPSet directly
python3 manage_ipset.py github github.com github.io
python3 manage_ipset.py docker-io registry-1.docker.io --round 30
Cron
# every hour, /etc/pve install
0 * * * * python3 /etc/pve/manage_ipset.py
# local install
0 * * * * /usr/local/sbin/manage_ipset.py -c /etc/manage_ipset.conf
Config (manage_ipset.conf)
[defaults]
round=50
stale_after=10
# dns_server=1.1.1.1 # optional, default: system resolver
# timeout=15
golang
host=go.dev
github
host=github.com
host=github.io round=10 stale_after=3
[defaults]section sets the global defaults. Optional.- An indented line
host=...adds a host to the current IPSet. An IPSet may hold several hosts; their IPs are merged into one set. - Per-host options (optional):
round,stale_after,dns_server,timeout. - Lines starting with
#are comments.
Options
| Option | Meaning | Default |
|---|---|---|
round |
max. DNS query rounds per host | 50 |
stale_after |
rounds without a new IP before stopping early | 10 |
dns_server |
resolver to query (dig @server) |
system |
timeout |
dig timeout in seconds |
15 |
Hosts with many rotating IPs (big CDNs, registry-1.docker.io)
benefit from a higher round; round=1 behaves like a single query.
How it works
manage_ipset.pyreadsmanage_ipset.confand groups hosts by IPSet.- For each host,
dig +short host A/AAAAis run up toroundtimes. As soon asstale_afterconsecutive rounds return no new IP, the loop stops. - The collected IPs are compared against the current IPSet content
(
pvesh get /cluster/firewall/ipset/<name> --output-format json). - Missing IPs are added (
pvesh create ... --cidr), stale IPs are removed (pvesh delete .../<ip>).
If a host in an IPSet does not resolve, that IPSet is left untouched — a single broken hostname cannot strip the IPs of its siblings.
Notes
- IPSet membership is matched by plain IP, ignoring the netmask that Proxmox stores per entry.
- The script exits non-zero if any IPSet was skipped.
- Legacy bash versions of both scripts are kept for reference in the
bash/directory.
License
GPLv3 — see LICENSE.
Copyright (C) 2026 Paul Klumpp