I wrote this for myself because I was tired of using the terminal for VPN start and stop.
A native GNOME (GTK4 + Libadwaita) graphical frontend for
openvpn3-linux. It wraps the
openvpn3 command-line tool behind a typed, testable service layer and
exposes profile management, connections, live sessions, logs, and network
diagnostics through a polished, HIG-compliant desktop application — no
terminal required for day-to-day use.
- Features
- UI tour
- Installation
- Building
- Running
- Configuration
- Troubleshooting
- Security model
- Architecture
- Testing
- Known limitations
- License
Live connection status, connected profile and server, public IP, VPN tunnel IP, session duration, upload/download totals with a real-time bandwidth graph, DNS servers, gateway, protocol, measured latency, and tunnel interface — plus a continuously updated system-health panel (openvpn3 service reachability, D-Bus, internet connectivity, tunnel health).
- Import
.ovpn/.conffiles from disk or a URL - Export, rename, duplicate, and remove profiles (with confirmation)
- Live search across name/host/tags/notes, four sort modes (name A–Z/Z–A, last used, favorites first)
- Favorites (starred), free-form tags, and notes — stored locally alongside the profiles openvpn3-linux itself manages
- Full ACL management: public access toggle, per-user grant/revoke,
lock-down (
config-acl) - Certificate inspection with expiry warnings (via
cryptography)
Connect / disconnect / reconnect, an auto-reconnect toggle per session, a live connection transcript showing the raw CLI exchange, and an in-app connection history.
Username/password, certificate, PKCS#11 PIN, and OTP/MFA challenges are
detected on the session-start stream and answered through native
dialogs. Credentials can optionally be saved to the GNOME Keyring
(Secret Service) — never written to disk in plaintext.
Real-time tail of both the openvpn3 log stream and the app's own structured logs in one view, with plain-text or regex filtering, severity coloring, a debug-verbosity toggle, copy-to-clipboard, and export to file.
Every active session with PID, interface, owner, status, and restart count, expandable to show DNS servers and pushed routes, with per-session disconnect/restart controls. Auto-refreshes every 5 seconds.
Tunnel interface, MTU, IPv4/IPv6 addresses, pushed routes, and traffic counters for the active session, plus three built-in diagnostics: a public-IP checker, a latency test (TCP connect time to the VPN gateway), and a DNS-leak test (compares system resolvers against VPN-provided DNS).
Native GNOME notifications for connect, disconnect, errors, authentication requests, reconnects, and certificate expiry — each individually toggleable in Settings. An AppIndicator/StatusNotifierItem tray icon (when the GNOME Shell AppIndicator extension is installed) shows live status, offers quick-connect for favorite profiles, and hide-to-tray on window close.
- Connect on login (XDG autostart entry)
- Connect on boot (generated
systemd --userunit) - Reconnect when the network becomes available (NetworkManager D-Bus signal) or on resume from suspend (logind D-Bus signal)
- Cron-style scheduled reconnects
- Pre/post connect/disconnect hook scripts with custom environment variables and a persisted execution log
Theme (system/light/dark), language, per-event notification toggles,
debug logging, custom openvpn3 CLI path, developer mode (enables the
Developer Console and the plugin loader), and full settings
import/export.
Every command the app has executed against openvpn3 — full argument
vector, exit code, duration, stdout, and stderr — plus a box to run any
subcommand manually. Manual commands are tokenized and executed directly
(exec, never a shell), so the same subprocess-safety guarantees apply
here as everywhere else in the app.
Global search across profiles, sessions, settings, and commands
(Ctrl+F); keyboard shortcuts for the main pages; a small plugin system
(services/plugin_service.py) for user-supplied Python extensions; full
backup/restore archives (settings, tags/favorites/notes, exported
profiles — credentials are deliberately excluded); and per-profile
traffic history.
- One wrapper, one gate. Every
openvpn3invocation goes throughopenvpn/cli_wrapper.py— argument vectors viaasyncio.create_subprocess_exec, never a shell. No other module spawns the CLI directly. - Never runs as root. Privileged tunnel operations are performed by
the openvpn3-linux D-Bus system services, which enforce their own
PolicyKit actions. The app itself never calls
sudo/pkexec. - Defensive parsing. openvpn3-linux's text output has changed shape across releases (and even across installs of the same release — see Known limitations); the parsers degrade to partial data and log a warning rather than raising, and every observed real-world format is pinned with a regression test.
Sidebar navigation: Dashboard · Profiles · Connections · Sessions · Live Logs · Network · Settings · Developer Console. Adaptive Libadwaita layout, dark/light/system theming, toasts for background errors, and GNOME HIG-compliant widgets throughout.
The GUI is a frontend; tunnels are created by openvpn3-linux's own D-Bus system services.
sudo apt update
sudo apt install openvpn3
openvpn3 version # sanity checkIf your distribution doesn't package it, see https://openvpn.net/openvpn3-linux/ — OpenVPN's own apt repository covers current Ubuntu LTS releases.
sudo apt install devscripts debhelper dh-python pybuild-plugin-pyproject \
python3-all python3-pytest python3-pytest-asyncio
git clone https://github.com/example/openvpn3-gui && cd openvpn3-gui
cp -r packaging/debian debian
dpkg-buildpackage -us -uc -b
sudo apt install ../openvpn3-gui_0.1.0-1_all.debRuntime dependencies are declared in the package and installed
automatically: python3-gi, gir1.2-gtk-4.0, gir1.2-adw-1,
gir1.2-secret-1, python3-cryptography, openvpn3. For the tray icon,
also install gir1.2-ayatanaappindicator3-0.1 and
gnome-shell-extension-appindicator (the latter usually ships with
Ubuntu already).
If
dpkg-checkbuilddepsfails onpython3-all: either the package isn't installed (sudo apt install python3-all), or your system's defaultpython3is older than 3.12 (check withpython3 --version).debian/controldeclares the real minimum viaX-Python3-Version: >= 3.12, read bydh_python3— there's no packaging-level workaround for a genuinely older interpreter; you'll need to build on Ubuntu 24.04+ or another distribution shipping Python 3.12+.
flatpak install org.gnome.Platform//46 org.gnome.Sdk//46
cd packaging/flatpak
flatpak-builder --user --install --force-clean build-dir org.openvpn3.Gui.jsonBecause openvpn3 must run on the host (it talks to system D-Bus
services managing kernel tunnels), grant the sandbox host-spawn access:
flatpak override --user --talk-name=org.freedesktop.Flatpak org.openvpn3.GuiThen in Settings → Advanced → CLI path, enter:
flatpak-spawn --host openvpn3
./packaging/appimage/build-appimage.sh
./build/appimage/openvpn3-gui-0.1.0-x86_64.AppImageBundles the Python layer only; relies on the host's GTK4/Libadwaita
stack (present on any Ubuntu 24.04+/GNOME 46+ system) and a host
openvpn3.
sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 gir1.2-secret-1 \
python3-cryptography openvpn3
git clone https://github.com/example/openvpn3-gui && cd openvpn3-gui
python3 -m venv --system-site-packages .venv && source .venv/bin/activate
pip install -e ".[dev]"
openvpn3-gui --debug--system-site-packages is required so the venv can see the distro's
PyGObject/GTK bindings (they aren't meaningfully pip-installable without
compiling against system GTK).
# Lint
ruff check src
# Full test suite (headless, no display/D-Bus required)
PYTHONPATH=src python -m pytest src/openvpn3_gui/tests -v
# Debian package
cp -r packaging/debian debian && dpkg-buildpackage -us -uc -b
# Flatpak
flatpak-builder --user --install --force-clean build-dir packaging/flatpak/org.openvpn3.Gui.json
# AppImage
./packaging/appimage/build-appimage.shBump __version__ in src/openvpn3_gui/__init__.py,
pyproject.toml, packaging/debian/changelog, and
data/org.openvpn3.Gui.metainfo.xml together when cutting a release.
openvpn3-gui # normal launch
openvpn3-gui --debug # verbose logging (app + more detail on CLI calls)
openvpn3-gui --no-log-file # don't write to ~/.local/state/openvpn3-gui/
openvpn3-gui --minimized # start hidden in the trayKeyboard shortcuts once running:
| Shortcut | Action |
|---|---|
Ctrl+F |
Global search |
Ctrl+1…5 |
Dashboard / Profiles / Connections / Sessions / Logs |
Ctrl+, |
Settings |
Ctrl+Shift+D |
Developer Console |
Ctrl+Q |
Quit |
| What | Where |
|---|---|
| Settings (theme, notifications, automation, etc.) | ~/.config/openvpn3-gui/settings.json |
| Profile metadata (tags, favorites, notes, groups) | ~/.local/share/openvpn3-gui/profile_metadata.json |
| Traffic history | ~/.local/share/openvpn3-gui/traffic_history.json |
| App log (rotating, NDJSON) | ~/.local/state/openvpn3-gui/openvpn3-gui.log |
| Hook-script execution logs | ~/.local/state/openvpn3-gui/script-runs/*.jsonl |
| Plugins | ~/.local/share/openvpn3-gui/plugins/<name>/plugin.py |
| Credentials | GNOME Keyring only — never on disk |
All paths respect $XDG_CONFIG_HOME / $XDG_DATA_HOME /
$XDG_STATE_HOME if set.
| Symptom | Cause / fix |
|---|---|
| "openvpn3 executable was not found" | Install openvpn3-linux, or set an explicit path in Settings → Advanced → CLI path |
| No tray icon | Install gir1.2-ayatanaappindicator3-0.1 and enable the AppIndicator GNOME Shell extension |
| "Secret Service is not available" | Ensure gnome-keyring is running; credentials just won't be saved between sessions otherwise |
| Dashboard health panel shows services Unavailable | systemctl status openvpn3-session@*.service; reinstall openvpn3-linux |
| A field on Sessions/Network/Dashboard stays "—" | That field isn't printed by your installed openvpn3 version's CLI output, or requires a live tun interface the app couldn't read — see Known limitations |
| Something looks wrong in a specific page | Open the Developer Console (Ctrl+Shift+D) — it shows the exact command, exit code, and raw stdout/stderr for everything the app has run, which is the fastest way to diagnose a CLI-output mismatch |
If you hit something the Developer Console doesn't explain, run from a
terminal with openvpn3-gui --debug 2>&1 | tee ~/ovpn-debug.log and
open an issue with the log attached.
- Never runs as root; never calls
sudo/pkexec. All privileged operations go through openvpn3-linux's own D-Bus services and their PolicyKit policy. - Credentials live exclusively in the GNOME Keyring via the Secret Service API — never written to disk. Backup archives deliberately exclude them.
- All subprocess calls use argument vectors (
exec), not a shell — there is no shell-injection surface, including in the Developer Console's manual-command box. - Destructive UI actions (remove profile, disconnect, revoke ACL access) always require explicit confirmation.
ui/ GTK4/Libadwaita pages, widgets, dialogs — talks only to services/
tray/ AppIndicator/SNI quick-menu facade
services/ UI-agnostic domain logic (one class per concern)
openvpn/ THE ONLY place that spawns the openvpn3 process, plus output parsers
dbus/ Read-only D-Bus watchers (openvpn3 service presence, NetworkManager, logind)
storage/ GNOME Keyring credentials, JSON-backed settings/metadata stores
settings/ Observable settings controller
models/ Typed dataclasses shared across every layer
utils/ Logging, error types, asyncio↔GLib bridge, i18n
tests/ pytest suite + a fake openvpn3 binary for integration tests
See docs/ARCHITECTURE.md for the full design rationale (async model,
dependency injection, parsing strategy) and docs/DEVELOPER.md for a
contributor's guide.
PYTHONPATH=src python -m pytest src/openvpn3_gui/tests -vRuns headlessly — no display or D-Bus needed. Covers CLI-output parsing
(including several real-world configs-list/sessions-list formats
captured from live systems), the async subprocess wrapper (success,
failure, timeout, streaming, history) against a fake openvpn3 binary,
the full service layer with an in-memory keyring, settings/metadata
persistence, and the automation cron matcher.
- openvpn3-linux's CLI output format is not standardized across
versions, and has been observed to differ even between installs of
ostensibly similar versions (e.g.
configs-listsometimes prints a D-Bus configuration path column, sometimes doesn't). The parsers are written defensively and degrade to partial data rather than crashing, but a field genuinely unsupported by your installed CLI will show as "—" rather than being invented. - Some session details (tunnel IPv4/IPv6, MTU, DNS) aren't printed by
any confirmed openvpn3 subcommand and are instead read from the
local system for the session's
tuninterface (/sys/class/net/<if>/mtu,ip -j addr,/etc/resolv.conf). This requires the interface name to be known first (fromsessions-list) and the local tools to be present; it degrades to "—" otherwise. - Gateway and protocol are not currently populated on the Dashboard/Network pages — no confirmed CLI source for them has been found yet.
- The AppIndicator-based tray icon requires the GNOME Shell AppIndicator extension; without it, the app still runs fine, just without a tray icon (hide-to-tray silently has no visible effect if the tray isn't available).
If your openvpn3 CLI exposes any of the above through a flag not yet wired up, the Developer Console will show you the exact command and output — that's the fastest path to getting it added.
GPL-3.0-or-later.