wg-bridge
Tunnel WireGuard's UDP traffic over a TCP connection (an SSH tunnel), for networks that block or throttle UDP but allow TCP/SSH.
How it works
WireGuard client -> UDP -> wg-bridge --client -> TCP -> SSH tunnel -> TCP -> wg-bridge --server -> UDP -> WireGuard server
- Client side runs next to your local WireGuard client. It binds the UDP port your WireGuard client talks to, and forwards everything over a TCP connection into an SSH tunnel.
- Server side runs near the actual WireGuard server. It listens for the TCP connection coming out of the SSH tunnel and forwards packets to the real WireGuard UDP endpoint.
You are responsible for setting up the SSH tunnel itself (e.g. ssh -L <tcp-port>:localhost:<tcp-port> user@server or a remote forward, depending on your topology) — wg-bridge only handles the UDP↔TCP relay on each end.
Each UDP datagram is sent over the TCP stream with a 2-byte length prefix, so datagram boundaries survive the byte-stream relay even when several packets arrive back-to-back (bursty traffic like a VNC/RDP session, not just isolated pings). This is a wire-format change — the client and server sides must run the same wg-bridge version; an old client cannot talk to a new server or vice versa.
Install
From PyPI
pip install wg-bridge
Or, to install it as an isolated global command-line tool rather than into a project environment (recommended for a CLI-only package like this):
pipx install wg-bridge
# or, using uv:
uv tool install wg-bridge
From GitHub (without cloning)
Installs the latest code on the default branch directly from the repository:
pip install git+https://github.com/liuweiseu/wg-bridge.git
# or:
pipx install git+https://github.com/liuweiseu/wg-bridge.git
# or:
uv tool install git+https://github.com/liuweiseu/wg-bridge.git
Pin a specific released version by appending @vX.Y.Z (see the releases page), e.g. git+https://github.com/liuweiseu/wg-bridge.git@v1.0.3.
From source
git clone https://github.com/liuweiseu/wg-bridge.git
cd wg-bridge
uv sync
Every wg-bridge ... command shown below becomes uv run wg-bridge ... when run this way (unless you've activated the project's .venv yourself).
Shell tab-completion
Once installed by any of the methods above, enable shell tab-completion (bash/zsh/fish/PowerShell) by running once from an interactive shell and restarting your terminal:
wg-bridge --install-completion
Usage
By default --server and --client detach into the background and hand control of the terminal straight back to you — no & or nohup needed. Output goes through Python's logging module to a wg-bridge.log file (the first line logged is always the working mode); use --status to check on it and --stop to stop it.
On the machine near the WireGuard server:
wg-bridge --server --wg-host <wireguard-server-ip> --tcp-port <port> [--wg-port 51820] [--log-file <path>]
$ wg-bridge --server --wg-host 192.168.88.199 --tcp-port 5980
[INFO] wg-bridge server started in background (PID 39195).
[INFO] listening on TCP 0.0.0.0:5980 -> forwarding to UDP 192.168.88.199:51820
[INFO] logs: /path/to/cwd/wg-bridge.log
$ cat wg-bridge.log
2026-09-09 23:18:47 [INFO] Working mode: server
2026-09-09 23:18:47 [INFO] Server bridge started: TCP:5980 -> UDP 192.168.88.199:51820
--log-file sets the path (directory + filename) of the log file; it defaults to ./wg-bridge.log in the current directory (its parent directory is created if it doesn't exist yet).
On the machine running the local WireGuard client:
wg-bridge --client --tcp-port <port> [--wg-port 51820] [--log-file <path>]
Add --foreground to either command to run it attached to the terminal instead (useful for debugging) — Ctrl-C stops it. In foreground mode, log lines are also echoed to the console in addition to the log file.
To stop whichever bridge process is currently running on that machine:
wg-bridge --stop
To check whether a bridge process is currently running on that machine:
wg-bridge --status
$ wg-bridge --status
[INFO] wg-bridge is running (PID 39195, mode=server)
[INFO] listening on TCP 0.0.0.0:5980 -> forwarding to UDP 192.168.88.199:51820
[INFO] logs: /path/to/cwd/wg-bridge.log
| Flag | Mode | Default | Description |
|---|---|---|---|
--server |
— | — | Run in server mode (near the WireGuard server). |
--client |
— | — | Run in client mode (next to the local WireGuard client). |
--stop |
— | — | Stop the wg-bridge process currently running on this machine. |
--status |
— | — | Show whether wg-bridge is running on this machine, and its bound host/port info if so. |
--foreground |
server/client only | — | Run attached to the terminal instead of detaching into the background. |
--log-file |
server/client only | ./wg-bridge.log |
Path (directory + filename) of the log file. |
--version |
— | — | Print the wg-bridge version and exit. |
--help / -h |
— | — | Show this help and exit. |
--install-completion |
— | — | Install shell tab-completion for the detected shell. |
--show-completion |
— | — | Print the completion script for the detected shell, without installing it. |
--wg-host |
server only | localhost |
WireGuard server address. |
--wg-port |
both | 51820 |
WireGuard UDP port. |
--tcp-port |
server/client only | (required) | TCP tunnel port; must match the SSH tunnel endpoint and be the same value on both sides. |
Exactly one of --server, --client, --stop, or --status is required.
Notes
- Each side assumes a single active WireGuard peer/session at a time; it does not multiplex multiple peers through one bridge process.
- Both sides retry automatically if the TCP connection drops.
- Starting
--serveror--clientrecords the running process's PID, its host/port settings, and its log file path in a state file under the system temp directory; starting a second instance while one is already tracked as running is refused until you--stopit.--stopsends the tracked process a termination signal and clears the state file;--statusreports it (or reports not running, cleaning up the state file first if it points at a process that's already gone — e.g. after a crash). - In the default background mode, if the process fails immediately (e.g. the TCP port is already in use), the command reports the failure and prints the last lines of
wg-bridge.loginstead of claiming success. wg-bridge.logis appended to, not truncated, across restarts — each run's own first line is alwaysWorking mode: server/Working mode: client.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wg_bridge-1.0.3.tar.gz.
File metadata
- Download URL: wg_bridge-1.0.3.tar.gz
- Upload date:
- Size: 7.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
375129282e54d3adb79e41b6585d21d459ae7f28c6876041edd50d0155874e99
|
|
| MD5 |
9780b794286e9d7270ea4cce95db8984
|
|
| BLAKE2b-256 |
8589c321a5174d63203b0d9945c7727c82e9a01892ef52dfce6f58511729284f
|
File details
Details for the file wg_bridge-1.0.3-py3-none-any.whl.
File metadata
- Download URL: wg_bridge-1.0.3-py3-none-any.whl
- Upload date:
- Size: 10.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92c1185e5b7ad22d2123902630e16f73db7552833a427369ec2160ebdb8a1f16
|
|
| MD5 |
75fc9bd4fcd8b8ba48a9ea31368dfd77
|
|
| BLAKE2b-256 |
3d828c76dd96f9372734e434dd84baeff3e02a2e9526e563d6c9dc55c0f0da85
|