00 What This Does
You visit a URL on your VPS. That URL triggers a chain of events that ends with your PC waking up from across the planet. No port forwarding on your home router. No exposing your LAN to the internet. Just encrypted tunnel magic and a 102-byte UDP packet that somehow brings hardware back from the dead.
your phone / laptop
public IP
web server
CGI runner
on VPS
encrypted tunnel
router on LAN
✓ finally
01 Tested Example Values
Replace these with your own. Do not use these exact values unless you want to wake up a random stranger's PC — which is rude, and also probably impossible, but still rude.
| THING | EXAMPLE VALUE |
|---|---|
| PC MAC address | xx:xx:xx:xx:xx:xx |
| VPS public IPv4 | xxx.xxx.xxx.xxx |
| WireGuard VPS tunnel IP | 10.100.0.1/24 |
| WireGuard OpenWrt tunnel IP | 10.100.0.2/24 |
| WireGuard UDP port | 51820 |
02 Prerequisites
| COMPONENT | DETAIL |
|---|---|
| VPS | Ubuntu or Debian — cloud provider of your choice |
| OpenWrt router | With internet access and SSH enabled |
| IPv6 on OpenWrt WAN | Required for the tunnel setup |
| PC | Connected by Ethernet — Wi-Fi WoL is a myth and a lie |
| WoL in BIOS/UEFI | Must be enabled. Every BIOS calls it something different. Good luck. |
| WoL in OS | Windows: Device Manager → NIC → Power Management. Linux: ethtool |
| SSH access | To both OpenWrt and the VPS |
| sudo on VPS | Or you'll be crying into your terminal |
03 Step 1 — Confirm WoL Works Locally First
▸ 1.1 Install WoL Tools on OpenWrt
SSH into OpenWrt and run:
opkg update opkg install etherwake luci-app-wol
▸ 1.2 Test Wake from OpenWrt
etherwake -i br-lan xx:xx:xx:xx:xx:xx
Not sure of your LAN interface name? Find it:
ip link
04 Step 2 — Install WireGuard
▸ 2.1 On the VPS
sudo apt update sudo apt install wireguard
If you use UFW:
sudo ufw allow 51820/udp
▸ 2.2 On OpenWrt
opkg update opkg install wireguard-tools luci-proto-wireguard kmod-wireguard
05 Step 3 — Generate WireGuard Keys
▸ 3.1 On OpenWrt
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey cat /etc/wireguard/publickey
▸ 3.2 On the VPS
wg genkey | tee privatekey | wg pubkey > publickey cat publickey
06 Step 4 — Configure WireGuard on the VPS
sudo nano /etc/wireguard/wg0.conf
[Interface] PrivateKey = <VPS_PRIVATE_KEY> Address = 10.100.0.1/24 ListenPort = 51820 [Peer] PublicKey = <OPENWRT_PUBLIC_KEY> AllowedIPs = 10.100.0.2/32
Get your VPS private key:
cat privatekey
Enable and start WireGuard:
sudo systemctl enable wg-quick@wg0 sudo systemctl start wg-quick@wg0 sudo wg
You should see interface wg0 listed. If you don't, the universe is testing you.
07 Step 5 — Configure WireGuard on OpenWrt
uci set network.wg0=interface uci set network.wg0.proto='wireguard' uci set network.wg0.private_key='<OPENWRT_PRIVATE_KEY>' uci set network.wg0.listen_port='51820' uci add_list network.wg0.addresses='10.100.0.2/24' uci add network wireguard_wg0 uci set network.@wireguard_wg0[-1].public_key='<VPS_PUBLIC_KEY>' uci set network.@wireguard_wg0[-1].endpoint_host='xxx.xxx.xxx.xxx' uci set network.@wireguard_wg0[-1].endpoint_port='51820' uci set network.@wireguard_wg0[-1].persistent_keepalive='25' uci add_list network.@wireguard_wg0[-1].allowed_ips='10.100.0.1/32' uci commit network /etc/init.d/network restart
Get your OpenWrt private key:
cat /etc/wireguard/privatekey
08 Step 6 — OpenWrt Firewall Rules
wg0 will be silently dropped
without it. You'll spend an hour wondering why everything looks correct but nothing works.
We've been there. It's not fun.
▸ 6.1 Allow Incoming WireGuard on WAN
uci add firewall rule uci set firewall.@rule[-1].name='Allow-WireGuard' uci set firewall.@rule[-1].src='wan' uci set firewall.@rule[-1].dest_port='51820' uci set firewall.@rule[-1].proto='udp' uci set firewall.@rule[-1].target='ACCEPT'
▸ 6.2 Create WireGuard Firewall Zone
uci add firewall zone uci set firewall.@zone[-1].name='wg' uci set firewall.@zone[-1].network='wg0' uci set firewall.@zone[-1].input='ACCEPT' uci set firewall.@zone[-1].forward='ACCEPT' uci set firewall.@zone[-1].output='ACCEPT'
▸ 6.3 Allow Forwarding from WireGuard to LAN
uci add firewall forwarding uci set firewall.@forwarding[-1].src='wg' uci set firewall.@forwarding[-1].dest='lan'
▸ 6.4 Apply Changes
uci commit firewall /etc/init.d/firewall restart
09 Step 7 — Test the WireGuard Tunnel
▸ From the VPS
ping 10.100.0.2
▸ From OpenWrt
ping 10.100.0.1
▸ Check Handshake
sudo wg
You want to see the OpenWrt peer listed, a recent handshake, and bytes transferred in both directions.
10 Step 8 — Create Wake Script on OpenWrt
nano /root/wake.sh
#!/bin/sh etherwake -i br-lan xx:xx:xx:xx:xx:xx
chmod +x /root/wake.sh /root/wake.sh
11 Step 9 — SSH Key Auth from VPS to OpenWrt
The web-triggered script runs as www-data. It cannot type a password.
We need passwordless SSH from VPS → OpenWrt over the WireGuard tunnel.
▸ 9.1 Generate SSH Key on the VPS
ssh-keygen -t ed25519 -f ~/oracle_emochii
This creates ~/oracle_emochii (private) and ~/oracle_emochii.pub (public).
▸ 9.2 Add Public Key to OpenWrt
mkdir -p /etc/dropbear nano /etc/dropbear/authorized_keys
Paste the contents of:
cat ~/oracle_emochii.pub
Fix permissions:
chmod 600 /etc/dropbear/authorized_keys
▸ 9.3 Test SSH from VPS
ssh -i ~/oracle_emochii root@10.100.0.2
▸ 9.4 Test Remote Wake Command
ssh -i ~/oracle_emochii root@10.100.0.2 "/root/wake.sh"
12 Step 10 — Install Nginx and fcgiwrap
sudo apt install nginx fcgiwrap sudo systemctl enable nginx sudo systemctl enable fcgiwrap sudo systemctl start nginx sudo systemctl start fcgiwrap
Allow HTTP through UFW:
sudo ufw allow 80/tcp
13 Step 11 — Create Wake Script on the VPS
sudo mkdir -p /usr/local/lib/wake sudo nano /usr/local/lib/wake/wake.sh
#!/bin/bash echo "Content-Type: text/plain" echo "" /usr/bin/ssh \ -i /usr/local/lib/wake/oracle_emochii \ -o BatchMode=yes \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ root@10.100.0.2 "/root/wake.sh" rc=$? echo "ssh exit code: $rc" if [ "$rc" -eq 0 ]; then echo "wake sent" else echo "wake failed" fi
sudo chown root:root /usr/local/lib/wake/wake.sh sudo chmod 755 /usr/local/lib/wake/wake.sh
14 Step 12 — Make SSH Key Available to the Web Script
The script runs as www-data. It can't read your home directory's private key.
Copy it somewhere accessible — but not world-readable, because we're not animals.
sudo cp ~/oracle_emochii /usr/local/lib/wake/oracle_emochii sudo chown root:www-data /usr/local/lib/wake/oracle_emochii sudo chmod 640 /usr/local/lib/wake/oracle_emochii sudo chmod 755 /usr/local/lib/wake
15 Step 13 — Configure Nginx
sudo nano /etc/nginx/sites-available/default
server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { try_files $uri $uri/ =404; } # Use a long random path — don't just use /wake location /wake-9f3a7c1b2e { include fastcgi_params; fastcgi_pass unix:/run/fcgiwrap.socket; fastcgi_param SCRIPT_FILENAME /usr/local/lib/wake/wake.sh; } }
location /wake-... block must be inside the server {} block.
Putting it outside the closing } is the #1 mistake that breaks Nginx.
Nginx's error message will be cryptic. Your frustration will not be.
Your final wake URL will be:
16 Step 14 — Validate and Reload Nginx
sudo nginx -t sudo systemctl restart nginx
17 Step 15 — Test the Whole Chain
▸ 15.1 Test Script as www-data First
This is the most important debugging step. If this works, the web trigger will work.
sudo -u www-data /usr/bin/ssh \ -i /usr/local/lib/wake/oracle_emochii \ -o BatchMode=yes \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ root@10.100.0.2 "/root/wake.sh"
▸ 15.2 Test the Local Web Endpoint
curl -i http://127.0.0.1/wake-9f3a7c1b2e
▸ 15.3 Test from Browser or Phone
18 Step 16 — Service Checks
sudo systemctl is-active nginx sudo systemctl is-active fcgiwrap sudo systemctl is-active wg-quick@wg0
Three times active. Anything else means something is broken and you have a date with journalctl.
19 Troubleshooting
▸ Nginx Fails to Restart
sudo nginx -t sudo systemctl status nginx sudo journalctl -xeu nginx.service
Usual culprit: the location block is outside the server {} block. Classic.
▸ Browser Shows 403 Forbidden
Possible causes: wrong script path, fcgiwrap can't execute the script, or the key isn't readable by www-data.
ls -ld /usr/local /usr/local/lib /usr/local/lib/wake ls -l /usr/local/lib/wake sudo tail -n 50 /var/log/nginx/error.log sudo journalctl -u fcgiwrap --no-pager -n 50
▸ Page Says "wake sent" but PC Doesn't Wake
The script in this guide prints the actual SSH exit code. If it says ssh exit code: 0 but
the PC doesn't wake, the problem is WoL itself — BIOS settings, NIC config, or a switch that
drops magic packets. Test directly:
sudo -u www-data /usr/bin/ssh \ -i /usr/local/lib/wake/oracle_emochii \ -o BatchMode=yes \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ root@10.100.0.2 "/root/wake.sh"
20 What You Now Have
| CAPABILITY | STATUS |
|---|---|
| Wake PC from anywhere on Earth | ✓ OPERATIONAL |
| No home port forwarding | ✓ CLEAN |
| WireGuard-encrypted communication | ✓ SECURED |
| WoL packet sent from inside LAN | ✓ RELIABLE |
| One-click trigger from browser/phone | ✓ DEPLOYED |
| Your home network exposed to internet | ✗ NONE |