I use AI to explain my idea more good but it’s my idea:
Automatically Update sys-firewall with Mullvad Server IPs
I created a script that automatically extracts Mullvad VPN server IP addresses and configures sys-firewall to only allow traffic to those IPs. This restricts all network activity through your firewall to only reach Mullvad servers.
How It Works
- Extracts IPs — Connects to
sys-vpn-mullvad-appand runsmullvad relay list, then parses out all IPv4 and IPv6 addresses - Generates firewall rules — Creates nftables rules that allow traffic only to those IPs
- Blocks everything else — Adds a final drop rule to block non-Mullvad destinations
- Applies to sys-firewall — Copies the rules to
/rw/config/qubes-firewall-user-script(persists across reboots) - Reloads firewall — Applies the new rules immediately
Usage
- Create the script in a regular qube (e.g.,
work):
bash
nano ~/mullvad-firewall-update.sh# Paste the script below
- Copy it to dom0:
bash
qvm-run --pass-io work 'cat ~/mullvad-firewall-update.sh' > /tmp/mullvad-firewall-update.shchmod +x /tmp/mullvad-firewall-update.sh
- Run from dom0:
bash
/tmp/mullvad-firewall-update.sh
The Script
bash
#!/bin/bash
# Qubes Mullvad Firewall IP Whitelist Generator# Run this script in dom0# Purpose: Extract Mullvad server IPs and restrict sys-firewall to only allow traffic to those IPs
set -e # Exit on error
echo "Extracting Mullvad server IPs from sys-vpn-mullvad-app..."IPS=$(qvm-run sys-vpn-mullvad-app 'mullvad relay list | grep -oP "\(\K[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(?=,)" | sort -u')
echo "Extracting IPv6 addresses..."IPS6=$(qvm-run sys-vpn-mullvad-app 'mullvad relay list | grep -oP "2604:[a-f0-9:]+(?=\))" | sort -u')
# Create nftables rules scriptcat > /tmp/qubes-firewall-user-script << 'EOF'#!/bin/bash# Mullvad server IPs only - auto-generated# This restricts all traffic through sys-firewall to only Mullvad VPN servers
EOF
# Add IPv4 allow rulesecho "# IPv4 Allow rules:" >> /tmp/qubes-firewall-user-scriptwhile read ip; do if [ ! -z "$ip" ]; then echo "nft add rule filter forward ip daddr $ip accept" >> /tmp/qubes-firewall-user-script fidone <<< "$IPS"
# Add IPv6 allow rulesecho "" >> /tmp/qubes-firewall-user-scriptecho "# IPv6 Allow rules:" >> /tmp/qubes-firewall-user-scriptwhile read ip; do if [ ! -z "$ip" ]; then echo "nft add rule filter forward ip6 daddr $ip accept" >> /tmp/qubes-firewall-user-script fidone <<< "$IPS6"
# Add default drop rule for everything elseecho "" >> /tmp/qubes-firewall-user-scriptecho "# Drop all other traffic" >> /tmp/qubes-firewall-user-scriptecho "nft add rule filter forward drop" >> /tmp/qubes-firewall-user-script
# Copy script from dom0 to sys-firewallecho "Copying firewall script to sys-firewall..."cat /tmp/qubes-firewall-user-script | qvm-run --pass-io --user=root sys-firewall 'cat > /tmp/qubes-firewall-user-script'
# Move to correct location and make executableecho "Installing firewall rules..."qvm-run --user=root sys-firewall 'mv /tmp/qubes-firewall-user-script /rw/config/qubes-firewall-user-script && chmod +x /rw/config/qubes-firewall-user-script'
# Reload firewall rulesqvm-run --user=root sys-firewall 'systemctl reload qubes-firewall'
echo ""echo "✓ Done! Firewall updated."echo " IPv4 IPs: $(echo "$IPS" | wc -l)"echo " IPv6 IPs: $(echo "$IPS6" | wc -l)"echo ""echo "WARNING: All non-Mullvad traffic through sys-firewall is now blocked!"echo "To revert, delete /rw/config/qubes-firewall-user-script in sys-firewall and reload."
# Optional: Clean uprm /tmp/qubes-firewall-user-script
Optional: Automate with Cron
To update the IP list automatically (useful since Mullvad adds/removes servers), add to dom0’s crontab:
bash
sudo crontab -e
Add this line (runs daily at midnight):
0 0 * * * /tmp/mullvad-firewall-update.sh
Important Notes
- This is restrictive — Only Mullvad server IPs will be allowed. If something breaks, you know why.
- To revert — Connect to sys-firewall and delete
/rw/config/qubes-firewall-user-script, then reload the firewall - Requires Mullvad CLI — Must be installed in
sys-vpn-mullvad-app - Updates needed — Run the script whenever you want to refresh the IP list
Is this script logic correct? Any feedback before I create a full community guide?