This guide covers building and deploying amneziawg-web on a Linux host.
For a quick orientation, see the README. For production hardening details, see DEPLOYMENT.md.
The installer builds from source by default. When you run it from a repository
checkout, it auto-detects the source directory for you. It lives at the repository root next to amneziawg-install.sh:
git clone https://github.com/wiresock/amneziawg-install.git
cd amneziawg-install
# 1. Install AmneziaWG (if not already done)
sudo ./amneziawg-install.sh
# 2. Install the web panel (builds from source)
sudo ./amneziawg-web.sh installIf Rust is not installed, add --install-rust to automatically install the toolchain:
sudo ./amneziawg-web.sh install --install-rustIf you have a pre-built binary, use --binary-src instead:
sudo ./amneziawg-web.sh install --binary-src ./target/release/amneziawg-webamneziawg-web.sh install runs amneziawg-web/scripts/amneziawg-web-install.sh
internally. All installer logic lives in the sub-script.
If you download only amneziawg-web.sh, the script will shallow-clone the repository
to a temporary directory automatically before continuing. This bootstrap step requires
git to be installed and available in PATH; if git is missing, the script will
fail early with an error and exit without making changes.
The installer handles user creation, directory setup, environment file generation, and systemd service installation. See Installer reference for all options.
| Requirement | Minimum version | Notes |
|---|---|---|
| Linux | Any modern kernel | x86_64 or aarch64 |
| Rust toolchain | 1.75+ | Install via rustup or use --install-rust |
| Build space | 2 GiB | Required only when compiling from source |
| AmneziaWG | Any release | awg binary must be at /usr/bin/awg |
| SQLite | 3.x | No separate install needed — embedded in binary via sqlx |
| Reverse proxy | nginx ≥ 1.18 or Caddy 2 | Required for TLS in production |
amneziawg-web does not require a separate database server, Redis, or container runtime.
Note: If you use
--source-dirto build from source, Rust must be installed. The installer can install Rust for you with--install-rust. If you use--binary-srcto provide a pre-built binary, Rust is not required on the target host.
For source builds, the installer automatically moves Cargo artifacts away from
a small or noexec source filesystem and limits Cargo to one job on a one-vCPU
or low-memory host. Set AMNEZIAWG_BUILD_ROOT to an existing writable directory
on an executable filesystem if you need to select the build disk explicitly.
The installer can build from source automatically (see Quick install). To build manually:
# Clone the repo (or download a release tarball)
git clone https://github.com/wiresock/amneziawg-install.git
cd amneziawg-install/amneziawg-web
# Build the release binary
cargo build --release
# Verify
./target/release/amneziawg-web --versionThe compiled binary is at target/release/amneziawg-web (~10–15 MB, statically linked except for libc).
sudo install -m 0755 target/release/amneziawg-web /usr/local/bin/amneziawg-web# Dedicated non-root service user
sudo useradd --system --no-create-home --shell /usr/sbin/nologin awg-web
# Database directory (writable by service user)
sudo mkdir -p /var/lib/amneziawg-web
sudo chown awg-web:awg-web /var/lib/amneziawg-web
# Config directory for the environment file (root-owned, mode 700)
sudo mkdir -p /etc/amneziawg-web
sudo chmod 0700 /etc/amneziawg-webamneziawg-web sends privileged operations through the root-owned
/usr/local/libexec/amneziawg-web-privileged helper. The helper validates
the requested subcommand, argument count, interface/client name, peer keys,
AllowedIPs, and configuration path. Config mutations are semantic, locked,
and atomic; arbitrary root-owned configuration content is never accepted.
The service runs as a dedicated non-root user (awg-web), and sudoers grants
passwordless access only to the helper's exact executable path.
The installer (amneziawg-web.sh install) handles all of this automatically:
- Installs the helper at
/usr/local/libexec/amneziawg-web-privilegedas0755 root:root - Installs an exact-path sudoers drop-in at
/etc/sudoers.d/amneziawg-web - Validates the file with
visudo -cf(if available) - Sets permissions to
0440(required by sudoers)
If installing manually from the amneziawg-web source directory, install the
helper and create the sudoers rule:
sudo install -d -m 0755 -o root -g root /usr/local/libexec
sudo install -m 0755 -o root -g root \
scripts/amneziawg-web-privileged \
/usr/local/libexec/amneziawg-web-privileged
cat <<'EOF' | sudo tee /etc/sudoers.d/amneziawg-web > /dev/null
awg-web ALL=(root) NOPASSWD: /usr/local/libexec/amneziawg-web-privileged
EOF
sudo chmod 0440 /etc/sudoers.d/amneziawg-web
sudo chown root:root /etc/sudoers.d/amneziawg-web
sudo visudo -cf /etc/sudoers.d/amneziawg-webVerify it works:
sudo -u awg-web sudo -n /usr/local/libexec/amneziawg-web-privileged show-allManaging AWG interfaces requires CAP_NET_ADMIN, which is only
available to root. Rather than running the whole web service as root,
we grant the service user passwordless sudo for one root-owned helper. Its
allow-listed operations provide:
show-all– read tunnel state with interface private keys and peer PSKs redactedremove-peer– disable a validated peer on a validated interfacereconcile-interface– derive the trusted stripped config inside the helper, exclude a bounded list of validated disabled-peer keys, and sync itread-params– expose only the non-secret parameters needed for client generationread-server-state– expose only interface addresses, managed-client markers, validated peer public keys, and peer AllowedIPs needed for allocation and lifecycle identity checksappend-peer– validate and atomically append one reconstructed managed-peer blockremove-client– atomically remove one exact, validated managed-client blockremove-client-if-key– atomically validate a managed client's public-key identity and remove its exact block
Every operation has a fixed argument shape. Unknown subcommands, malformed
interface/client names, keys or AllowedIPs, unsafe configuration paths,
symbolic/hard links, and non-regular files are rejected before a privileged
command is invoked. Existing root-owned server private keys and PSKs are never
exposed to the service. Apart from the newly generated peer payload required by
append-peer, the helper accepts no secrets; raw config files and
caller-supplied syncconf content are never accepted. Config mutations and live
reconciliation share a stable per-interface lock.
This follows the principle of least privilege.
Important: The systemd unit does not set NoNewPrivileges=yes
because that would block the sudo escalation. All other hardening
directives (ProtectSystem=strict, ProtectHome=yes, etc.) remain
active.
| File | Purpose | Permissions |
|---|---|---|
/usr/local/libexec/amneziawg-web-privileged |
Validates privileged operations and performs locked, semantic AWG/config actions | 0755 root:root |
/etc/sudoers.d/amneziawg-web |
Allows awg-web to run only the exact privileged-helper path |
0440 root:root |
The uninstaller removes both files. The upgrader reinstalls the helper and rewrites the sudoers drop-in to keep the privilege boundary current.
Client config files are expected in AWG_CONFIG_DIR (default:
/etc/amnezia/amneziawg/clients). Each file should be a standard WireGuard/AmneziaWG
*.conf with a [Peer] PublicKey entry matching a live tunnel peer.
The service user needs read+write access to the config directory because
client creation writes new config files directly (in Rust). The installer
sets AWG_CONFIG_DIR to 0700 owned by the service user automatically.
If you need to adjust permissions manually:
sudo chown awg-web:awg-web /etc/amnezia/amneziawg/clients
sudo chmod 0700 /etc/amnezia/amneziawg/clientspython3 -c "import argon2; print(argon2.PasswordHasher().hash('yourpassword'))"Output looks like:
$argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash>
Store this in AUTH_PASSWORD_HASH. Never store the plaintext password.
sudo tee /etc/amneziawg-web/env.conf << 'EOF'
# Authentication
AUTH_ENABLED=true
AUTH_USERNAME=admin
AUTH_PASSWORD_HASH=$argon2id$v=19$m=65536,t=3,p=4$REPLACE_THIS
AUTH_SESSION_TTL_SECS=86400
AUTH_SECURE_COOKIE=true
# Optional: headless API access
# AUTH_API_TOKEN=
# Server
AWG_WEB_LISTEN=127.0.0.1:8080
AWG_WEB_DB=/var/lib/amneziawg-web/awg-web.db
AWG_CONFIG_DIR=/etc/amnezia/amneziawg/clients
AWG_POLL_INTERVAL=30
AWG_INSTALL_SCRIPT=/usr/local/bin/amneziawg-install.sh
RUST_LOG=amneziawg_web=info
EOF
sudo chmod 0600 /etc/amneziawg-web/env.conf
sudo chown root:root /etc/amneziawg-web/env.confAWG_WEB_DB accepts:
- a plain filesystem path:
/var/lib/amneziawg-web/awg-web.db(recommended) - a relative path:
awg-web.db(resolved relative toWorkingDirectory) - a SQLite URL:
sqlite:///var/lib/amneziawg-web/awg-web.db
The database file is created automatically if it does not exist (the directory must be writable by the service user).
To generate a bearer token for API access:
openssl rand -hex 32# Minimal, auth off
./target/release/amneziawg-web
# With environment file
set -a; source /etc/amneziawg-web/env.conf; set +a
./target/release/amneziawg-web
# Check it's up
curl http://127.0.0.1:8080/api/health
# → {"status":"ok"}# Copy the unit file
sudo cp packaging/amneziawg-web.service /etc/systemd/system/
# Enable the EnvironmentFile line
sudo sed -i 's|#EnvironmentFile=|EnvironmentFile=|' \
/etc/systemd/system/amneziawg-web.service
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable --now amneziawg-web
# Check status
sudo systemctl status amneziawg-web
sudo journalctl -u amneziawg-web -fThe panel should not be exposed directly on a public interface.
Use nginx or Caddy to terminate TLS and proxy to 127.0.0.1:8080.
server {
listen 443 ssl http2;
server_name awg.example.com;
ssl_certificate /etc/letsencrypt/live/awg.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/awg.example.com/privkey.pem;
# Recommended: restrict to trusted IP ranges
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}awg.example.com {
@allowed remote_ip 10.0.0.0/8
handle @allowed {
reverse_proxy 127.0.0.1:8080
}
respond "Forbidden" 403
}After adding a reverse proxy, set AUTH_SECURE_COOKIE=true in your environment file and restart the service.
A multi-stage Dockerfile is provided in the repository root.
docker build -t amneziawg-web .
docker run -d \
--name amneziawg-web \
-p 127.0.0.1:8080:8080 \
-v /var/lib/amneziawg-web:/data \
-v /etc/amnezia/amneziawg/clients:/etc/amnezia/amneziawg/clients:ro \
-e AUTH_ENABLED=true \
-e AUTH_USERNAME=admin \
-e AUTH_PASSWORD_HASH='$argon2id$...' \
-e AWG_WEB_DB=/data/awg-web.db \
amneziawg-webDocker limitations:
- The
awgbinary is not bundled in the Docker image. You must either bind-mount it from the host or use--network=hostto access the AWG kernel module. - For most deployments, running as a systemd service (steps 8–9 above) is simpler.
| Variable | Default | Description |
|---|---|---|
AWG_WEB_LISTEN |
0.0.0.0:8080 |
TCP bind address |
AWG_WEB_DB |
awg-web.db |
SQLite database path |
AWG_CONFIG_DIR |
/etc/amnezia/amneziawg/clients |
Client .conf directory |
AWG_POLL_INTERVAL |
30 |
Poll interval in seconds |
RUST_LOG |
amneziawg_web=info |
Log verbosity |
AUTH_ENABLED |
false |
Enable auth; set true in production |
AUTH_USERNAME |
admin |
Admin username |
AUTH_PASSWORD_HASH |
(empty) | Argon2id PHC string |
AUTH_API_TOKEN |
(absent) | Bearer token for API-only clients |
AUTH_SECURE_COOKIE |
false |
Add Secure flag to session cookie |
AUTH_SESSION_TTL_SECS |
86400 |
Session lifetime (seconds) |
Use the unified upgrade command so the application, privileged helper, and sudoers rule stay in sync:
sudo ./amneziawg-web.sh upgrade --source-dir ./amneziawg-web
# Or: sudo ./amneziawg-web.sh upgrade --binary ./target/release/amneziawg-webDatabase migrations run automatically on startup.
The amneziawg-web.sh install command automates the full installation process.
sudo ./amneziawg-web.sh installYou will be prompted for all important settings; press Enter to accept the defaults.
# Source-build (recommended)
HASH="$(python3 -c "import argon2; print(argon2.PasswordHasher().hash('yourpassword'))")"
sudo ./amneziawg-web.sh install \
--non-interactive \
--source-dir ./amneziawg-web \
--username admin \
--password-hash "${HASH}"
# Pre-built binary (advanced / CI)
sudo ./amneziawg-web.sh install \
--non-interactive \
--binary-src ./target/release/amneziawg-web \
--username admin \
--password-hash "${HASH}"| Option | Default | Description |
|---|---|---|
--source-dir DIR |
(auto-detected) | Build from source in this directory |
--binary-src PATH |
./target/release/amneziawg-web |
Path to a pre-built binary |
--install-rust |
(off) | Install Rust via rustup if cargo is missing |
--install-dir DIR |
/usr/local/bin |
Binary installation directory |
--data-dir DIR |
/var/lib/amneziawg-web |
SQLite database directory |
--env-file FILE |
/etc/amneziawg-web/env.conf |
Generated environment file path |
--config-dir DIR |
/etc/amnezia/amneziawg/clients |
AWG client config directory |
--host HOST |
127.0.0.1 |
Bind host |
--port PORT |
8080 |
Bind port |
--username NAME |
admin |
Admin username |
--password-hash HASH |
(required in non-interactive) | Argon2id PHC hash |
--poll-interval SECS |
30 |
Polling interval |
--session-ttl SECS |
86400 |
Session lifetime |
--no-enable |
— | Skip enabling service at boot |
--no-start |
— | Skip starting service immediately |
--force |
— | Overwrite existing env.conf without prompting |
--non-interactive |
— | Run without prompts |
--source-dirand--binary-srcare mutually exclusive. Use--source-dirto build from source, or--binary-srcto install a pre-built binary.
- Preflight checks – verifies root, systemd, AWG binary, and application binary (or source)
- Build – (source mode only) verifies Rust toolchain and runs
cargo build --release - User + directories – creates
awg-websystem user, data dir (0750), env dir (0700) - Binary install – copies binary to
--install-dir - Privileged helper – installs
/usr/local/libexec/amneziawg-web-privilegedas0755 root:root - Sudoers – installs
/etc/sudoers.d/amneziawg-web(0440 root:root) grantingawg-webpasswordless sudo for only the exact helper path - Env file – writes all runtime variables to
--env-filewith mode0600 - Service – installs systemd unit, reloads daemon, optionally enables and starts
The installer is idempotent:
- System user is not recreated if it exists
- Existing env file is preserved unless
--forceis given - Existing service unit is preserved unless
--forceis given - The privileged helper is refreshed atomically
- The helper-only sudoers drop-in is regenerated and validated before replacement
To upgrade, use the dedicated upgrade script (see Upgrade reference):
# Rebuild from source and upgrade
sudo ./amneziawg-web.sh upgrade --source-dir ./amneziawg-web
# Or upgrade with a pre-built binary
sudo ./amneziawg-web.sh upgrade --binary ./target/release/amneziawg-webThe upgrade command is available via the unified entry point:
# Rebuild from source and upgrade (recommended)
sudo ./amneziawg-web.sh upgrade --source-dir ./amneziawg-web
# Or upgrade with a pre-built binary
sudo ./amneziawg-web.sh upgrade --binary ./target/release/amneziawg-webamneziawg-web.sh upgrade runs amneziawg-web/scripts/amneziawg-web-upgrade.sh
internally.
If the web panel was installed via the standalone script and the repository files are
not present locally, amneziawg-web.sh will shallow-clone the repository to a
temporary directory automatically (source-mode upgrades will then build from that
checkout). Only the script itself needs to be present; the repository tree is
fetched on demand when missing.
The upgrade script stages and validates the application, any installer-managed lifecycle script, and both privilege artifacts before it stops an active service:
| Action | What happens |
|---|---|
| Built | (source mode) compiled from source via cargo build --release |
| Replaced | installed binary (/usr/local/bin/amneziawg-web) |
| Replaced | installer-managed AWG lifecycle script recorded by /etc/amneziawg-web/installed-awg-script.path |
| Replaced | privileged helper (/usr/local/libexec/amneziawg-web-privileged) |
| Regenerated | sudoers drop-in (/etc/sudoers.d/amneziawg-web), validated before replacement |
| Restarted | service (only if it was active before upgrade) |
| Preserved | env/config directory (/etc/amneziawg-web/) |
| Preserved | data directory (/var/lib/amneziawg-web/) |
| Preserved | systemd unit file (unless --refresh-unit is given) |
| Preserved | service user (awg-web) |
| Preserved | unmarked/operator-managed AWG lifecycle script; re-run the installer with --force to replace and register it for AWG protocol controls |
By default, the upgrade script detects whether the service was running:
- If active: all artifacts are staged, the service is stopped, helper/sudoers/application are atomically replaced, and the service is restarted
- If inactive: the same artifacts are refreshed; the service is left inactive
Use --restart to force a restart even if the service was inactive, or --no-restart
to skip restarting entirely.
sudo ./amneziawg-web.sh upgrade --source-dir ./amneziawg-webThe script prints a plan showing what will be replaced and what will be preserved, then asks for confirmation.
sudo ./amneziawg-web.sh upgrade --source-dir ./amneziawg-web --force
# or equivalently:
sudo ./amneziawg-web.sh upgrade --source-dir ./amneziawg-web --non-interactivesudo ./amneziawg-web.sh upgrade \
--source-dir ./amneziawg-web \
--force --restart
# Or with a pre-built binary:
sudo ./amneziawg-web.sh upgrade \
--binary ./target/release/amneziawg-web \
--force --restartIf the service unit file has changed in the repository, use --refresh-unit:
sudo ./amneziawg-web.sh upgrade \
--source-dir ./amneziawg-web \
--refresh-unit --forceThis reinstalls the unit file from the repository copy and reloads the systemd daemon.
The EnvironmentFile directive is automatically updated to match your --env-file path.
| Option | Default | Description |
|---|---|---|
--source-dir DIR |
(auto-detected) | Build from source in this directory |
--binary PATH |
— | Path to a pre-built replacement binary |
--install-rust |
(off) | Install Rust via rustup if cargo is missing |
--install-dir DIR |
/usr/local/bin |
Binary install directory |
--env-file FILE |
/etc/amneziawg-web/env.conf |
Env/config file path |
--data-dir DIR |
/var/lib/amneziawg-web |
Data directory |
--restart |
(off) | Always restart service after upgrade |
--no-restart |
(off) | Never restart service after upgrade |
--refresh-unit |
(off) | Reinstall systemd unit from repository copy |
--force |
(off) | Skip confirmation prompts |
--non-interactive |
(off) | Alias for --force; suitable for CI/automation |
--help |
— | Show usage |
--source-dirand--binaryare mutually exclusive. If neither is given, the script auto-detects the source directory from the repository layout.
The upgrade script assumes the same default paths as the installer. If you used
custom --install-dir, --data-dir, or --env-file during installation,
pass the same values to the upgrade script:
sudo ./amneziawg-web.sh upgrade \
--source-dir ./amneziawg-web \
--install-dir /opt/awg/bin \
--env-file /opt/awg/env.conf \
--data-dir /opt/awg/data \
--force- Resolve binary – builds from source (
--source-dir) or uses provided binary (--binary) - Validate – verifies the existing installation and source binary
- Plan – prints what will be replaced and what will be preserved
- Confirm – asks for confirmation (skipped with
--force) - Stop – stops the service if it was active
- Replace binary – copies source to temp file, then atomically moves it
- Refresh unit – (only with
--refresh-unit) reinstalls the unit file, reloads daemon - Restart – restarts the service based on restart policy
The uninstall command is available via the unified entry point:
sudo ./amneziawg-web.sh uninstallamneziawg-web.sh uninstall runs amneziawg-web/scripts/amneziawg-web-uninstall.sh
internally.
If the web panel was installed via the standalone script and the repository files are
not present locally, amneziawg-web.sh will shallow-clone the repository to a
temporary directory automatically:
# Works even if you never cloned the repository
sudo ./amneziawg-web.sh uninstallBy default, the uninstaller removes the service integration, privileged helper, and installed binary while preserving all configuration and data:
| Action | What happens |
|---|---|
| Removed | systemd service (stopped + disabled) |
| Removed | systemd unit file (/etc/systemd/system/amneziawg-web.service) |
| Removed | sudoers drop-in (/etc/sudoers.d/amneziawg-web) |
| Removed | privileged helper (/usr/local/libexec/amneziawg-web-privileged) |
| Removed | installed binary (/usr/local/bin/amneziawg-web) |
| Reloaded | systemd daemon |
| Preserved | env/config directory (/etc/amneziawg-web/) |
| Preserved | data directory (/var/lib/amneziawg-web/) |
| Preserved | service user (awg-web) |
This makes uninstall reversible — re-install with ./amneziawg-web.sh install --force
and your configuration and database are still in place.
sudo ./amneziawg-web.sh uninstallThe script prints a plan showing what will be removed and what will be preserved, then asks for confirmation before proceeding.
sudo ./amneziawg-web.sh uninstall --force
# or equivalently:
sudo ./amneziawg-web.sh uninstall --non-interactiveTo remove configuration or data, you must explicitly request it:
# Remove config + data, no prompts
sudo ./amneziawg-web.sh uninstall --purge-config --purge-data --force
# Full cleanup including service user
sudo ./amneziawg-web.sh uninstall --purge-config --purge-data --remove-user --force| Option | Default | Description |
|---|---|---|
--install-dir DIR |
/usr/local/bin |
Binary install directory |
--data-dir DIR |
/var/lib/amneziawg-web |
Data directory |
--env-file FILE |
/etc/amneziawg-web/env.conf |
Env/config file path |
--purge-config |
(off) | Also remove env/config directory |
--purge-data |
(off) | Also remove data directory and all data |
--remove-user |
(off) | Also remove the service user (awg-web) |
--force |
(off) | Skip confirmation prompts |
--non-interactive |
(off) | Alias for --force; suitable for CI/automation |
--help |
— | Show usage |
The uninstaller assumes the same default paths as the installer. If you used
custom --install-dir, --data-dir, or --env-file during installation,
pass the same values to the uninstaller:
sudo ./amneziawg-web.sh uninstall \
--install-dir /opt/awg/bin \
--data-dir /opt/awg/data \
--env-file /opt/awg/env.conf \
--force- Plan – prints what will be removed and what will be preserved
- Confirm – asks for confirmation (skipped with
--force) - Stop + disable – gracefully stops and disables the systemd service
- Remove unit – deletes the systemd unit file, reloads daemon
- Remove sudoers – deletes
/etc/sudoers.d/amneziawg-web - Remove helper – deletes
/usr/local/libexec/amneziawg-web-privileged - Remove binary – deletes the installed binary
- Purge config – (only with
--purge-config) removes the env/config directory - Purge data – (only with
--purge-data) removes the data directory - Remove user – (only with
--remove-user) removes the service user