Install EdgeVPN
EdgeVPN is a single statically compiled binary with no runtime dependencies. Pick whichever route suits the machine; if you just want the shortest path to a running network, take the install script and carry on to your first network.
Install script
The repository ships an installer at
install.sh:
curl -sfL https://raw.githubusercontent.com/mudler/edgevpn/master/install.sh | sh
It is Linux only — it aborts on any other platform. What it does:
- Detects the architecture (
x86_64,arm64,armv6,i386) and looks up the latest release tag from the GitHub API. - Downloads the matching release archive and installs the
edgevpnbinary into/usr/local/bin, usingsudowhen not running as root. If/usr/local/binis not writable and/opt/binalready exists, it uses/opt/bininstead; it will not create that directory. - Writes a service unit: a systemd template unit
/etc/systemd/system/edgevpn@.serviceif systemd is present, or/etc/init.d/edgevpnunder OpenRC.
The script installs the unit but does not enable or start anything, so nothing runs until you ask it to.
Environment variables it honours:
| Variable | Effect |
|---|---|
VERSION |
Install a specific release tag instead of the latest |
INSTALL_BIN_DIR |
Where to put the binary (default /usr/local/bin) |
INSTALL_SYSTEMD_DIR |
Where to write the unit (default /etc/systemd/system) |
DOWNLOADER |
curl (default) or wget |
ARCH, OS |
Override the detected architecture and platform |
For example:
curl -sfL https://raw.githubusercontent.com/mudler/edgevpn/master/install.sh | VERSION=v0.35.3 sh
Setting VERSION yourself is also the workaround when the GitHub API is
unavailable or rate-limits you: the script’s version lookup has no working
fallback, so a failed lookup leaves VERSION empty, the download URL is
malformed, and the install aborts on the failed download rather than
installing anything wrong.
The installer does not verify a checksum or signature on the download — the
script has a TODO where that check belongs. If that matters to you, take the
manual route and check the archive against the
edgevpn-<tag>-checksums.txt file published with the release.
Running it as a service
The systemd unit is a template, so one installation can run several networks side by side. Each instance reads its configuration from an environment file named after the instance:
# /etc/systemd/system.conf.d/edgevpn-home.env
EDGEVPNTOKEN=<your token>
ADDRESS=10.1.0.11/24
IFACE=edgevpn0
sudo systemctl enable --now edgevpn@home
The unit raises LimitNOFILE to 49152 and applies
sysctl -w net.core.rmem_max=2500000 before starting, which is the buffer-size
tuning described in troubleshooting. Create the
/etc/systemd/system.conf.d/ directory first if it does not exist — the
installer does not create it, and the unit will fail to start without the
environment file.
See the environment variables reference for everything that can go in that file.
Release archives
Every release publishes statically compiled archives on the
releases page, for Linux, macOS,
Windows and FreeBSD on amd64, arm64, arm, 386 and riscv64.
Archives are named edgevpn-<tag>-<OS>-<arch>.tar.gz — for example
edgevpn-v0.35.3-Linux-x86_64.tar.gz. A edgevpn-<tag>-checksums.txt file is
published alongside them.
tar xvf edgevpn-*-Linux-x86_64.tar.gz
sudo install -m 755 edgevpn /usr/local/bin/edgevpn
edgevpn --version
Homebrew (macOS)
If you’re using homebrew in MacOS, you can use the edgevpn formula:
brew install edgevpn
Container image
Images are published to quay.io/mudler/edgevpn for linux/amd64,
linux/arm64 and linux/arm.
latest is not the latest release. The image workflow
(.github/workflows/images.yml) pushes :latest on every push to
master, so that tag tracks the development branch. It only re-tags a
release as latest when the git tag matches ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$
— and every EdgeVPN tag is v-prefixed (v0.35.3), which never matches. Pin
to a version tag if you want a release.
So the tags you can expect are:
| Tag | What it is |
|---|---|
v0.35.3 (a release tag) |
The image built from that release |
<8-char commit SHA> |
The image built from that exact commit, on both master pushes and tag pushes |
latest |
Whatever master last pushed — a development build |
Bringing up the VPN interface from inside a container needs host networking,
the NET_ADMIN capability and the TUN device:
docker run --rm \
--network host \
--cap-add NET_ADMIN \
--device /dev/net/tun:/dev/net/tun \
-e EDGEVPNTOKEN=<your token> \
quay.io/mudler/edgevpn:v0.35.3 --address 10.1.0.11/24
Sub-commands that do not create an interface — edgevpn file-send,
edgevpn service-connect, edgevpn proxy — need none of that and run in a
plain container.
Docker Compose
There is a
docker-compose.yml
in the repository with the same settings plus a healthcheck. Using docker is
still experimental as setups can vary wildly, so you’ll likely need to edit it
— at minimum the EDGEVPNTOKEN and the volume path, both marked CHANGEME.
Note that it pins image: quay.io/mudler/edgevpn:latest, which is the
development build described above.
git clone https://github.com/mudler/edgevpn
cd edgevpn
sudo docker compose up --detach
Build from source
You need Go 1.26 (the version in go.mod), make, and
Node.js 20.19 or newer. The web interface is a React
application that is compiled and then embedded into the binary, so a JavaScript
toolchain is part of a full build.
git clone https://github.com/mudler/edgevpn
cd edgevpn
make build # compiles the web interface, then the Go binary
make build compiles the web interface first and the Go binary afterwards.
Running go build on its own works only when api/react-ui/dist already
exists — the interface is embedded with //go:embed, so a missing directory is
a compile error rather than a warning. If you are working on the Go side only,
you can stub it out:
mkdir -p api/react-ui/dist && touch api/react-ui/dist/index.html
Building the documentation site is the one thing that needs more: Hugo and
Node/npm, both of which docs/scripts/build.sh sets up for you.
cd docs && make build # one-off build into docs/public
cd docs && make serve # live preview on http://localhost:1313
See contributing for the rest of the development workflow.