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.
Metadata
Release files for wg-bridge 1.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| wg_bridge-1.0.3.tar.gz | 7.5 kB | Without provenance | Details for wg_bridge-1.0.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wg_bridge-1.0.3-py3-none-any.whl | Python 3 | none | any | Details for wg_bridge-1.0.3-py3-none-any.whl |
Total release size: 18.0 kB
Release files / wg_bridge-1.0.3.tar.gz
| Download URL | wg_bridge-1.0.3.tar.gz |
|---|---|
| Size | 7.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
375129282e54d3adb79e41b6585d21d459ae7f28c6876041edd50d0155874e99
|
|
BLAKE2b-256 checksum How to use checksums |
8589c321a5174d63203b0d9945c7727c82e9a01892ef52dfce6f58511729284f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / wg_bridge-1.0.3-py3-none-any.whl
| Download URL | wg_bridge-1.0.3-py3-none-any.whl |
|---|---|
| Size | 10.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92c1185e5b7ad22d2123902630e16f73db7552833a427369ec2160ebdb8a1f16
|
|
BLAKE2b-256 checksum How to use checksums |
3d828c76dd96f9372734e434dd84baeff3e02a2e9526e563d6c9dc55c0f0da85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Security for wg-bridge 1.0.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.