What's new
  • SNBForums Code of Conduct

    SNBForums is a community for everyone, no matter what their level of experience.

    Please be tolerant and patient of others, especially newcomers. We are all here to share and learn!

    The rules are simple: Be patient, be nice, be helpful or be gone!

VPNMON VPNMON-R3 v1.4.1 -Jul 2, 2025- Monitor WAN/Dual-WAN/OpenVPN Health & Reset Multiple OpenVPN Connections (Now available in AMTM!) Wireguard Alpha!

You read this right... We are now supporting WIREGUARD! Whoooo! :)

First early alpha of VPNMON-R3 v1.5.0 is available for anyone who wants to have fun... @iTyPsIDg @Stephen Harrington ;)

Code:
curl --retry 3 "https://raw.githubusercontent.com/ViktorJp/VPNMON-R3/develop/vpnmon-r3.sh" -o "/jffs/scripts/vpnmon-r3.sh" && chmod 755 "/jffs/scripts/vpnmon-r3.sh"

* Operations Menu has been expanded to handle the Wireguard reset/reconnect/stop/unmonitor functionality.
* Enable/Disable (M)onitored VPN?WG slots is available to pick which WG slots you want to monitor/unmonitor
* Update/Maintain (V)PN/(W)G Server lists is WORKING. I have a set of 4 Wireguard servers that VPNMON-R3 can randomly pick from and connect to on a reset. More info below.
* Any kind of automation, ie. pulling live data from an API as we do with VPN at the moment is impossible... but maybe some day we can figure something out. We will need to rely on pre-configured server lists (as discussed more below).
* Various scenarios are functional to keep tabs on the WG connection and keep it connected, like being non-responsive, having an excessive ping or a handshake that isn't renewing timely.

View attachment 66669

As stated above... I've configured 4 different Proton Wireguard servers and added them to the WG1 Server list. In order to be able to connect to another server, I will need 5 items in a single row, comma delimited.

When pressing (W)G Server Lists, you are presented with the 5 server list slots you can edit. Instructions on which fields are described in the INSTRUCTIONS section.

View attachment 66673

It would need to look something like this... example below:

Code:
ConnectionName,EndpointIP,EndpointPort,PrivateKey,PublicKey
City WG,143.32.55.23,34334,fasdkkfj44jafdja399ffkasdjfj=,221t949sfasdfjlkasdfj32323kf=

This information is something you would need to get from your VPN provider... in my case, Proton VPN. It provides an interface to pick a location (city), and a slew of available servers under each. You then download a .conf file for that particular server... open it up with your favorite text editor, and copy/paste this info into the VPNMON-R3 WG server list. Don't forget the commas!

Here's an example taken from my setup:

View attachment 66674

Disclaimer: Again, this is based off my experience with Proton Wireguard... I'm definitely curious to hear about your experience! :)
It's a very very nice!
ENJOY! (I've definitely got more work/refining to do before beta/release...)
I can see it now... Keep up the great works!
 
First early alpha of VPNMON-R3 v1.5.0 is available for anyone who wants to have fun...
Will try and give it a crack when I get the house to myself ... maybe tomorrow even.
I only really use a single WG slot via NordVPN but thank you as always and looking forward to playing @Viktor Jaep
 
Hey @TITAN... it should automatically refresh the list if you have option #5 enabled, ie. when it resets your connection. You currently can schedule a reset anytime/frequency with a custom cronjob, or schedule for it to happen 1x a day from within VPNMON. Are you looking for the custom server list to refresh without a connection reset?View attachment 63855
Installed and working fine with StrongVPN with client connections to two servers. Thanks for the update. Will keep any eye open for any quirks.
 
You read this right... We are now supporting WIREGUARD! Whoooo! :)

First early alpha of VPNMON-R3 v1.5.0 is available for anyone who wants to have fun... @iTyPsIDg @Stephen Harrington ;)

Code:
curl --retry 3 "https://raw.githubusercontent.com/ViktorJp/VPNMON-R3/develop/vpnmon-r3.sh" -o "/jffs/scripts/vpnmon-r3.sh" && chmod 755 "/jffs/scripts/vpnmon-r3.sh"

* Operations Menu has been expanded to handle the Wireguard reset/reconnect/stop/unmonitor functionality.
* Enable/Disable (M)onitored VPN?WG slots is available to pick which WG slots you want to monitor/unmonitor
* Update/Maintain (V)PN/(W)G Server lists is WORKING. I have a set of 4 Wireguard servers that VPNMON-R3 can randomly pick from and connect to on a reset. More info below.
* Any kind of automation, ie. pulling live data from an API as we do with VPN at the moment is impossible... but maybe some day we can figure something out. We will need to rely on pre-configured server lists (as discussed more below).
* Various scenarios are functional to keep tabs on the WG connection and keep it connected, like being non-responsive, having an excessive ping or a handshake that isn't renewing timely. Huge thanks also to @ZebMcKayhan for his encouragement to dive into this long ago, and for providing some excellent methodologies in his WG watchdog script.

View attachment 66669

As stated above... I've configured 4 different Proton Wireguard servers and added them to the WG1 Server list. In order to be able to connect to another server, all it takes is 5 items entered into a single row, comma delimited.

When pressing (W)G Server Lists, you are presented with the 5 server list slots you can edit. Instructions on which fields are described in the INSTRUCTIONS section.

View attachment 66673

It would need to look something like this... example below:

Code:
ConnectionName,EndpointIP,EndpointPort,PrivateKey,PublicKey
City WG,143.32.55.23,34334,fasdkkfj44jafdja399ffkasdjfj=,221t949sfasdfjlkasdfj32323kf=

This information is something you would need to get from your VPN provider... in my case, Proton VPN. It provides an interface to pick a location (city), and a slew of available servers under each. You then download a .conf file for that particular server... open it up with your favorite text editor, and copy/paste this info into the VPNMON-R3 WG server list. Don't forget the commas!

Here's an example taken from my setup:

View attachment 66674

Disclaimer: Again, this is based off my experience with Proton Wireguard... I'm definitely curious to hear about your experience! :)

ENJOY! (I've definitely got more work/refining to do before beta/release...)
Excellent work!

I hope this helps you with automating the server list.

I took your desired output and created a script that can output in exactly that format for NordVPN. For now, I have hardcoded the server recommendations list; however, I see that as being an argument that could be passed to give more flexibility, just like I have for the access token.

Bash:
#!/usr/bin/env bash

ACCESS_TOKEN="$1"

CREDENTIALS_URL="https://api.nordvpn.com/v1/users/services/credentials"
SERVER_RECOMMENDATIONS_URL="https://api.nordvpn.com/v1/servers/recommendations?&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=3"

PRIVATE_KEY=$(curl -s -u token:"$ACCESS_TOKEN" "$CREDENTIALS_URL" | jq -r .nordlynx_private_key)

curl -s "$SERVER_RECOMMENDATIONS_URL" | jq --arg private_key "$PRIVATE_KEY" -r '.[] | . as $parent | .technologies | .[] | select(.identifier == "wireguard_udp") | [$parent.name, $parent.station, $private_key, .metadata[0].value] | "\(.[0]),\(.[1]),51820,\(.[2]),\(.[3])"'

What do you think? This is the current output:

Code:
United States #9397,185.203.218.154,51820,PRIVATE_KEY,PUBLIC_KEY
United States #9391,185.203.218.142,51820,PRIVATE_KEY,PUBLIC_KEY
United States #9392,185.203.218.144,51820,PRIVATE_KEY,PUBLIC_KEY
 
One thing I noticed when attempting the Alpha version: The Address and Allowed IPs are empty. The allowed IPs should be 0.0.0.0/0, but the address should probably be in the config for the interface since it varies per provider.

1752586438228.png
 
One thing I noticed when attempting the Alpha version: The Address and Allowed IPs are empty. The allowed IPs should be 0.0.0.0/0, but the address should probably be in the config for the interface since it varies per provider.

View attachment 66722
I'm assuming you've already applied a WG .conf file to the slot you're using. That way, those values are already set. I've noticed that these values don't change from config to config on Proton. What are you seeing?
 
I'm assuming you've already applied a WG .conf file to the slot you're using. That way, those values are already set. I've noticed that these values don't change from config to config on Proton. What are you seeing?
I see. I had a completely blank slot that I was configuring. I agree that they do not change except by provider. For example, Nord has different values from Proton.

Additionally, I don't think private keys change at all. I've reviewed some that I generated as far back as March. If we can store these in the config (per interface, allowing multiple providers to work), then they can be referenced, making it very easy to automate building the server list.

Move Private key elsewhere so vr3wgsvr#.txt looks has this format:
Code:
ConnectionName,EndpointIP,EndpointPort,PublicKey
Example for US servers using Wireguard and Nord:
Bash:
curl -s "https://api.nordvpn.com/v1/servers/recommendations?filters\[country_id\]=228&filters\[servers_groups\]\[identifier\]=legacy_standard&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=5" | jq -r '.[] | . as $parent | .technologies | .[] | select(.identifier == "wireguard_udp") | [$parent.name, $parent.station, .metadata[0].value] | "\(.[0]),\(.[1]),51820,\(.[2])"'
Alternatively, it could stay the same and you just have to add the private key like this:
Bash:
curl -s "https://api.nordvpn.com/v1/servers/recommendations?filters\[country_id\]=228&filters\[servers_groups\]\[identifier\]=legacy_standard&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=5" | jq -r '.[] | . as $parent | .technologies | .[] | select(.identifier == "wireguard_udp") | [$parent.name, $parent.station, .metadata[0].value] | "\(.[0]),\(.[1]),51820,{PRIVATE_KEY},\(.[2])"'

Lastly, can you have the Public WG IP perform a lookup to determine the IP instead of displaying the value in the input address? This is mainly a formatting issue for a private VPN server I use:
1752590529569.png
 
Last edited:
Thanks for this, Proton here- just setting this up now-will report back

Edit: I have set up 2 different WG vpn's- using couple different servers on each, seems to be working
 
Last edited:
For Proton users comfortable with Python, you can use these two scripts to generate conf files and then a summary file to paste into VPNMON-R3 (note that you need to go to https://account.protonvpn.com/downloads to get the credential information while Developer tools is open in your browser):
Python:
import http.client
import http.cookies
import json
import base64
import hashlib
import os
import random
import argparse
import time
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes

"""
Copyright - FuseTim 2024

This code is dual-licensed under both the MIT License and the Apache License 2.0.

You may choose either license to govern your use of this code.

MIT License:
https://opensource.org/licenses/MIT

Apache License 2.0:
https://www.apache.org/licenses/LICENSE-2.0

By contributing to this project, you agree that your contributions will be licensed under
both the MIT License and the Apache License 2.0.
"""

######################################################################################

# Credentials (found in Headers and Cookies) for certificate file using Developer Tools
auth_server = "" # See `x-pm-uid` header
auth_token  = "" # See `AUTH-<x-pm-uid>` cookie
session_id  = "" # See `Session-Id` cookie
web_app_version = "[email protected]" # See `x-pm-appversion` header

# Settings
prefix = "VPNMON-R3" # Prefix is used for config file and name in ProtonVPN Dashboard
output_dir = "./"
selected_countries = ["CO"]
selected_cities = [ "Bogota" ]
selected_tier = 2  # 0 = Free, 2 = Plus
selected_features = [ "-TOR", "-SecureCore" ]  # Features that a server should have ("P2P", "TOR", "SecureCore", "XOR", etc) or not ("-P2P", etc)
max_servers = 30  # Maximum of generated config
max_servers_per_city = 30  # Maximum number of servers per city
listing_only = False  # Do not generate config, just list available servers with previous selectors

config_features = {
    "SafeMode": False,
    "SplitTCP": True,
    "PortForwarding": True,
    "RandomNAT": False,
    "NetShieldLevel": 0,  # 0, 1 or 2
}

######################################################################################

# Contants
connection = http.client.HTTPSConnection("account.protonvpn.com")
C = http.cookies.SimpleCookie()
C["AUTH-"+auth_server] = auth_token
C["Session-Id"] = session_id
headers = {
    "x-pm-appversion": web_app_version,
    "x-pm-uid": auth_server,
    "Accept": "application/vnd.protonmail.v1+json",
    "Cookie": C.output(attrs=[],header="", sep="; ")
}


def generateKeys():
    """Generate a client key-pair using the API. Could be generated offline but need more work..."""
    print("Generating key-pair...")
    connection.request("GET", "/api/vpn/v1/certificate/key/EC", headers=headers)
    response = connection.getresponse()
    print("Status: {} and reason: {}".format(response.status, response.reason))
    resp = json.loads(response.read().decode())
    priv = resp["PrivateKey"].split("\n")[1]
    pub = resp["PublicKey"].split("\n")[1]
    print("Key generated:")
    print("priv:", priv)
    print("pub:", pub)
    return [resp["PrivateKey"], pub, priv]


def getPubPEM(priv):
    """Return the Public key as string without headers"""
    return priv[1]

def getPrivPEM(priv):
    """Return the Private key as PKCS#8 without headers"""
    return priv[2]

def getPrivx25519(priv):
    """Return the x25519 base64-encoded private key, to be used in Wireguard config."""
    hash__ = hashlib.sha512(base64.b64decode(priv[2])[-32:]).digest()
    hash_ = list(hash__)[:32]
    hash_[0] &= 0xf8
    hash_[31] &= 0x7f
    hash_[31] |= 0x40
    new_priv = base64.b64encode(bytes(hash_)).decode()
    return new_priv


def registerConfig(server, priv):
    """Register a Wireguard configuration and return its raw response."""
    h = headers.copy()
    h["Content-Type"]= "application/json"
    print("Registering Config for server", server["Name"],"...")
    body = {
    "ClientPublicKey": getPubPEM(priv),
    "Mode": "persistent",
    "DeviceName": prefix+"-"+server["Name"],
    "Features": {
        "peerName": server["Name"],
        "peerIp": server["Servers"][0]["EntryIP"],
        "peerPublicKey": server["Servers"][0]["X25519PublicKey"],
        "platform": "Windows",
                # You can add features there (PortForwarding, SplitTCP, ModerateNAT
                # See https://github.com/ProtonMail/WebClients/blob/8b5035d6f848b76d005814fca260bb616e83a4b2/packages/components/containers/vpn/WireGuardConfigurationSection/feature.ts#L53
                "SafeMode": config_features["SafeMode"],
                "SplitTCP": config_features["SplitTCP"],
                "PortForwarding": config_features["PortForwarding"] if server["Features"] & 4 == 4 else False,
                "RandomNAT": config_features["RandomNAT"],
                "NetShieldLevel": config_features["NetShieldLevel"], # 0, 1 or 2
    }
    }
    connection.request("POST", "/api/vpn/v1/certificate", body=json.dumps(body), headers=h)
    response = connection.getresponse()
    print("Status: {} and reason: {}".format(response.status, response.reason))
    resp = json.loads(response.read().decode())
    print(resp)
    return resp

def generateConfig(priv, register):
    """Generate a Wireguard config using the ProtonVPN API answer."""
    conf = """[Interface]
# Key for {prefix}
PrivateKey = {priv}
Address = 10.2.0.2/32
DNS = 10.2.0.1

[Peer]
# {server_name}
PublicKey = {server_pub}
AllowedIPs = 0.0.0.0/0
Endpoint = {server_endpoint}:51820
    """.format(prefix=prefix, priv=getPrivx25519(priv), server_name=register["Features"]["peerName"], server_pub=register["Features"]["peerPublicKey"], server_endpoint=register["Features"]["peerIp"])
    return conf


def write_config_to_disk(name, conf):
    f = open(output_dir+"/"+name+".conf", "w")
    f.write(conf)
    f.close()


# VPN Listings

connection.request("GET", "/api/vpn/logicals", headers=headers)
response = connection.getresponse()
print("Status: {} and reason: {}".format(response.status, response.reason))

servers = json.loads(response.read().decode())["LogicalServers"]

for s in servers:
    feat = [
    "SecureCore" if s["Features"] & 1 == 1 else "-SecureCore",
    "TOR" if s["Features"] & 2 == 2 else "-TOR",
    "P2P" if s["Features"] & 4 == 4 else "-P2P",
    "XOR" if s["Features"] & 8 == 8 else "-XOR",
    "IPv6" if s["Features"] & 16 == 16 else "-IPv6"
    ]
    if (not s["EntryCountry"] in selected_countries and not s["ExitCountry"] in selected_countries) or s["Tier"] != selected_tier:
        continue
    if len(list(filter(lambda sf: not (sf in feat), selected_features))) > 0:
        continue
    print("- Server", s["Name"])
    print("  > ID:", s["ID"])
    print("  > EntryCountry:", s["EntryCountry"])
    print("  > ExitCountry:", s["ExitCountry"])
    print("  > Tier:", s["Tier"])
    print("  > Features:")
    print("      - SecureCore:", "Y" if s["Features"] & 1 == 1 else "N")
    print("      - Tor:", "Y" if s["Features"] & 2 == 2 else "N")
    print("      - P2P:", "Y" if s["Features"] & 4 == 4 else "N")
    print("      - XOR:", "Y" if s["Features"] & 8 == 8 else "N")
    print("      - IPv6:", "Y" if s["Features"] & 16 == 16 else "N")
    print("  > Score:", s["Score"])
    print("  > Load:", s["Load"])
    print("  > Status:", s["Status"])
    print("  > Instance:")
    for i in s["Servers"]:
        print("    - Instance n°",i["Label"],":", i["ID"])
        print("      > EntryIP:", i["EntryIP"])
        print("      > ExitIP:", i["ExitIP"])
        print("      > Domain:", i["Domain"])
        print("      > X25519PublicKey:", i["X25519PublicKey"])
    if not listing_only:
        keys = generateKeys()
        reg = registerConfig(s, keys)
        config = generateConfig(keys, reg)
        write_config_to_disk(reg["DeviceName"], config)
        max_servers-=1
    if (max_servers <= 0):
        break
    time.sleep(5)

connection.close()

After the files are generated:
Bash:
#!/usr/bin/env bash

PREFIX="Proton"

for f in *.conf; do
  # Extract line 8, remove leading '# ' and trim
  name=$(sed -n '8s/^# *//p' "$f" | xargs)
  # Extract Endpoint, replace : with ,
  endpoint=$(grep -m1 '^Endpoint' "$f" | awk '{print $3}' | sed 's/:/,/')
  # Extract PrivateKey (first occurrence)
  privkey=$(grep -m1 '^PrivateKey' "$f" | awk '{print $3}')
  # Extract PublicKey (first occurrence)
  pubkey=$(grep -m1 '^PublicKey' "$f" | awk '{print $3}')
  # Output as CSV line
  echo "$PREFIX $name,$endpoint,$privkey,$pubkey"
done > summary.txt
 
@Viktor Jaep - I submitted a PR for the develop branch, Alpha version. I added WG Server List Automation.

I used this command to build the WG server list:
Bash:
curl --silent --retry 3 --connect-timeout 3 --max-time 6 --retry-delay 1 --retry-all-errors "https://api.nordvpn.com/v1/servers/recommendations?filters\[country_id\]=228&filters\[servers_groups\]\[identifier\]=legacy_standard&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=5" | jq -r '.[] | . as $parent | .technologies | .[] | select(.identifier == "wireguard_udp") | [$parent.locations[].country.city.name, ($parent.name | split("#") | .[1]), $parent.station, .metadata[0].value] | "\(.[0]) #\(.[1]),\(.[2]),51820,{REPLACE_WITH_YOUR_PRIVATE_KEY},\(.[3])"'

It created the following sanitized output:

Code:
Miami #6610,37.120.215.35,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #9382,185.203.218.124,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #9396,185.203.218.152,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #5940,185.245.86.54,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #9394,185.203.218.148,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=

Proton, sadly, doesn't allow automation and I don't have any other VPN providers I can test against besides Nord.
 
I see. I had a completely blank slot that I was configuring. I agree that they do not change except by provider. For example, Nord has different values from Proton.
Yeah... I think it's best to make sure each slot functions on its own before assigning it as a monitored slot in VPNMON-R3. That would help reduce the amount of troubleshooting since we know we had a good known config and connection to start out with.
Additionally, I don't think private keys change at all. I've reviewed some that I generated as far back as March. If we can store these in the config (per interface, allowing multiple providers to work), then they can be referenced, making it very easy to automate building the server list.
So this is not the case for me on Proton. The Public and Private keys are different for each server. This is why I determined that these were values I'd need to capture in the server list in order to correctly populate them in the WG client slots when switching from server to server.

Alternatively, it could stay the same and you just have to add the private key like this:
Bash:
curl -s "https://api.nordvpn.com/v1/servers/recommendations?filters\[country_id\]=228&filters\[servers_groups\]\[identifier\]=legacy_standard&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=5" | jq -r '.[] | . as $parent | .technologies | .[] | select(.identifier == "wireguard_udp") | [$parent.name, $parent.station, .metadata[0].value] | "\(.[0]),\(.[1]),51820,{PRIVATE_KEY},\(.[2])"'
So this is great news that we can still use similar automation of sorts, though we would need that private key. That's going to take some thought since that would need to be saved separately. I would love to see what some of the requirements are for some of the other VPN providers so we can try to make something like this work for everyone. So far, we know:

NordVPN - Automation capable via API / Caveat: private key needs to be saved separately
ProtonVPN - Automation not possible (yet) / Caveat: public+private keys are unique and saved in the .conf
StrongVPN - ?
Others - ?

But for now, you could technically just save that private key in the server list, eventhough it's duplicated throughout.

Lastly, can you have the Public WG IP perform a lookup to determine the IP instead of displaying the value in the input address? This is mainly a formatting issue for a private VPN server I use:
View attachment 66724
Yes... in fact, that address is factually incorrect. That's actually the endpoint IP. I'm going to see if I can just grab it's public IP instead.

Thanks for the testing on this! :)
 
Thanks for this, Proton here- just setting this up now-will report back

Edit: I have set up 2 different WG vpn's- using couple different servers on each, seems to be working
Awesome!! Glad to hear! :)
 
For Proton users comfortable with Python, you can use these two scripts to generate conf files and then a summary file to paste into VPNMON-R3 (note that you need to go to https://account.protonvpn.com/downloads to get the credential information while Developer tools is open in your browser):
That's some crazy stuff here... thanks for digging into this. But yeah, super specialized for just Proton... hoping we can find an easier way. I was thinking of finding a way of dumping all the config files into a folder, and have the script go through them and pull out the necessary info to create a server list.

But getting a mass download of config files might also be a hindrance... because, at least on the Proton side, you have to select which server you want to connect to, after which it saves your selection and notes that your public/private key are good for 1 year, and get to extend it if needed. So it keeps track of which servers you're authenticated against to use.

At this point, picking servers, grabbing their conf files, and entering that info into a server list file seems to be the easiest way to go still. :(
 
@Viktor Jaep - I submitted a PR for the develop branch, Alpha version. I added WG Server List Automation.

I used this command to build the WG server list:
Bash:
curl --silent --retry 3 --connect-timeout 3 --max-time 6 --retry-delay 1 --retry-all-errors "https://api.nordvpn.com/v1/servers/recommendations?filters\[country_id\]=228&filters\[servers_groups\]\[identifier\]=legacy_standard&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=5" | jq -r '.[] | . as $parent | .technologies | .[] | select(.identifier == "wireguard_udp") | [$parent.locations[].country.city.name, ($parent.name | split("#") | .[1]), $parent.station, .metadata[0].value] | "\(.[0]) #\(.[1]),\(.[2]),51820,{REPLACE_WITH_YOUR_PRIVATE_KEY},\(.[3])"'

It created the following sanitized output:

Code:
Miami #6610,37.120.215.35,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #9382,185.203.218.124,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #9396,185.203.218.152,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #5940,185.245.86.54,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
Miami #9394,185.203.218.148,51820,PRIVATE_KEY,e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=

Proton, sadly, doesn't allow automation and I don't have any other VPN providers I can test against besides Nord.
Fantastic! We will get this added to the automation page down the road! :) You rock, @iTyPsIDg!!
 
Yeah... I think it's best to make sure each slot functions on its own before assigning it as a monitored slot in VPNMON-R3. That would help reduce the amount of troubleshooting since we know we had a good known config and connection to start out with.

So this is not the case for me on Proton. The Public and Private keys are different for each server. This is why I determined that these were values I'd need to capture in the server list in order to correctly populate them in the WG client slots when switching from server to server.


So this is great news that we can still use similar automation of sorts, though we would need that private key. That's going to take some thought since that would need to be saved separately. I would love to see what some of the requirements are for some of the other VPN providers so we can try to make something like this work for everyone. So far, we know:

NordVPN - Automation capable via API / Caveat: private key needs to be saved separately
ProtonVPN - Automation not possible (yet) / Caveat: public+private keys are unique and saved in the .conf
StrongVPN - ?
Others - ?

But for now, you could technically just save that private key in the server list, eventhough it's duplicated throughout.


Yes... in fact, that address is factually incorrect. That's actually the endpoint IP. I'm going to see if I can just grab it's public IP instead.

Thanks for the testing on this! :)
I looked at my configuration files for two WG servers hosted by StrongVPN. The servers were in different cities. Each server configuration has a different private and public key. I'm not sure if they offer multiple servers in the same city. They do for OpenVPN. Not sure if this information will help.
 
I looked at my configuration files for two WG servers hosted by StrongVPN. The servers were in different cities. Each server configuration has a different private and public key. I'm not sure if they offer multiple servers in the same city. They do for OpenVPN. Not sure if this information will help.
Thank you for validating that, @CaptainSTX! Looks like Proton works very similarly.
 
NordVPN - Automation capable via API / Caveat: private key needs to be saved separately
ProtonVPN - Automation not possible (yet) / Caveat: public+private keys are unique and saved in the .conf
StrongVPN - ?
Others - ?
SurfShark looks like it should be possible. I see a pubKey value in the JSON:
JSON:
{
  "country": "United States",
  "countryCode": "US",
  "region": "The Americas",
  "regionCode": "AM",
  "load": 38,
  "createdAt": "2018-10-16T12:09:14+00:00",
  "updatedAt": "2025-07-15T23:56:50+00:00",
  "id": "71d5961f-4b6c-4a9c-98b7-505435e3e466",
  "coordinates": {
    "longitude": -74.0063889,
    "latitude": 40.7141667
  },
  "info": [
    {
      "createdAt": "2023-06-12T13:17:35+00:00",
      "updatedAt": "2025-07-15T23:55:07+00:00",
      "id": "ec4f4327-90a1-4d12-90cc-f9400bbd0813",
      "entry": {
        "value": "U2FsdGVkX1+p0pMo6qf//Nb44Yt9DkjUxbCHwwjxLa8="
      }
    }
  ],
  "type": "generic",
  "location": "New York",
  "connectionName": "us-nyc.prod.surfshark.com",
  "pubKey": "rhuoCmHdyYrh0zW3J0YXZK4aN3It7DD26TXlACuWnwU=",
  "tags": [
    "p2p"
  ],
  "transitCluster": null,
  "flagUrl": "https://cdn.ss-cdn.com/assets/flags/US.png"
}
Someone that has SurfShark would need to verify if the value is the same from a download.

I'm not so sure about the rest. I looked briefly based on the queries here, and nothing else returned a public key.
 
That's some crazy stuff here... thanks for digging into this. But yeah, super specialized for just Proton... hoping we can find an easier way. I was thinking of finding a way of dumping all the config files into a folder, and have the script go through them and pull out the necessary info to create a server list.

But getting a mass download of config files might also be a hindrance... because, at least on the Proton side, you have to select which server you want to connect to, after which it saves your selection and notes that your public/private key are good for 1 year, and get to extend it if needed. So it keeps track of which servers you're authenticated against to use.

At this point, picking servers, grabbing their conf files, and entering that info into a server list file seems to be the easiest way to go still. :(
Yeah, it looks like Nord made it easiest for us. The same private key is always used, and it's easy to generate once and save.
 
Yeah, it looks like Nord made it easiest for us. The same private key is always used, and it's easy to generate once and save.
Looks like the case... so I'll create an entry to save this value in order to reference it for the server list. Thanks for the sleuthing on this! :)
 

Similar threads

Support SNBForums w/ Amazon

If you'd like to support SNBForums, just use this link and buy anything on Amazon. Thanks!

Sign Up For SNBForums Daily Digest

Get an update of what's new every day delivered to your mailbox. Sign up here!
Back
Top