A native terminal UI for managing WireGuard tunnels on Linux.
Built with Rust + ratatui as a single native binary. No GUI stack. No Electron.
No WebView. No NetworkManager layer. It works over SSH on minimal servers and
manages plain /etc/wireguard/*.conf tunnels through wg and wg-quick, with
root operations kept behind a small auditable Rust helper.
Prefer a desktop window? The sibling
wireguard-gui provides the same
project philosophy as a native Slint GUI.
This project is intentionally small.
It does not try to become a WireGuard platform, daemon, configuration database,
or browser dashboard. It stays close to the Linux WireGuard workflow: plain
.conf files in /etc/wireguard, wg, wg-quick, wg show, wg showconf,
wg syncconf, wg-quick save, systemd wg-quick@<name> units where available,
and the system journal.
The GUI and TUI are separate first-class tools. Install the one you want. Hack the one you want. They share a privilege model and project direction, but there is no mandatory runtime core, daemon, or hidden platform layer.
The goal is a native terminal client that is easy to use, easy to inspect, easy to fork, and still close enough to WireGuard primitives that you can understand what it is doing.
Terminal users do not need a browser dashboard just to bring a tunnel up. They also should not have to memorize scattered commands for routine operations.
wg, wg-quick, systemctl, and journalctl are powerful, but the workflow is
spread across several tools. wg-tui puts the common operator loop in one
terminal screen: tunnel list, active state, live details, logs, imports, QR
display, editing, diagnostics, and boot-time activation.
It is useful on laptops, servers, routers, SSH sessions, and minimal Linux systems where a desktop stack is the wrong dependency.
- Lists tunnels from
/etc/wireguard. - Shows active/inactive state.
- Connects and disconnects with one key.
- Shows interface public key, listen port, addresses, DNS, and start-on-boot.
- Shows peer public key, preshared-key indicator, allowed IPs, endpoint, keepalive, latest handshake, and transfer totals.
- Shows live connection health and throughput.
- Refreshes without restarting.
- Provides a Log tab backed by journald where available.
- Copies the interface public key through OSC 52 when the terminal supports it.
- Edits the selected tunnel in
$VISUAL, then$EDITOR, thennano. - Creates temp editor files mode
0600under a private user directory. - Removes temp editor files after editing.
- Validates before save.
- Saves through the helper with backups and atomic replacement.
- Creates new tunnels from Interface-only, full-tunnel, or split-tunnel generated templates in Easy Mode.
- Generates keypairs and preshared keys.
- Imports
.conffiles. - Imports QR-code images.
- Shows a terminal QR code for a tunnel.
- Exports all tunnels to
~/wireguard-tunnels.zip. - Renames and removes tunnels through helper verbs.
- Shows running config with
wg showconf. - Applies compatible saved edits to a running tunnel with
wg syncconf. - Saves live running state back to disk with
wg-quick save. - Toggles start-on-boot with systemd
wg-quick@<name>when systemd is present. - Toggles a helper-managed kill switch for active tunnels using nftables (preferred) or iptables/ip6tables; auto-allows established SSH traffic.
- Provides Easy mode for everyday actions and Advanced mode for raw operations.
- Remembers the Easy/Advanced preference under the user config directory.
wg-tui doctorprints a read-only checklist.wg-tui setupoffers confirmation-based fixes for missing prerequisites.docs/DISTROS.mdexplains client vs server/gateway setup by distro.
- No GUI dependencies.
- No desktop stack requirement.
- No NetworkManager dependency.
- No Electron.
- No WebView.
- No browser dashboard.
- No mandatory daemon or background service.
- No central runtime core shared by GUI and TUI users.
- No hidden config database.
- No bundled WireGuard kernel module.
The release page ships a static binary for every common Linux CPU - 32- and 64-bit, Intel/AMD and ARM (no glibc version to worry about):
wireguard-tui-*-x86_64-linux.tar.gz- 64-bit Intel/AMDwireguard-tui-*-i686-linux.tar.gz- 32-bit Intel/AMDwireguard-tui-*-aarch64-linux.tar.gz- 64-bit ARM (Raspberry Pi 3/4/5, …)wireguard-tui-*-armv7-linux.tar.gz- 32-bit ARM (older Pi / embedded)wireguard-tui_*_amd64.deb- Debian/Ubuntu (x86_64)SHA256SUMS,SHA256SUMS.minisig,minisign.pub- integrity + signature
Not sure which to pick? Run uname -m: x86_64 → x86_64, i686/i386 → i686,
aarch64/arm64 → aarch64, armv7l → armv7.
The first-party packages install wg-tui, wg-helper, and the authorization
rule. They do not install GUI libraries.
Fedora / RHEL / Rocky — install from COPR:
dnf copr enable jamillejung/wireguard-tui
sudo dnf install wireguard-tuigit clone https://github.com/JamilleJung/wireguard-tui.git
cd wireguard-tui
./install.shinstall.sh detects the package manager, installs wireguard-tools and build
requirements, ensures Rust when needed, builds the release binary as the
invoking user, installs the binary/helper, and configures helper authorization.
The default install stays server-friendly: no desktop entry and no icon. If you want an app-menu launcher on a workstation:
./install.sh --desktopSupported package managers:
| Distro family | Package manager |
|---|---|
| Debian / Ubuntu / Mint | apt |
| Fedora / RHEL / Rocky | dnf / yum |
| Arch / Manjaro / EndeavourOS | pacman |
| openSUSE | zypper |
| Alpine | apk |
| Void | xbps-install |
| Solus | eopkg |
Auth backend:
./install.sh # sudoers drop-in, default
./install.sh --polkit # polkit rule insteadUninstall:
./install.sh uninstallTunnel configs in /etc/wireguard are left in place.
Download the artifact you want plus SHA256SUMS. When SHA256SUMS.minisig is
attached, verify both the signature and checksum:
minisign -Vm SHA256SUMS -P RWTyrstfFCLYkpMwbcyBRl+aGGcJikl35GY1esJDO6HTEJFIMvUC8f1Q
sha256sum -c SHA256SUMS --ignore-missingThe public key is also committed as minisign.pub:
minisign -Vm SHA256SUMS -p minisign.pubIf the signature is not attached for a release, use SHA256SUMS as an integrity
check only and prefer building from source for higher assurance.
Launch:
wg-tui
# or the longer name:
wireguard-tuiCLI helpers (both names work):
wg-tui --version # or: wireguard-tui --version
wg-tui --help # or: wireguard-tui --help
wg-tui doctor # or: wireguard-tui doctor
wg-tui setup # or: wireguard-tui setupKeys in the main UI:
| Key | Action |
|---|---|
Up / k, Down / j |
Move selection, or scroll the Log tab |
Enter / a |
Activate or deactivate the selected tunnel |
n |
Create a new tunnel from an Interface-only/full/split preset |
i |
Import a .conf file or QR image |
d |
Delete the selected tunnel |
s |
Toggle start-on-boot |
Q |
Show the tunnel as a QR code |
y |
Copy the interface public key with OSC 52 |
Tab |
Switch between Tunnels and Log |
m |
Toggle Easy / Advanced mode |
r |
Refresh now |
? |
Show help |
q / Esc |
Quit |
Advanced mode also enables:
| Key | Action |
|---|---|
e |
Edit the selected tunnel in $VISUAL / $EDITOR / nano |
g |
Generate a keypair and preshared key |
c |
Show the running config with wg showconf |
+ |
Quick-add a new [Peer] section to the selected tunnel |
K |
Toggle the helper-managed kill switch for an active tunnel |
p |
Save live state with wg-quick save |
R |
Rename the selected tunnel |
x |
Export all tunnels to ~/wireguard-tunnels.zip |
Import browser:
| Key | Action |
|---|---|
Space |
Mark/unmark a file for bulk import |
Enter |
Open a directory, import highlighted file, or import marked files |
Right / l |
Enter a directory |
Left / h / Backspace |
Go up one directory |
Esc |
Cancel import |
Easy mode shows everyday actions, including creating a tunnel. Advanced mode adds raw editing, key generation, running config, kill switch, save-live, rename, and export. The mode choice is remembered.
wg-tui doctor
wg-tui setupdoctor is read-only, does not require root, and exits intentionally:
0= OK1= warnings only2= critical missing requirements
It checks:
wgwg-quick/etc/wireguard- installed helper
- helper authorization
- systemd for start-on-boot
- journald/logger availability for logs
- a working
resolvconfcommand for configs withDNS =(including the backingsystemd-resolvedservice whenresolvconfis aresolvectlshim)
setup is confirmation-based. It offers to install wireguard-tools, a
resolvconf provider, activate an installed resolver shim, and create
/etc/wireguard when missing. It does not connect tunnels, enable tunnel
start-on-boot, delete configs, or install arbitrary config files. It points you
at install.sh or packages for helper installation.
Designed with a small auditable privilege boundary:
- The TUI runs as a normal user.
- Root operations go through
wg-helper. - Authorization is scoped to the helper path, not to the TUI binary.
- Default source install uses a sudoers drop-in for the helper only.
./install.sh --polkitinstalls a polkit rule for the helper only.- If neither passwordless path is available, the app falls back to
pkexec. - The TUI can reuse a co-installed GUI helper when present.
The helper exposes fixed verbs only:
list, active, read, dump, up, down, save, rename, delete,
enable, disable, is-enabled, sync, showconf, persist, log,
killswitch-status, killswitch-enable, and killswitch-disable.
Helper hardening:
- Fixed
PATH. - Fixed
/etc/wireguardroot. - Tunnel names must match
^[A-Za-z0-9][A-Za-z0-9_.-]{0,14}$. - Names containing
.., slashes, backslashes, empty strings, or leading symbols are rejected. - No caller-controlled root destination paths.
- No
evalorsh -caround caller-controlled values. - Command calls use argv-style arguments.
- Operations that may hang are wrapped with timeouts.
- Saves and renames validate config shape in the helper before replacing files.
- Saves and renames write a temp file, set mode
0600, best-effortsync -f, and rename into place. - Overwrite, rename, and delete create timestamped backups first.
- Mutating actions are logged without private keys.
- Kill switch verbs require an active
wg-quicktunnel with a WireGuard fwmark, use iptables/ip6tables when present, and do not install a daemon.
Important WireGuard reality: wg-quick supports PreUp, PostUp, PreDown,
and PostDown, which run arbitrary commands as root when a tunnel is
activated. To keep the passwordless helper grant from becoming full root, the
helper refuses to save any config containing those hooks - so an imported or
hand-typed .conf with a PostUp line is rejected, not silently saved. If you
genuinely need a hook, edit the file under /etc/wireguard directly as root.
QR and zip export contain private keys. Treat them like the config file itself.
cargo audit (run in CI) flags two unmaintained / soundness advisories on
transitive proc-macro / optional dependencies of the ratatui terminal-UI
framework (paste, lru). Neither is exploitable, and neither ships in a
meaningful runtime path - paste is a build-time proc-macro and lru is an
optional-and-disabled crate inside ratatui, which this project does not
control. Removing them would mean replacing the entire TUI framework. So they are
documented --ignore entries in the audit gate, which still fails on any real or
new vulnerability, and Dependabot watches for upstream fixes. Every release
binary embeds an SBOM, so cargo audit bin <binary> audits exactly what was
compiled in. See SECURITY.md.
This is MIT open source. Fork it to hack on your own ideas.
| Path | Purpose |
|---|---|
src/main.rs |
App entrypoint, event loop, key handling, rendering |
src/backend.rs |
Helper client, WireGuard/system operations, QR/export |
src/config.rs |
WireGuard config parsing and validation |
src/create.rs |
Easy Mode tunnel templates and defaults |
src/clipboard.rs |
OSC52 single-field copy normalization |
src/secrets.rs |
Secret redaction and script-hook detection |
src/validation.rs |
Tunnel name validation and import-name sanitization |
src/doctor.rs |
Read-only checks and setup hints |
src/bin/wg-helper.rs |
Privileged Rust helper and fixed verb surface |
install.sh |
Distro-aware source installer |
tests/helper-validation.sh |
Shell tests for helper name validation |
docs/ |
Tutorial, distro guide, release notes |
packaging/ |
Polkit, AUR/RPM/APK/Void metadata, optional desktop assets |
.github/workflows/ |
CI and release automation |
cargo fmt --all - --check
cargo clippy --all-targets - -D warnings
cargo test
cargo build --release
bash -n install.sh
bash -n tests/installer-sanity.sh
shellcheck -S warning install.sh tests/helper-validation.sh tests/installer-sanity.sh
bash tests/helper-validation.sh target/release/wg-helper
bash tests/installer-sanity.sh
target/release/wg-tui --help
target/release/wg-tui --version
target/release/wg-tui doctorcargo run --releaseDuring development, the binary can use the in-tree helper. You can also point it at a helper explicitly:
cargo build --bin wg-helper
WG_HELPER=/absolute/path/to/wg-helper cargo runIn release builds, WG_HELPER is ignored unless WG_ALLOW_UNSAFE_HELPER=1 is
set and the target is an absolute, root-owned, non-world-writable file. That is
intentional: a helper override can become a root boundary.
Keep normal terminal actions in src/main.rs. If an action needs root, add a
fixed helper verb, validate tunnel names before filesystem access, keep paths
fixed, avoid shell expansion, create backups before destructive changes, and do
not log private keys.
- The tunnel says "active" but nothing connects (no handshake,
0 B received). Nine times out of ten your clock is wrong, not the server. WireGuard stamps every handshake with the current time; if your PC's clock leaps into the future (hello, dual-boot Windows and dying CMOS batteries), the server records that future timestamp and then — thanks to its replay protection — politely ignores every correctly-timed handshake you send afterwards. Your packets cheerfully leave the building; the server just refuses to talk to a time traveller who keeps trying to "replay" tomorrow. Fix: sync the clock (timedatectl set-ntp true, or install chrony), and if it already drifted, the server's peer needs a reset (on wg-easy:docker restart wg-easy). PressDto run Diagnose and it points right at it — a feature lovingly forged during one very long evening of staring at0 B receivedand slowly losing our minds. 🕰️🛸 wg-tui doctorreports missingwgorwg-quick: installwireguard-tools.DNS for tunnels (resolvconf)is a warning: runwg-tui setup. On RHEL/Fedora/Rocky, an installedresolvectlshim also needssystemd-resolvedactive;doctorprints the manual command.- The helper prompts every time: run
./install.shor./install.sh --polkitso the installed helper path is authorized. - Kill switch fails: the tunnel must be active and the system needs nftables, iptables, or ip6tables available.
- Start-on-boot is unavailable: the system does not provide
systemctl. - The Log tab is empty:
journalctlis missing or the system is not using journald. - The QR code is too large: enlarge the terminal or reduce font size, then press
Qagain. ydoes not copy: the terminal or multiplexer does not allow OSC 52 clipboard writes.$EDITORwith complex shell quoting is not parsed like a shell command. Use a simple editor command or wrapper script.
- Start-on-boot is systemd-only.
- The release workflow ships static tarballs for x86_64, i686, aarch64, and
armv7;
.debcoverage remains x86_64-focused. - Editing uses an external editor by design.
- Terminal QR codes can be large.
- OSC 52 copy depends on terminal support.
- The project does not bundle WireGuard tools or kernel modules.
- The kill switch is intentionally helper-managed firewall state, not a daemon or persistent firewall manager.
- More distro packages where maintainers want them (COPR, official Alpine/Void).
If wg-tui is useful to you, please give it a star on GitHub - it
genuinely helps other people discover the project and motivates further work.
👉 Star wireguard-tui on GitHub ⭐
You can also watch the repo for releases and fork it to hack on your own ideas.
This is a free, open-source project built in spare time. If it saved you some trouble and you'd like to say thanks, a coffee is hugely appreciated 💛
MIT. WireGuard is a registered trademark of Jason A. Donenfeld. This is an independent, unofficial client and is not affiliated with or endorsed by the WireGuard project.