I aimed to create a VPN router using my Ubiquiti Edge Router ER-12 and utilise the configuration files from NordVPN. Initially, I attempted this with OpenVPN configuration files, which worked flawlessly, but unfortunately, OpenVPN resulted in poor throughput. (I have written another blog post about setting this up with OpenVPN if you need it, but I recommend using WireGuard instead.)

NordVPN’s implementation of WireGuard is known as NordLynx. However, NordVPN does not make these profiles easily accessible like Mullvad VPN does. On the NordVPN website, only OpenVPN configurations are readily available for download.

To obtain the NordLynx configuration files, I discovered two methods online.

  1. One approach is to use the method described by Daniel Rosenberg. You can find more details in his article on using WireGuard with NordVPN on an EdgeRouter here.
  2. The other method I found is a more practical solution from “MyShittyCode”, which provides a straightforward script to extract WireGuard configurations. You can read about it here.

I initially used the Daniel Rosenberg method in a Linux VM on my MacBook, which worked, but after discovering the second option from MyShittyCode, I found their script to be quite useful and decided to modify it slightly, as you can see below.

The primary step in the MyShittyCode method involves setting up NordVPN to generate an ACCESS TOKEN for use in the script. This is relatively straightforward; simply follow the steps outlined in the original blog post.

What You Need to Set in the Script

To use the script, you need to configure the following variables:

# --- Configuration Variables ---
ACCESS_TOKEN="**YOUR TOKEN HERE!!**"
TOTAL_CONFIGS=1
DNS_SERVER="103.86.96.100"
COUNTRY_NAME="Netherlands"
  • ACCESS_TOKEN: This can be obtained by following the steps in the original post.
  • TOTAL_CONFIGS: You can set this to any number of configurations you wish to generate.
  • DNS_SERVER: You can use any DNS server, but to avoid DNS leak complaints from NordVPN, use 103.86.96.100 or 103.86.99.100.
  • COUNTRY_NAME: Set this to any country where NordVPN has endpoints; you might prefer to use your current location.
	#!/usr/bin/env bash
	
	# NordVPN WireGuard Configuration Generator
	# Checks prerequisites and generates WireGuard configuration files.
	
	# --- Configuration Variables ---
	ACCESS_TOKEN="<<YOUR TOKEN HERE!!>>"
	TOTAL_CONFIGS=1
	DNS_SERVER="103.86.96.100"
	COUNTRY_NAME="Netherlands"
	
	# --- Functions for Prerequisite Checks ---
	check_command() {
	  if ! command -v "$1" >/dev/null 2>&1; then
	    echo "❌ ERROR: Required command '$1' is not installed."
	    MISSING_DEPENDENCIES=true
	  else
	    echo "✅ '$1' is installed."
	  fi
	}
	
	check_internet_connection() {
	  if ! curl -s --head https://api.nordvpn.com >/dev/null; then
	    echo "❌ ERROR: Unable to connect to NordVPN API. Please check your internet connection."
	    exit 1
	  else
	    echo "✅ Internet connection is working."
	  fi
	}
	
	check_access_token() {
	  RESPONSE=$(curl -s -u token:"$ACCESS_TOKEN" "https://api.nordvpn.com/v1/users/services/credentials")
	  if [[ $(echo "$RESPONSE" | jq -r '.nordlynx_private_key') == "null" ]]; then
	    echo "❌ ERROR: Invalid or expired ACCESS_TOKEN."
	    exit 1
	  else
	    echo "✅ ACCESS_TOKEN is valid."
	  fi
	}
	
	# --- Perform Prerequisite Checks ---
	echo "🔍 Checking prerequisites..."
	MISSING_DEPENDENCIES=false
	
	check_command curl
	check_command jq
	
	if [ "$MISSING_DEPENDENCIES" = true ]; then
	  echo "🚨 Please install the missing dependencies before running this script again."
	  exit 1
	fi
	
	check_internet_connection
	check_access_token
	
	echo "🚀 All prerequisites satisfied. Proceeding..."
	
	# --- Fetch Country ID ---
	COUNTRY_ID=$(curl -s "https://api.nordvpn.com/v1/servers/countries" | \
	jq -r ".[] | select(.name==\"$COUNTRY_NAME\") | .id")
	
	if [ -z "$COUNTRY_ID" ]; then
	  echo "❌ ERROR: Country '$COUNTRY_NAME' not found or invalid."
	  exit 1
	fi
	
	# --- Fetch Private Key ---
	PRIVATE_KEY=$(curl -s -u token:"$ACCESS_TOKEN" \
	"https://api.nordvpn.com/v1/users/services/credentials" | jq -r '.nordlynx_private_key')
	
	# --- Fetch Recommended Servers and Generate Configurations ---
	SERVER_RECOMMENDATIONS_URL="https://api.nordvpn.com/v1/servers/recommendations?&filters\[country_id\]=$COUNTRY_ID&filters\[servers_technologies\]\[identifier\]=wireguard_udp&limit=$TOTAL_CONFIGS"
	
	curl -s "$SERVER_RECOMMENDATIONS_URL" | jq -r --arg private_key "$PRIVATE_KEY" --arg dns "$DNS_SERVER" '
	.[] |
	{
	  filename: (.locations[0].country.name + " - " + .locations[0].country.city.name + " - " + .hostname + ".conf"),
	  ip: .station,
	  publicKey: (.technologies[] | select(.identifier=="wireguard_udp") | .metadata[] | select(.name=="public_key").value)
	} |
	{
	  filename: .filename,
	  config: [
	    "# " + .filename,
	    "",
	    "[Interface]",
	    "PrivateKey = \($private_key)",
	    "Address = 10.5.0.2/32",
	    "DNS = \($dns)",
	    "",
	    "[Peer]",
	    "PublicKey = " + .publicKey,
	    "AllowedIPs = 0.0.0.0/0, ::/0",
	    "Endpoint = " + .ip + ":51820"
	  ] | join("\n")
	} |
	"echo \"" + .config + "\" > \"" + .filename + "\""
	' | sh
	
	echo "🎉 WireGuard configuration file(s) successfully generated!"

Running the Script

When you run the script, you should see a smooth output like this:

✅ 'curl' is installed.
✅ 'jq' is installed.
✅ Internet connection is working.
✅ ACCESS_TOKEN is valid.
🚀 All prerequisites satisfied. Proceeding...
🎉 WireGuard configuration file(s) successfully generated! 

You will find one or more configuration files generated, depending on your settings, similar to this:

-rw-rw-r-- 1 admin admin  290 Mar 19 20:58 'Netherlands - Amsterdam - nl729.nordvpn.com.conf' 

The content of the file will resemble the following:

# Netherlands - Amsterdam - nl729.nordvpn.com.conf

[Interface]
PrivateKey = ** YOUR_PRIVATE_KEY_HERE **
Address = 10.5.0.2/32
DNS = 103.86.96.100

[Peer]
PublicKey = ** YOUR_PUBLIC_KEY_HERE **
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 213.232.87.204:51820

Installing Wireguard package on your Edgerouter

The EdgeRouter does not come with WireGuard pre-installed. You will need to install it manually. There is a helpful individual on GitHub who maintains the WireGuard packages for EdgeOS. You can find the current release here.

For my router, the ER-12, the correct package can be found here. Now, open an SSH session to your Edge router and enter the following commands:

curl -OL https://github.com/WireGuard/wireguard-vyatta-ubnt/releases/download/1.0.20211208-1/e300-v2-v1.0.20220627-v1.0.20210914.deb

sudo dpkg -i e300-v2-v1.0.20220627-v1.0.20210914.deb
  • The curl command will download the required package onto the router. Ensure you download the correct one; in my case, it is e300-v2-v1.0.20220627-v1.0.20210914.deb.
  • The dpkg command will install the package on the router.

After Obtaining the Configuration Files and package installation

Once you have obtained the configuration files and installed the package, you are ready to proceed with the next step: setting up your Edge Router with a VPN connection.

Setting up your Edge Router with a VPN connection

The script below is an EdgeOS script designed to set up your VPN tunnel for your home LAN. In the example below, it is assumed that you have VLANs and that VLAN30 is the network for all clients that need to be routed through the WireGuard tunnel wg0.

Assumptions:

  • VLAN30 is configured with the IP range 192.168.30.0/24.
  • You wish to exclude nodes 192.168.30.50, 192.168.30.51, and 192.168.30.52 from the tunnel (for example, these might be RING.COM devices that experience issues with VPNs).
  • You need to exclude nodes 104.17.127.1 (Ring oath1) and 104.17.128.1 (Ring oath2) for clients that use the wg0 tunnel. This is necessary for clients that require access to RING.COM apps, as RING.COM does not support VPNs.
  • You want to exclude subnets within RFC1918 in the wg0 VPN tunnel.
  • You aim to exclude email services from Apple and Google, as they often have issues with VPNs.

Note: Ensure you set your Private Key and Public Key in the script below, which can be found in the configuration file named “Netherlands - Amsterdam - nl729.nordvpn.com.conf”.

Note: Set the correct IP address below, found in the same configuration file. In this example, the IP address is 213.232.87.204

configure

# Wireguard configuration
set interfaces wireguard wg0 address 10.5.0.2/32
set interfaces wireguard wg0 description 'NordVPN Wireguard'
set interfaces wireguard wg0 listen-port 37728
set interfaces wireguard wg0 mtu 1420
set interfaces wireguard wg0 peer YOUR_PUBLIC_KEY_HERE allowed-ips 0.0.0.0/0
set interfaces wireguard wg0 peer YOUR_PUBLIC_KEY_HERE description nl729.nordvpn.com.conf
set interfaces wireguard wg0 peer YOUR_PUBLIC_KEY_HERE endpoint 213.232.87.204:51820
set interfaces wireguard wg0 peer YOUR_PUBLIC_KEY_HERE persistent-keepalive 25
set interfaces wireguard wg0 private-key YOUR_PRIVATE_KEY_HERE
set interfaces wireguard wg0 route-allowed-ips false
commit


# NAT-rule for traffic from 192.168.30.0/24 to VPN-tunnel
set service nat rule 5020 description 'masquerade for wg0/NordVPN'
set service nat rule 5020 log disable
set service nat rule 5020 outbound-interface wg0
set service nat rule 5020 protocol all
set service nat rule 5020 type masquerade
commit

# Allow incoming WireGuard protocol traffic on the WAN
set firewall name WAN_LOCAL rule 15 action accept
set firewall name WAN_LOCAL rule 15 description 'Allow incoming WireGuard'
set firewall name WAN_LOCAL rule 15 destination port 37728
set firewall name WAN_LOCAL rule 15 protocol udp
commit

# Static route for traffic from 192.168.30.0/24 via de VPN-tunnel
set protocols static table 1 description 'Route out via wg0/NordVPN'
set protocols static table 1 interface-route 0.0.0.0/0 next-hop-interface wg0
set protocols static table 1 route 0.0.0.0/0 blackhole distance 255
commit

# Hosts in 192.168.30.0/24 we don't want through wg0/NordVPN 
set firewall group address-group Vlan30_Hosts_NordVPN_EXCLUDE description 'Hosts in VLAN30 we dont want through wg0/NordVPN'
set firewall group address-group Vlan30_Hosts_NordVPN_EXCLUDE address 192.168.30.50	# host50
set firewall group address-group Vlan30_Hosts_NordVPN_EXCLUDE address 192.168.30.51 # host51
set firewall group address-group Vlan30_Hosts_NordVPN_EXCLUDE address 192.168.30.52 # host52
commit

# Sites on the Internet we dont want through wg0/NordVPN
set firewall group address-group Internet_Hosts_NordVPN_EXCLUDE description 'Sites on the Internet we dont want through wg0/NordVPN'
set firewall group address-group Internet_Hosts_NordVPN_EXCLUDE address 104.17.127.1 # Ring oath1
set firewall group address-group Internet_Hosts_NordVPN_EXCLUDE address 104.17.128.1 # Ring oath2
commit

# None routable subnets 
set firewall group address-group RFC1918 description 'Allow None routable subnets'
set firewall group address-group RFC1918 address 192.168.0.0/16
set firewall group address-group RFC1918 address 172.16.0.0/12
set firewall group address-group RFC1918 address 10.0.0.0/8
commit

# Mail ports (spoiler: Apple Mail and Google Mail don't like VPN's) 
set firewall group port-group Mail_ports description 'Mail poorten'
set firewall group port-group Mail_ports port 993
set firewall group port-group Mail_ports port 587
set firewall group port-group Mail_ports port 465
set firewall group port-group Mail_ports port 2525  
commit

# traffic from 192.168.30.0/24 to RFC1918 not via wg0
set firewall modify SOURCE_ROUTE rule 10 description 'traffic from 192.168.30.0/24 to RFC1918 not via wg0'
set firewall modify SOURCE_ROUTE rule 10 source address 192.168.30.0/24
set firewall modify SOURCE_ROUTE rule 10 destination group address-group RFC1918
set firewall modify SOURCE_ROUTE rule 10 action accept

# traffic from excluded hosts to 0.0.0.0/0 not via wg0
set firewall modify SOURCE_ROUTE rule 20 description 'traffic from excluded hosts to 0.0.0.0/0 not via wg0'
set firewall modify SOURCE_ROUTE rule 20 source group address-group Vlan30_Hosts_NordVPN_EXCLUDE
set firewall modify SOURCE_ROUTE rule 20 destination address 0.0.0.0/0
set firewall modify SOURCE_ROUTE rule 20 action accept

# traffic for excluded sites on Internet not via wg0
set firewall modify SOURCE_ROUTE rule 21 description 'traffic for excuded sites on Internet not via wg0'
set firewall modify SOURCE_ROUTE rule 21 destination group address-group Internet_Hosts_NordVPN_EXCLUDE
set firewall modify SOURCE_ROUTE rule 21 action accept

# traffic from MAIL ports to 0.0.0.0/0 not via wg0  
set firewall modify SOURCE_ROUTE rule 30 description 'traffic from MAIL ports to 0.0.0.0/0 not via wg0'  
set firewall modify SOURCE_ROUTE rule 30 destination group port-group Mail_ports  
set firewall modify SOURCE_ROUTE rule 30 action accept  

# Firewall rule traffic from 192.168.30.0/24 through wg0
set firewall modify SOURCE_ROUTE rule 100 description 'traffic from 192.168.30.0/24 through wg0'
set firewall modify SOURCE_ROUTE rule 100 source address 192.168.30.0/24
set firewall modify SOURCE_ROUTE rule 100 modify table 1

# Apply firewall-modification to VLAN30
set interfaces switch switch0 vif 30 firewall in modify SOURCE_ROUTE

commit
save
exit

The interface specified in the script can be configured to any interface you need. In this example, it is VLAN30 on switch0, but you can easily use eth2 or any other interface that suits your requirements.

# Apply firewall-modification to eth2
set interfaces ethernet eth2 firewall in modify SOURCE_ROUTE

Or If you want it to be applied to your complete switch0

# Apply firewall-modification to switch0
set interfaces switch switch0 firewall in modify SOURCE_ROUTE

Disabling a VPN

To disable a VPN, use the following commands:

configure

delete interfaces switch switch0 vif 30 firewall in modify SOURCE_ROUTE

commit
save
exit

Re-enabling a VPN

To re-enable a VPN after disabling it, use the following commands:

configure

set interfaces switch switch0 vif 30 firewall in modify SOURCE_ROUTE

commit
save
exit

If this blog was any help to you, give away a free Lucky Four-Leaf Clover

📝