| .github | ||
| assets | ||
| docker | ||
| docs | ||
| mobil | ||
| scripts | ||
| src | ||
| .dockerignore | 161 B | |
| contributing.md | 5.9 KB | |
| deploy.sh | 8.2 KB | |
| destroy.sh | 7.2 KB | |
| docker-compose.dev.yml | 613 B | |
| docker-compose.https.yml | 1.7 KB | |
| docker-compose.yml | 1.7 KB | |
| Dockerfile | 4.0 KB | |
| Dockerfile.dev | 1.2 KB | |
| LICENSE | 33.7 KB | |
| package.json | 633 B | |
| pnpm-lock.yaml | 431 B | |
| README.md | 9.1 KB |
WireGuard admin panel with a built-in STUN obfuscator. Traffic from the client to the server is masked as STUN and XOR-encoded — to bypass DPI in regions with blocks.
node and wg-obfuscator as independent long-running services.name: phoboswg
volumes:
phobos_etc_wireguard:
phobos_sqlite_data:
phobos_certs_data:
services:
phobos:
image: ghcr.io/ground-zerro/phobos:latest
container_name: phobos
ports:
- "51822-51921:51822-51921/udp"
- "51831:51831/tcp"
volumes:
- phobos_etc_wireguard:/etc/wireguard
- phobos_sqlite_data:/app/server/data
- phobos_certs_data:/app/certs
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
Open http://<host>:51831, complete the initial setup, create a client.
Run one command on a fresh server:
curl -fsSL https://raw.githubusercontent.com/Ground-Zerro/Phobos/main/deploy.sh | sudo bash
Script behavior:
ghcr.io/ground-zerro/phobos,After deployment open http://<WG_HOST>:51831/ — the wizard guides through:
Optional parameters:
WG_HOST=<PUBLIC_IP_OR_DOMAIN> \
WG_EASY_IMAGE=ghcr.io/ground-zerro/phobos:latest \
curl -fsSL https://raw.githubusercontent.com/Ground-Zerro/Phobos/main/deploy.sh | sudo bash
To skip the wizard and pre-configure admin credentials via environment:
INIT_ENABLED=true \
INIT_USERNAME=admin \
INIT_PASSWORD=YourPassword \
WG_HOST=<PUBLIC_IP_OR_DOMAIN> \
curl -fsSL https://raw.githubusercontent.com/Ground-Zerro/Phobos/main/deploy.sh | sudo bash
Ports:
http://<WG_HOST>:51831/UDP <WG_HOST>:51822-51921 (one port per preset, allocated from this range)Client device Server (Docker)
───────────── ────────────────
app ─► 127.0.0.1:13255 0.0.0.0:<preset.extPort>
(local WG client) (wg-obfuscator instance for this preset)
│ │ STUN unwrap + XOR decode
│ plain WG ▼
▼ 127.0.0.1:51820 (wg0, loopback-only)
127.0.0.1:13255 │
(wg-obfuscator client) │ WG
│ ▼
│ XOR + STUN wrap FORWARD → internet
▼
UDP → <server_ip>:<preset.extPort>
A single wg-obfuscator process loads /run/wg-obfuscator.conf, where the server holds one [preset-<id>] section per active preset; the binary forks one listener per section. Each listener forwards decoded traffic to the same loopback WireGuard port — so the WG side stays unaware of the preset selection.
Presets let you serve different clients (or groups of clients) through obfuscator listeners with independent parameters on the same WG interface.
What's in a preset:
name — free-form label shown in the admin UI;extPort — public UDP port the listener binds to, must be in 51822–51921 (the range docker-compose publishes by default);key — XOR key shared with this preset's clients only;masking — STUN / AUTO / NONE;idle — idle session timeout (seconds);dummy — max dummy bytes per packet;clientWgLocalPort — local WG port on the client side (matters when several Phobos profiles coexist on one router);isDefault — exactly one preset has this flag; it's used by every client that has no explicit assignment.Why use it:
STUN with light padding, hold a stricter AUTO-mode preset on a different port as backup.Admin UI — Admin Panel → Obfuscator presets:
Add preset — Node picks the next free port in the range (or you supply one), generates a fresh key, applies the config and restarts the listener (single s6-svc -r).Set as default — moves the default flag in a single transaction; new and unassigned clients immediately use this preset.Regenerate key / Regenerate port — rotates the value and reapplies.Delete — only allowed for non-default presets; clients pointing at it fall back to default automatically (ON DELETE SET NULL).Per-client assignment:
/clients/<id>) has the same dropdown — switching it produces a new package download that targets the chosen preset's port/key.Lifecycle:
Obfuscator.applyAll(): it regenerates /run/wg-obfuscator.conf from the DB and issues s6-svc -r /run/service/wg-obfuscator. The single restart re-forks all listeners; existing sessions reconnect via idle-timeout.setsid so all forked child listeners share a process group; finish follows up with pkill -f /usr/local/bin/wg-obfuscator to guarantee no zombie listeners survive a stop/restart.curl -sL http://<origin>/api/install/<token> | sh
wg-obfuscator and configures WireGuard.The downloaded WireGuard config carries the preset's parameters as an [instance] section appended to the standard [Peer] block, so the client obfuscator targets the right server port with the right key.
pnpm dev
AGPL-3.0-only — see LICENSE.
PhobosWG is a fork of wg-easy © 2021 Emile Nijssen, licensed AGPL-3.0-only. The fork carries the same license: -only means it cannot be relicensed under GPLv3, nor upgraded to a later AGPL version.
AGPL section 13 applies to every deployed instance: users who interact with a modified PhobosWG over a network must be offered the Corresponding Source of the version actually running. If you modify PhobosWG and run it as a service, publishing your modified source is an obligation, not a courtesy — this is what AGPL adds over GPLv3, where the obligation is triggered only by distributing binaries.
wg-obfuscator © ClusterM is licensed GPL-3.0 and runs as a separate process supervised by s6, not linked into the panel. Its sources are kept in src/phobos-obfuscator/; the binaries shipped to clients in the Phobos package are built from them.
This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security. "WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld.