Open-source project
wg-easy/wg-easy avatar
wg-easy/wg-easy

wg-easy's image builds AmneziaWG from source alongside its WireGuard admin

The easiest way to run WireGuard VPN + Web-based Admin UI.

27,485 stars2,613 forksTypeScriptAGPL-3.0

At a glance

What is it?
wg-easy packages a WireGuard tunnel and a web admin interface into one Docker container, licensed AGPL-3.0 and written in TypeScript. Reading the compose file and Dockerfile rather than the feature list is what tells you what this actually deploys: an AmneziaWG userspace build, two Linux capabilities, and two published ports with different protocols.
Who is it for?
wg-easy fits a single Linux host where you want to hand colleagues a VPN client without touching wg-quick, and where 2FA, OIDC, client expiration and Prometheus metrics are worth having in the box. Do not deploy it without deciding how the Web UI is exposed, because the project treats the reverse proxy as optional and offers a documented proxy-less guide, which leaves the authentication decision to you.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • wg-easy/wg-easy27,485 ★ · +508
Star history
Repository2026-09-212026-11-13
wg-easy/wg-easy27k27.5k

508 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The image compiles AmneziaWG from a pinned branch and patches wg-quick

The feature list says WireGuard and lists experimental AmneziaWG support, and the Dockerfile shows what that means in the build.

The build stage clones two repositories rather than installing a package. `AWGTOOLS_BRANCH` is set to `v3.1.20260812` and points at `amnezia-vpn/amneziawg-tools`, while `AWGGO_BRANCH` is set to `v3.1.20260828` and points at `amnezia-vpn/amneziawg-go`. Both are cloned with `--depth 1 --branch` and built with `make`, which means the image carries amneziawg-go and amneziawg-tools.

The build also patches the upstream helper script. A `sed` substitution rewrites a line in `amneziawg-tools/src/wg-quick/linux.bash` so that `net.ipv4.conf.all.src_valid_mark=1` is set only when the current value is not already 1. That makes the helper idempotent, which matters when a script runs repeatedly against the same host.

What the README does not do is describe how AmneziaWG's behaviour differs from what you would get from a stock install, or offer a build without it. If your expectation is the tunnel you already run elsewhere, verify that assumption before handing out client configs.

The reverse proxy is optional, and the fallback guide is named reverse-proxyless

The installation section walks through Docker, then says to set up a reverse proxy to be able to access the Web UI securely from the internet, and immediately adds that this step is optional, with a pointer to a separate guide for people who decide not to do it.

That sentence is the security decision of this project in one line. The Web UI is the panel that creates peers, hands out configuration files and shows per-client traffic, and the project supports 2FA, OAuth for Google and GitHub, and generic OIDC providers such as Authelia and Authentik, so authentication inside the app is possible. What the reverse proxy adds is transport-level protection and a place to enforce access before the app sees a request at all.

If you skip it, the `HOST` setting is what determines reachability. The compose file carries a commented block showing `PORT=51821`, `HOST=0.0.0.0` and `INSECURE=false`, and those are the three values a reviewer should look at before starting the container.

Dedicated guides exist for Caddy, Traefik and AdGuard Home, so the intended production shape is a proxy in front. The proxy-less path exists, and choosing it means you are the one who has to justify the exposure.

NET_ADMIN and SYS_MODULE with a read-only /lib/modules bind is the privilege ask

The compose file adds two Linux capabilities to the container, and the second one is the interesting one.

`NET_ADMIN` is unsurprising: a VPN needs to configure interfaces, routes and firewall rules. `SYS_MODULE` is the ability to load kernel modules, and alongside it the container bind-mounts the host's module directory as `/lib/modules:/lib/modules:ro`. The read-only flag limits what the container can write, but the capability is what lets it interact with the kernel's module interface at all.

The file also sets five sysctls that have to be right for routing to work: `net.ipv4.ip_forward=1`, `net.ipv4.conf.all.src_valid_mark=1`, `net.ipv6.conf.all.disable_ipv6=0`, `net.ipv6.conf.all.forwarding=1` and `net.ipv6.conf.default.forwarding=1`. Without the forwarding flags the container builds a tunnel that carries no traffic.

Networking is given a fixed address rather than left to Docker's allocator: the service sits at `10.42.42.42` and `fdcc:ad94:bacf:61a3::2a` on a bridge network with subnets `10.42.42.0/24` and `fdcc:ad94:bacf:61a3::/64`, and IPv6 is explicitly enabled on that network.

Per-client firewall filtering, one of the advertised features, requires iptables and therefore depends on `NET_ADMIN` being granted. On a host where your security policy forbids these capabilities, this deployment will not start.

Two published ports on two protocols, and nothing in the compose file labels them

The service publishes exactly two things:

code
51820:51820/udp
51821:51821/tcp

One is UDP and one is TCP, which is the shape you would expect for a tunnel listener alongside a web interface, but the compose file does not say which is which, and neither does the README. What you can pin down from the files is the TCP port, because the commented environment block sets `PORT=51821`, tying 51821 to the Web UI.

The container's healthcheck ties the other end of the picture to the tunnel, and it is a direct shelling out rather than an HTTP probe:

code
HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1"

The container is healthy when `wg show` reports an interface. That is a meaningful signal about the tunnel, and it means an orchestrator will restart a container whose WireGuard interface has disappeared even when the web server is perfectly fine.

For firewall rules, treat both as required rather than guessing which one is optional, and keep 51821/tcp off the public internet unless you have put a proxy in front of it.

The root package.json claims version 1.0.0, because it is a build harness and nothing else

The root `package.json` is worth reading as an artifact in its own right. It declares `"version": "1.0.0"`, `"private": true`, and it has no dependencies at all, only one devDependency, `prettier` at `^3.9.10`. The current release is v15.5.0, so the number in this file tracks nothing you would recognise as a product version.

The real work is in `src/`, which is a separate workspace. The Dockerfile copies `src/package.json`, `src/pnpm-lock.yaml` and `src/pnpm-workspace.yaml` before running `pnpm install`, then copies the rest of `src` and runs `pnpm build`. So there are two npm trees, and the outer one is glue.

The scripts show how much of the workflow is delegated to Docker. `dev` is `docker compose -f docker-compose.dev.yml up wg-easy --build --watch`, `cli:dev` is a `docker compose run --build --rm -it wg-easy cli:dev`, and `build` is `docker build -t wg-easy .`. Even previewing the documentation runs a container, through `docs:preview`, which serves the `docs` directory on port 8080 with zensical.

`packageManager` is pinned to `[email protected]`.

Development needs Docker running, because every script shells out to compose

The development prerequisites are short: Docker, Node LTS with corepack enabled, and Visual Studio Code. What they imply is longer than they look.

Both documented development commands are described as starting with Docker. The dev server is:

code
pnpm dev

and the CLI is:

code
pnpm cli:dev

Neither runs the TypeScript directly. Both invoke `docker compose` against `docker-compose.dev.yml` with `--build`, and the CLI variant adds `--rm -it` so the container is thrown away when the session ends. That means the inner loop is a container rebuild with a watch mount, and there is no documented path for running the server on the host without Docker.

There is a separate `Dockerfile.dev` and a `docker-compose.dev.yml` in the repository, which is how the containerized loop is kept distinct from the production image.

One more developer-only detail worth copying into your own notes. If you add something that should be auto-importable and your editor complains about it, the fix is to reinstall inside the source workspace and step back out:

code
cd src
pnpm install
cd ..

AGPL-3.0-only, plus a disclaimer that the project is not WireGuard

The license is AGPL-3.0-only, and for a network-facing admin panel that is the part to think through rather than the copyright.

The AGPL reaches users of a modified version over a network, which is precisely the situation wg-easy creates: a Web UI that people connect to from outside. If you run this internally for a company, that is ordinary use. If you intend to change it and offer it to others, you owe them the corresponding source, and the only-for-internal-use framing does not hold.

The README also carries a disclaimer that is easy to skim and useful to keep: the project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security, and "WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld.

So the name on the dashboard is not yours to rebrand. Combined with the AmneziaWG build in the Dockerfile, the picture is of a community project that presents itself as an interface for WireGuard rather than as a WireGuard distribution.

The documentation lives on its own site rather than in the repository README, which is why the README is mostly a list of links to Getting Started, Basic Installation and the per-proxy tutorials.

Editorial conclusion

wg-easy fits a single Linux host where you want to hand colleagues a VPN client without touching wg-quick, and where 2FA, OIDC, client expiration and Prometheus metrics are worth having in the box. Do not deploy it without deciding how the Web UI is exposed, because the project treats the reverse proxy as optional and offers a documented proxy-less guide, which leaves the authentication decision to you. Verify three things before the first client connects: whether the AmneziaWG build in the image is the behaviour you expect, whether your host allows the NET_ADMIN and SYS_MODULE capabilities with `/lib/modules` bind-mounted read-only, and whether 51821/tcp stays off the public internet. The current release is v15.5.0, published 2026-10-24.

Frequently asked questions

What is wg-easy?

It is a web-based admin interface for WireGuard packaged as a single Docker container, described as an all-in-one combination of WireGuard and a Web UI. Clients can be listed, created, edited, deleted, enabled and disabled, with QR codes and downloadable configuration files.

How do I install wg-easy?

Install Docker first by running curl -sSL https://get.docker.com | sh as root, then log in again. Run WireGuard Easy with Docker Compose, following the basic installation tutorial; docker run and podman are documented as alternatives.

How do I access the wg-easy Web UI?

The compose file publishes 51820 on UDP and 51821 on TCP, and the commented environment block sets PORT=51821. The project tells you to put a reverse proxy in front to reach the UI securely from the internet, and calls that step optional with a separate guide for going without one.

Is wg-easy secure?

The container is granted NET_ADMIN and SYS_MODULE and bind-mounts the host module directory read-only, because it manages a WireGuard interface. The project itself adds 2FA support, OAuth for Google and GitHub, and generic OIDC providers such as Authelia and Authentik, and treats the reverse proxy as an optional step rather than a requirement.

How do I update wg-easy?

The published image is referenced as ghcr.io/wg-easy/wg-easy:15, so the tag tracks a major version rather than a specific build. The repository keeps a CHANGELOG.md, and a scripts:version script backed by bash scripts/version.sh handles versioning in development.

Official sources

  1. License: AGPL-3.0
  2. Project website
  3. README
  4. Releases
  5. wg-easy/wg-easy on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wg-easy-wg-easy.svg)](https://hysenlabs.com/projects/wg-easy-wg-easy)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/wg-easy-wg-easy.svg)](https://hysenlabs.com/projects/wg-easy-wg-easy)

Was this useful?