Proton VPN Wireguard Port Forwarding possible?

Hello all.

I have been tinkering around with flint 2 for a while now and for the life of me cannot get port forwarding to work.

I am on debian trixie and am by no means experienced in linux, networking or scripts, I put together this in an attempt to automate the process. Proton is as far as I know the only vpn left that supports port forwarding, but gives no control, they assign a random port number when a tunnel is created.
So my intent was to have a script that runs when i log in and handles the tedium for me

  • detects what port has been assigned
  • creates port forward rule within luci
  • creates ufw rule

-ideally remove old ufw rules created by this process without touching any other rules i manually set

-gives a desktop pop up notification telling me which port is active

- pushes the active port to qbittorrent

#!/bin/bash
# Force right desktop screen for notif
export DISPLAY=:0
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u USER)/bus"

# --- CONFIGURATION ---
ROUTER_IP="ROUTER_UO"
DEBIAN_IP="DESKTOP_IP"
INTERFACE="wgclient2"
QBT_URL="http://localhost:8080"
QBT_USER="placeholder"
QBT_PASS="placeholder"
# ---------------------

LAST_PORT=""

while true; do
    PORT=""

    # 1. ROUTE MANAGEMENT
    ssh root@$ROUTER_IP "
        ip route add 10.2.0.1 dev $INTERFACE scope link 2>/dev/null
        ip route add 10.2.0.2 dev $INTERFACE scope link 2>/dev/null
    "

    # 2. PORT DETECTION
    for GATEWAY in "10.2.0.1" "10.2.0.2"; do
        OUT=$(ssh root@$ROUTER_IP "for i in 1 2 3; do RES=\$(natpmpc -a 1 0 udp 60 -g $GATEWAY 2>&1); if echo \"\$RES\" | grep -q \"public port\"; then echo \"\$RES\"; exit 0; fi; done")
        PORT=$(echo "$OUT" | grep -oE "public port [0-9]+" | awk '{print $3}' | head -n 1)
        if [ -n "$PORT" ]; then
            SELECTED_GW=$GATEWAY
            break
        fi
    done

    # 3. Trigger update on port change
    if [ -n "$PORT" ] && [ "$PORT" != "$LAST_PORT" ]; then
        echo "$(date): New Port $PORT. Updating rules..."

        # WIPE old rules with 'VPN-Sync' tag
        while sudo ufw status numbered | grep -q 'VPN-Sync'; do
            NUM=$(sudo ufw status numbered | grep 'VPN-Sync' | head -n 1 | awk -F"[][]" '{print $2}' | tr -d ' ')
            sudo ufw --force delete "$NUM" >/dev/null 2>&1
        done

        # Add the fresh port rules IPv4, IPv6 disabled on router
        sudo ufw allow proto tcp from any to any port "$PORT" comment 'VPN-Sync'
        sudo ufw allow proto udp from any to any port "$PORT" comment 'VPN-Sync'

        # 4. ROUTER/Luci UPDATE 
        ssh root@$ROUTER_IP << EOF
        # Remove any previous manual rules to avoid ghost entries
        uci -q delete firewall.ProtonForward

        # Create a named redirect section LuCI can read
        uci set firewall.ProtonForward=redirect
        uci set firewall.ProtonForward.name="ProtonForward"
        uci set firewall.ProtonForward.src="wan"
        uci set firewall.ProtonForward.src_dport="$PORT"
        uci set firewall.ProtonForward.dest="lan"
        uci set firewall.ProtonForward.dest_ip="$DEBIAN_IP"
        uci set firewall.ProtonForward.dest_port="$PORT"
        uci set firewall.ProtonForward.proto="tcp udp"
        uci set firewall.ProtonForward.target="DNAT"
        uci set firewall.ProtonForward.enabled="1"

        # For VPN forwarding: match the tunnel IP
        uci set firewall.ProtonForward.src_ip="10.2.0.2"

        uci commit firewall
        /etc/init.d/firewall restart
EOF




        # 5. QBITTORRENT UPDATE
        COOKIE_FILE=$(mktemp)
        curl -s -i -X POST -d "username=$QBT_USER&password=$QBT_PASS" "$QBT_URL/api/v2/auth/login" -c "$COOKIE_FILE" > /dev/null
        curl -s -X POST -b "$COOKIE_FILE" -d "json={\"listen_port\": $PORT}" "$QBT_URL/api/v2/app/setPreferences"
        rm -f "$COOKIE_FILE"

        # 6. FIXED NOTIFICATION
        if command -v notify-send >/dev/null; then
             sudo -u USER DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u USER)/bus notify-send "VPN Sync" "Port $PORT is now live."
        fi

        LAST_PORT="$PORT"
    fi
    sleep 45
done

Some of the pieces are static
Qbittorrent:

  • bound to ethernet network interface
  • bound to desktop ip
  • pmp turned off

Router:

  • VPN tunnel name
  • Kill switch on

VPN:

  • All configs have pmp enabled
  • persistentkeepalive=25
  • MTU 1380
  • Allow remote access the LAN subnet on
  • killswitch on

Now I am well are this is nothing impressive or even polished, but what’s confusing is that it works intermittently.
2 days ago worked fine, port was open and reachable upload speeds were great
Yesterday I had to run the script a few times when changing server but it all worked
Today, the script seems to have done everything, detected new port number, created port forward rule in luci, created ufw rule, pushed port to qbittorrent, but my upload speeds are 0 and any port check tool says the port is closed and I get issues like this indicating traffic being sent but problems lying elsewhere

14:17:27.508617 [.] ACK (Handshake Complete)
14:17:27.508680 [P.] Data Sent (68 bytes)
14:17:27.856602 [R.] Connection Reset by Peer

If anyone has the time to take a look and see where i'm going wrong or even just suggestions for things like the not yet working ufw redundant rule clearing part, i'll be happy to hear it, Thanks

I did a quick skim to your code, but I think this need to be wgclient.

I think the named section still would work, but you need to test it how the firewall interpretes it, it is valid uci code but not sure if it is valid for the firewall with interpretation, normally I use uci add firewall redirect and then uci set firewall.@redirect[-1]..., but your biggest issue is that you no longer need to portforward from wan as origin but use the protonvpn.

Also it could be that it just was working by hole punching but less effective.

I use mullvad which is behind a nat, but sometimes I also upload, and when I test ubuntu torrent it is clear to me, but yes that could explain why it worked and then not.

Thanks for taking the time, you appear to be right, a bit of brute force and ignorance allowed it to temporarily work but it's not long term. Proton staff got back to me about a ticket I raised with them a while ago and they responded saying port forwarding with router level vpn straight up doesn't work and their advice was

To forward a port to your device, you'll have to use the regular Proton VPN application or a manual configuration directly on your device, instead of the router.

So I guess gluetun has to be the way forward, shame, gluetun causes wild cpu spikes for me, but I guess I was bashing my head against the wall for something that wasn't going to work regardless