A tiny TCP reverse proxy that wakes sleeping machines via Wake-on-LAN before
forwarding the connection - so ssh user@ssh.yourdomain.com just works, even
when the target machine is powered off.
Inspired by go-wol-proxy, which
does the same thing for HTTP. go-wol-ssh works at the TCP layer, so it can
wake machines on any port-based protocol (SSH, RDP, VNC, etc.) - no client-side
ProxyCommand or special app support required.
ssh user@ssh.domain.com -p 2222
│
▼
┌───────────────────────────────┐
│ go-wol-ssh (Docker container) │
│ │
│ 1. Dial target:22 (2s) │
│ └─ already up? proxy now │
│ 2. Send WOL magic packet │
│ 3. Poll target:22 until up │
│ 4. io.Copy both ways │
└───────────────────────────────┘
│
▼
target machine:22
Each machine gets its own listening port. Connect to ssh.domain.com:2222 for
machine A, ssh.domain.com:2223 for machine B, and so on.
Create config.yaml (see config.example.yaml):
listen_host: "0.0.0.0"
wake_timeout: 120 # seconds to wait for the machine to wake
poll_interval: 3 # seconds between reachability checks
keepalive_packets_interval: 30 # seconds between WOL keepalive packets
machines:
- label: "Gaming PC"
port: 2222 # the port clients connect to
ip: "192.168.100.2" # target machine IP
mac: "aa:bb:cc:dd:ee:ff" # target MAC for the magic packet
broadcast: "192.168.100.255" # subnet broadcast address
ssh_port: 22 # port to proxy/probe on the target
wol_port: 9 # WOL UDP port (7 or 9)
keepalive_packets: true # send periodic WOL packets while connected
on_disconnect: "curl -s ntfy.sh/your-topic -d 'Gaming PC session ended'" # optional: shell command run on the proxy host after each session ends
- label: "Workstation"
port: 2223
ip: "192.168.100.3"
mac: "11:22:33:44:55:66"
broadcast: "192.168.100.255"
ssh_port: 22
wol_port: 9
keepalive_packets: false| Field | Required | Default | Description |
|---|---|---|---|
listen_host |
no | 0.0.0.0 |
Interface to bind all listeners on |
wake_timeout |
no | 120 |
Seconds to wait before giving up on a wake |
poll_interval |
no | 3 |
Seconds between reachability checks |
keepalive_packets_interval |
no | 30 |
Seconds between WOL keepalive packets when keepalive_packets is enabled |
machines[].label |
yes | - | Human-readable label (used in logs) |
machines[].port |
yes | - | Port clients connect to; must be unique per machine |
machines[].ip |
yes | - | Target machine's LAN IP |
machines[].mac |
yes | - | Target machine's MAC (colon or dash separated) |
machines[].broadcast |
yes | - | Subnet broadcast address for WOL |
machines[].ssh_port |
no | 22 |
Port to probe and proxy on the target |
machines[].wol_port |
no | 9 |
UDP port for WOL magic packet |
machines[].keepalive_packets |
no | false |
Send a WOL packet every keepalive_packets_interval seconds while at least one connection is active. If the machine suspends mid-session, this re-wakes it within one interval so the SSH connection can resume without a manual reconnect. |
machines[].on_disconnect |
no | - | Shell command (sh -c) run on the proxy host (not the target machine) after a session ends. Only fires when a connection was successfully proxied; skipped if the machine never woke up. Runs in the background so it does not block new connections. |
network_mode: host is required so UDP broadcasts reach the LAN. This works
fine inside an unprivileged Proxmox LXC container with features: nesting=1
(same setup as go-wol-proxy).
docker-compose.yml:
services:
ssh-wol:
image: ghcr.io/karamanliev/go-wol-ssh:latest
network_mode: host
restart: unless-stopped
volumes:
- ./config.yaml:/etc/ssh-wol/config.yaml:roRun it:
docker compose up -d
docker compose logs -fUpdate to the latest image:
docker compose pull && docker compose up -dPoint ssh.yourdomain.com at the host (LXC container, VM, etc.) running
go-wol-ssh. Pick whichever local DNS resolver you use.
- Open the Pi-hole admin UI.
- Go to Local DNS → DNS Records.
- Add a record:
- Domain:
ssh.yourdomain.com - IP Address:
<IP of the machine/LXC running go-wol-ssh>
- Domain:
- Click Add.
That's it - Pi-hole picks it up immediately. Verify with:
dig @<pihole-ip> ssh.yourdomain.com +short- Open the AdGuard Home admin UI.
- Go to Filters → DNS rewrites.
- Click Add DNS rewrite:
- Domain:
ssh.yourdomain.com - Answer:
<IP of the machine/LXC running go-wol-ssh>
- Domain:
- Click Save.
Verify with:
dig @<adguard-ip> ssh.yourdomain.com +shortFrom any SSH client - terminal, Termius on iOS, VS Code Remote, etc.:
ssh -i ~/.ssh/mykey -p 2222 user@ssh.yourdomain.com # Gaming PC
ssh -i ~/.ssh/mykey -p 2223 user@ssh.yourdomain.com # WorkstationFirst connection after a cold boot takes ~10–30s (the time for the machine to
POST and sshd to start). Follow-up connections are immediate.
Optional ~/.ssh/config convenience (not required, just ergonomic):
Host pc
HostName ssh.yourdomain.com
Port 2222
User myuser
IdentityFile ~/.ssh/mykey
ServerAliveInterval 30
Host workstation
HostName ssh.yourdomain.com
Port 2223
User myuser
IdentityFile ~/.ssh/mykey
ServerAliveInterval 30
Then just ssh pc or ssh workstation.
go build -o ssh-wol .
./ssh-wol ./config.yamlOr build the Docker image:
docker build -t go-wol-ssh .
docker run --rm --network host -v "$PWD/config.yaml:/etc/ssh-wol/config.yaml:ro" go-wol-sshMIT