Self-hosted service
DigneZzZ/remnawave-scripts avatar
DigneZzZ/remnawave-scripts

remnawave-scripts, one-line installs for a Remnawave VPN panel

Collection of automation scripts and utilities for RemnaWave project. Features deployment tools, configuration management, and workflow automation scripts to streamline development and operations

597 stars65 forksShellMIT

At a glance

What is it?
This repository is a set of self-updating bash scripts that install and operate Remnawave, the panel and node software behind a Xray-based VPN, plus Caddy masking, WARP and Tor outbound, and NetBird mesh VPN. It is operationally opinionated, and it takes over the configuration files on the machine it runs on.
Who is it for?
remnawave-scripts suits an operator who has decided to run Remnawave and wants the panel, the node, the reverse proxy and the outbound stack installed and updated in a consistent way rather than assembled by hand. It does not suit a server where you need to keep control of your own configuration files, and it does not suit a first-time operator, since the scripts are written for someone who already understands the panel and the node.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

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

Editorial analysis

Five scripts, each a self-updating command

The repository is a collection of automation scripts and utilities for the Remnawave project, and the README describes the result as one-liner installs and a full-featured CLI for the Remnawave Panel, RemnaNode, Reality masking, WARP and Tor, and enterprise-grade backups, with Docker-based operation, a bilingual English and Russian interface, idempotent operations and self-updates.

The scripts are listed in a table with their versions and purposes.

| Script | Version | Purpose | |---|---|---| | remnawave.sh | 6.8.0 | Panel: install, Caddy, backups, subscription-page | | remnanode.sh | 4.6.0 | Node: Xray-core, logs, auto-restart | | selfsteal.sh | 2.11.1 | Caddy masking for Reality, 11 website templates | | wtm.sh | 1.5.2 | WARP + Tor: WireGuard outbound for Xray, WARP+ | | netbird.sh | 2.0.0 | NetBird mesh VPN: CLI, cloud-init and Ansible |

Each script installs itself as a global command. After installation, `remnawave`, `remnanode` and `selfsteal` are commands on the system, and running one without arguments opens an interactive menu.

The self-update behaviour is the design decision that matters most. Every script keeps itself up to date: it checks its own version on `update` and when the menu opens, applies a newer one without asking, and re-runs your command. Downloads try GitHub first and then jsDelivr mirrors.

That last property is what makes these scripts different from a runbook. A runbook tells you what to type; these scripts tell you what to type and then change themselves afterwards, which is convenient and also the first thing to think carefully about before you run one on a server you care about.

The licence is MIT, with a LICENSE file at the root, and the last push to the repository was on 2026-10-11.

The install is a pipe into bash, and there is a CLI-only variant

The quick start is three commands, one per component.

bash
# Remnawave Panel
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install

# RemnaNode
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnanode.sh) @ install

# Caddy Selfsteal — Reality masking
bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/selfsteal.sh) @ install

The shape is worth understanding before you paste it. The script is downloaded with curl and piped straight into bash, so the code that runs is whatever is at that URL at that moment, and the `@ install` argument is passed as a positional parameter to it.

There is a second mode, and it exists for a specific reason. The README asks whether you want only the CLI without installing anything else, and the answer is to swap `install` for `install-script`, which just drops the command into `/usr/local/bin`.

bash
sudo bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install-script

The documentation gives the reasons: it is handy on a server you manage remotely, or to grab the newest CLI right now. The `install-script` and `update-script` commands only install or refresh the CLI itself and touch no containers, and `uninstall-script` removes the CLI from `/usr/local/bin`.

There is a third path for servers where GitHub is blocked, and it is not a fallback bolted on afterwards. Every script mirrors itself through jsDelivr, so you can use the CDN URL instead of the github.com raw URL, and once installed the scripts fall back to those mirrors on their own, including for self-updates.

The mirror URL has the form `https://cdn.jsdelivr.net/gh/DigneZzZ/remnawave-scripts@main/<script>.sh`, with the script name standing in for the file.

So the operational model is: pick a mirror you can reach, install the CLI, and the CLI keeps itself current from whichever mirror answers.

Generated files are marked and left alone when you edit them

The panel script's most interesting behaviour is how it treats configuration files you have edited by hand, and it is worth reading the documentation on this closely because it is unusual for an installer script.

The panel install is described as turnkey: the `.env`, secrets, ports, compose file and the admin account are generated automatically, with the credentials written to `admin-credentials.txt`.

Then there is the ownership rule. Every generated file, naming the `Caddyfile`, `.env`, `.env.subscription` and `docker-compose.yml`, is marked with a `# managed-by: remnawave.sh v<version>` comment and accompanied by a sha256 sidecar file. When you edit one of those files, the script stops rewriting it. The new template is written next to it as `<file>.new-<version>` with a diff, so you can see what changed and merge it yourself.

That is a considered design, and it solves a real problem: the usual behaviour of an installer that regenerates a config is to silently overwrite the operator's changes, and the operator is then surprised. Here the script detects the edit and stands down.

Two safety checks come with it. Before any Caddy reload the configuration is validated with `caddy validate` inside the running container, and afterwards every domain is probed, and a domain that stops answering rolls the file back automatically. So a bad edit does not leave you with a broken reverse proxy.

There are also two flags that govern how the script behaves in automation. `--yes` and `-y` mean never prompt, and keep every hand-edited config as it is. `--strict` exits non-zero when a config was kept instead of updated, which is what you want in a script that runs unattended and needs to know that a merge is pending.

The panel also handles a subscription page alongside the panel or standalone on a separate server, with an API token created automatically with least-privilege scopes, and there is a `subpage-token` and `subpage-restart` pair for managing it afterwards.

Updates take a snapshot first and can run migrations

The update path is the part of a script collection that most often goes wrong in practice, and this one is explicit about it.

The `update` command updates the script and the containers and runs migrations, and the documentation describes it as a safe update: a database and config snapshot before every update, plus automatic migrations, including from version 2 to version 3.

That matches what the changelog entry for release 6.7.1 refers to, a block around the `SHORT_UUID` during an update, and the 6.7.0 entry about the panel and node 3.4.x and the subscription link format.

There is a version compatibility detail that will matter if you are mixing component versions. The documentation states that only the node line 3.3.0 through 3.3.2 requires panel 3.3.0 or later, because those releases enforce a derived-SNI TLS handshake. Node 3.4.0 made that check opt-in through an `SNI_VERIFICATION` setting that is off by default, so the latest node works with an older panel again.

That is a small thing to read and a large thing to get wrong, since a mismatched node and panel can mean a handshake that fails in a way that looks like a network problem.

The panel command set also includes `backup`, `restore` and `schedule`, where schedule is for cron, and an `upgrade-postgres` command described as an optional PostgreSQL 17 to 18 upgrade that goes dump, fresh volume, restore, with rollback. The mention of a fresh volume and a rollback is the important part: a major database version change in place is a different operation from a patch update, and the script treats it as such.

The repository also ships a standalone `remnawave-backup.sh` and a `restore.sh` at the top level, plus a `remnawave-api-v222.yaml` and a `news.md`, so parts of the tooling are usable outside the main script.

The other three scripts cover masking, outbound and mesh

The panel and node are the core, and the other three scripts are the pieces around them.

`selfsteal.sh` is for Caddy masking for Reality, described as Caddy masking for Reality with 11 website templates, and it has its own README at README-selfsteal.md. The name refers to the technique of serving a decoy site on the same address that the proxy uses, so a casual visitor sees a website rather than a blocked connection. The templates directory, `sni-templates/`, is at the top of the repository.

`wtm.sh` handles WARP plus Tor as a WireGuard outbound for Xray, with WARP+ support, and has its own README at README-warp.md. This is the piece that changes where your traffic appears to come from, and the two mechanisms are different: WARP is a Cloudflare product that gives you an egress address, and Tor is a separate anonymity network. The script's job is to make one or the other the outbound for an Xray node.

`netbird.sh` covers NetBird mesh VPN, and unlike the other two it is not a script with an interactive menu in the same sense. It is described as offering a CLI, cloud-init and Ansible, which means it is aimed at provisioning a mesh network across a fleet rather than configuring one node. That is a different audience from the panel script, and it is worth noticing that the repository is not a single-purpose tool but a small collection of things one operator happens to need.

For the node itself, `remnanode.sh` handles Xray-core, logs and auto-restart, and there is a separate `remnanode-xray-update.sh` at the top level for updating Xray without touching the rest of the node.

One structural note: the scripts are in the repository root rather than in a `scripts/` directory, alongside `assets/`, `docs/`, `tests/` and the various READMEs. The tests directory suggests the scripts are exercised, which is unusual for a collection of this kind.

What you give up by letting the script own the machine

The trade-offs here are the same ones you accept with any idempotent installer that can rewrite its own configuration, and they are worth naming before you run one on a production host.

The first is that the script decides where things live and how they are wired. The install generates the `.env`, the compose file, the Caddyfile, the secrets and the admin account, and it picks ports and writes credentials to a file in the working directory. That is the convenience, and it is also the loss of control: a server configured that way is a server you did not configure.

The second is the self-update. Scripts that update themselves without asking mean that a behaviour change arrives on a schedule you did not choose, on a server with a production panel behind it. The snapshot before every update and the automatic rollback of a failed Caddy config are the mitigations, and they are good ones, but they protect the configuration, not the version of the script you are running.

The third is the pipe-to-bash pattern itself. The command downloads and executes whatever is at that URL at that moment, with no checksum and no signature, from a repository that does not publish releases. The mirror fallback through jsDelivr is convenient when GitHub is blocked, and it also means there are two sources of the same content in play.

The fourth is the scope. The repository's topics are remnanode, remnawave, selfsteal, tor and warp, and the panel script configures a Caddy reverse proxy with an optional authentication portal and MFA. A self-hosted VPN panel that terminates TLS and manages subscriptions is a security-sensitive deployment, and a script that generates the admin credentials and the secrets is convenient in exactly the way that needs a careful hand afterwards.

The alternative is the plain one. Remnawave publishes its own panel and node images, so an operator who wants a container-based install with an explicit compose file and hand-managed secrets can write that compose file themselves and skip the generator. The cost of that route is exactly the work the script is doing: the initial configuration, the reverse proxy with automatic certificates, the subscription page with its API token, the backup schedule, and the update procedure with migrations. The scripts buy that work, and they buy it by taking over the machine.

Editorial conclusion

remnawave-scripts suits an operator who has decided to run Remnawave and wants the panel, the node, the reverse proxy and the outbound stack installed and updated in a consistent way rather than assembled by hand. It does not suit a server where you need to keep control of your own configuration files, and it does not suit a first-time operator, since the scripts are written for someone who already understands the panel and the node. Verify first that the panel and node versions you intend to run are the ones the scripts target, that your server's Caddy and Docker state is compatible with a fresh install, and that you have a working backup path before letting an automatic update touch a running deployment.

Frequently asked questions

How do I install the Remnawave panel with these scripts?

Run bash <(curl -Ls https://github.com/DigneZzZ/remnawave-scripts/raw/main/remnawave.sh) @ install. The README also documents a CLI-only variant, install-script, which drops the command into /usr/local/bin without setting up any containers, and a jsDelivr mirror URL for servers where GitHub is blocked.

What happens if I edit a config file that the script generated?

Every generated file is marked with a managed-by comment and a sha256 sidecar, so the script detects your edit and stops rewriting it. The new template is written next to it as a .new-<version> file with a diff, and the Caddy config is validated inside the container and probed per domain, with an automatic rollback if a domain stops answering.

Do the scripts update themselves?

Yes. Each script checks its own version on update and when the menu opens, applies a newer version without asking, and re-runs your command. Downloads try GitHub first and then jsDelivr mirrors, and the fallbacks apply to self-updates as well.

Which node versions need a matching panel version?

Only the node line 3.3.0 through 3.3.2 requires panel 3.3.0 or later, because those releases enforce a derived-SNI TLS handshake. Node 3.4.0 made that check opt-in through SNI_VERIFICATION, off by default, so the latest node works with an older panel again.

Is a backup taken before an update?

Yes. The documentation describes a safe update as a database and config snapshot before every update, plus automatic migrations including from version 2 to version 3. There are also separate backup, restore and schedule commands, with schedule intended for cron.

Official sources

  1. DigneZzZ/remnawave-scripts on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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/dignezzz-remnawave-scripts.svg)](https://hysenlabs.com/projects/dignezzz-remnawave-scripts)

Was this useful?