One command to spin up your own WireGuard VPN on a fresh VPS — with QR codes, sane defaults, and friendly output.
$ sudo qvpn init
✓ Server keys generated
✓ Client config: /etc/wireguard/clients/phone.conf
✓ wg-quick@wg0 is running
✓ QR code (PNG) saved → /etc/wireguard/clients/phone_qr.png
━━━━━━━━━━━━━━━━━━━━
✓ VPN ready
━━━━━━━━━━━━━━━━━━━━You want a private VPN on your own VPS — no third-party logs, no monthly bill, no
WireGuard documentation deep-dive. qvpn packages the boring parts (keys, configs,
firewall, sysctl, systemd) behind a single CLI:
| What you used to do | What you do now |
|---|---|
| Edit two config files, run three scripts in order | sudo qvpn init |
cat, wg, awk, iptables -L to see who's on |
sudo qvpn status |
Hand-edit wg0.conf to revoke a client |
sudo qvpn remove <name> |
- A VPS with a public IP and root access
- Linux: Ubuntu 20.04+ / Debian 11+ (Fedora, RHEL, Arch supported best-effort)
- Open UDP port (default:
51820) on your provider's firewall
qvpn itself only needs bash. Server install pulls in wireguard, qrencode, and ufw.
curl -fsSL https://raw.githubusercontent.com/baymac/quick-vpn/main/install.sh | sudo bashgit clone https://github.com/baymac/quick-vpn.git
cd quick-vpn
sudo ./install.shscp qvpn root@your-vps:/root/
ssh root@your-vps
chmod +x ./qvpn
./qvpn initsudo qvpn init # set up server + first client (named "phone")
sudo qvpn add laptop # add another client; auto-assigns a free IP
sudo qvpn list # see all clients
sudo qvpn status # service health + live peer state
sudo qvpn show laptop # re-display QR + config
sudo qvpn remove phone # revoke a client cleanly
sudo qvpn teardown # remove everythingAfter init, your terminal shows a QR code. Open the WireGuard mobile app, tap
+ → Scan from QR code, enable the tunnel — done. For desktop, copy the printed
config text into a new tunnel.
| Command | What it does |
|---|---|
qvpn init |
First-time WireGuard setup + create the first client |
qvpn add <name> |
Add a new client (auto-assigns IP unless --ip) |
qvpn remove <name> |
Revoke a client and delete its files |
qvpn list |
List all clients with status |
qvpn show <name> |
Re-display QR / config; supports --qr-only, --conf-only, --save FILE |
qvpn status |
Service health + live peer table (RX/TX/handshake) |
qvpn restart |
Restart wg-quick@wg0 |
qvpn teardown |
Stop service + remove configs (use --purge for full uninstall) |
qvpn version |
Print version |
qvpn help [cmd] |
Show help (optionally per-command) |
Run qvpn help <command> for full flags on any command.
-y, --yes skip confirmation prompts
-q, --quiet suppress informational output
-v, --verbose show debug output
--dry-run print actions without executing
--no-color disable colored output (also: NO_COLOR=1)
--color WHEN always | never | auto
-c, --config FILE use this config file
-V, --version print version
-h, --help show help
qvpn works with zero configuration. To override defaults, you have three
options — listed in increasing priority:
- Built-in defaults (subnet
10.0.0.0/24, port51820, DNS1.1.1.1, 8.8.8.8) - Config file (auto-loaded from
./qvpn.confor/etc/wireguard/qvpn.conf) - CLI flags (
--listen-port,--dns,--name, …)
A config file is just bash-style key=value:
# /etc/wireguard/qvpn.conf
LISTEN_PORT="41820"
DNS_SERVERS="9.9.9.9, 1.0.0.1"
CLIENT_NAME="phone"See qvpn.conf.example for the full list.
After qvpn init, the resolved settings are persisted to /etc/wireguard/.qvpn-meta
so subsequent commands (add, status, …) don't need to re-detect or re-prompt.
sudo qvpn add phonePrints a QR code; scan it with the WireGuard mobile app.
sudo qvpn add desktop --ip 10.0.0.42Copy the printed config into the WireGuard desktop client (Manage Tunnels → Add empty tunnel).
ssh root@your-vps "qvpn show desktop --conf-only" > desktop.confsudo qvpn status── Peers ──
NAME VPN IP RX TX HANDSHAKE
──── ────── ── ── ─────────
phone 10.0.0.2 2.34 MB 18.7 MB 12s ago
laptop 10.0.0.3 1.10 GB 342.5 MB 2m ago
tv 10.0.0.4 0 B 0 B never
sudo qvpn remove phoneThis removes the peer from the live interface, persists the change to
wg0.conf, and deletes the client's keys and QR code.
sudo qvpn teardown # soft: keeps packages + IP forwarding
sudo qvpn teardown --purge # hard: also removes packagesConnection works briefly, then drops.
You're using the same client config on two devices. Each client must be unique —
add a separate one (qvpn add laptop2).
qvpn add says "IP is already in use".
Run qvpn list to see what's taken, or omit --ip to auto-assign.
"Could not auto-detect public IP".
The VPS can't reach the IP-detection endpoints. Pass it explicitly:
sudo qvpn init --server-ip 1.2.3.4.
WireGuard doesn't start after init.
sudo qvpn status # check service health
sudo journalctl -u wg-quick@wg0 -n 50Common causes: provider firewall blocks UDP/51820, or the host interface
auto-detect picked the wrong NIC. Override with --interface ens3.
Phone scans the QR but won't connect. Open the provider firewall for the WireGuard UDP port. If you're behind a NAT, you may also need port forwarding on the upstream router.
I want to change the listen port after install.
Edit /etc/wireguard/wg0.conf, run sudo qvpn restart, then re-run sudo ufw allow <new-port>/udp. (Or just sudo qvpn teardown and qvpn init --listen-port <port>.)
- Server private key lives in
/etc/wireguard/privatekey(mode600). - Client private keys live in
/etc/wireguard/clients/<name>_privatekey(mode600). - Anyone with root on the server has full access to all client configs — that's inherent to self-hosted VPNs, not specific to qvpn.
qvpn teardownwipes all keys, so you can't recover client tunnels — re-runqvpn initandqvpn addto create new ones.
sudo qvpn teardown --purge # remove WireGuard, configs, packages
sudo /path/to/install.sh uninstall # remove the qvpn binary itselfOr just sudo rm /usr/local/bin/qvpn.
qvpn is one self-contained bash script (qvpn) plus a thin installer.
Hack on it directly:
git clone https://github.com/baymac/quick-vpn.git
cd quick-vpn
bash -n qvpn # syntax check
shellcheck qvpn # static analysis (optional)
./qvpn help # smoke test (no root needed for help)
./tests/run.sh # full test suite (no root, no WireGuard)The test suite is pure bash — no external dependencies. It covers the parsers (including regression tests for the greedy-regex bug), validators, and the black-box CLI surface. Run a subset with a glob:
./tests/run.sh 'test_parser_*' # only parser tests
./tests/run.sh -v test_cli_version # verbose, single testFor end-to-end verification, test on a real VPS in a throwaway environment —
qvpn teardown --purge makes iteration cheap.
MIT