README
¶
Manage Remnawave from reviewed HCL: preview changes before they reach users, import existing panel objects, and keep nodes, hosts, squads, billing, and subscription configuration reproducible.
A community-maintained Terraform provider for Remnawave — a modern proxy management panel built on top of Xray-core. This project is not affiliated with or endorsed by the Remnawave project. Manage VPN users, nodes, hosts, squads, billing, subscription pages, and more as infrastructure-as-code.
At a glance: 28 resources · 28 data sources · 6 Remnawave versions tested in CI · signed checksums, SBOMs, and build provenance for every release
Get started in 5 minutes · Browse the Registry docs · Ask a question
If this provider saves you manual panel work, consider starring the repository — it helps other Remnawave operators discover the project. Production users are also invited to share what they manage.
Table of Contents
- Why Terraform + Remnawave?
- Compatibility
- Quick Start
- Authentication
- Provider Configuration
- Resources
- Data Sources
- Examples
- Importing Existing Resources
- Versioning & Upgrades
- Release Integrity
- Development
- Contributing
- License
Why Terraform + Remnawave?
| Without Terraform | With this provider |
|---|---|
| Create users one-by-one in the web UI | Define hundreds of users in HCL, terraform apply |
| Manual config drift between nodes | Version-controlled, reproducible node configs |
| No audit trail for changes | Git history + Terraform plan/apply logs |
| Onboarding = manual clicks | Onboarding = terraform init && terraform apply |
Compatibility
The provider supports Remnawave panel v2.7.x, v2.8.x, v3.0.x, v3.1.x, v3.2.x, v3.3.x, and v3.4.x.
The acceptance test suite runs against all eight versions in CI on every push
to main and every pull request — see the full CI matrix.
| Remnawave version | Status |
|---|---|
| v3.4.3 | ✅ Tested (primary) |
| v3.3.2 | ✅ Tested (matrix) |
| v3.3.1 | ✅ Tested (matrix) |
| v3.2.3 | ✅ Tested (matrix) |
| v3.1.0 | ✅ Tested (matrix) |
| v3.0.0 | ✅ Tested (matrix) |
| v2.8.1 | ✅ Tested (matrix) |
| v2.7.4 | ✅ Tested (matrix) |
The client auto-detects the backend version via /api/system/metadata on the
first version-dependent operation and adapts API calls where contracts differ
between versions. No configuration is required.
Quick Start
Create an API token in Remnawave, save the following as main.tf, and keep the
token outside Terraform configuration:
terraform {
required_providers {
remnawave = {
source = "batonogov/remnawave"
version = "~> 1.0" # x-release-please-major
}
}
}
provider "remnawave" {
# REMNAWAVE_ENDPOINT and REMNAWAVE_API_TOKEN are read from the environment.
}
resource "remnawave_user" "quickstart" {
username = "terraform-quickstart"
expire_at = "2030-01-01T00:00:00.000Z"
traffic_limit_bytes = 10737418240 # 10 GiB
traffic_limit_strategy = "MONTH"
description = "Managed by Terraform"
}
export REMNAWAVE_ENDPOINT="https://panel.example.com"
export REMNAWAVE_API_TOKEN="..."
terraform init
terraform plan
terraform apply
The complete getting-started example includes verification, cleanup, and guidance for adopting an existing panel. Review the plan before applying it to production.
Authentication
The provider supports two authentication methods:
API Token (recommended)
Generate a token in the panel under API Keys and pass it via the api_token
attribute or the REMNAWAVE_API_TOKEN environment variable. This is a static
JWT — no login round-trip, works with scoped permissions.
provider "remnawave" {
endpoint = "https://panel.example.com"
api_token = var.remnawave_api_token
}
Username / Password
If no API token is provided, the provider logs in via POST /api/auth/login
and obtains a JWT automatically. After a 401 response, the provider refreshes
the token and transparently replays only GET and HEAD requests. Mutating
requests fail without replay because the server may already have applied them;
the next operation authenticates with a fresh token.
provider "remnawave" {
endpoint = "https://panel.example.com"
username = "admin"
password = var.remnawave_password
}
Note: Username/password auth requires setting
proxy_headers = trueorNODE_ENV=developmenton the backend — Remnawave'sProxyCheckMiddlewarerequiresX-Forwarded-For/X-Forwarded-Protoheaders for browser-originated requests.
Reverse-Proxy Authentication
If an outer reverse proxy requires a gateway cookie or service-token headers,
pass them with the sensitive custom_headers map. For example, Cookie must
contain the complete cookie pair:
variable "remnawave_gateway_cookie" {
type = string
description = "Complete cookie pair required by the outer reverse proxy, for example cookie_name=cookie_value."
sensitive = true
}
provider "remnawave" {
endpoint = "https://panel.example.com"
api_token = var.remnawave_api_token
custom_headers = {
Cookie = var.remnawave_gateway_cookie # cookie_name=cookie_value
}
}
Alternatively, supply the map as a JSON object through the environment:
export REMNAWAVE_CUSTOM_HEADERS='{"Cookie":"cookie_name=cookie_value"}'
When custom_headers is configured in HCL, it replaces the environment map as
a whole; the two maps are not merged. Terraform's Sensitive marker only
redacts values from Terraform output. Inject header secrets through environment
variables or a secret store, and never commit secret-bearing .tfvars files.
Provider Configuration
All provider attributes can be supplied through environment variables — the provider block may be empty when the environment is configured. Explicit HCL values take precedence over environment variables.
| Attribute | Env var | Type | Description |
|---|---|---|---|
endpoint |
REMNAWAVE_ENDPOINT |
string |
Base URL of the panel, e.g. https://panel.example.com |
api_token |
REMNAWAVE_API_TOKEN |
string (sensitive) |
Pre-generated API token (JWT). If set, username/password are ignored |
username |
REMNAWAVE_USERNAME |
string |
Admin username for login |
password |
REMNAWAVE_PASSWORD |
string (sensitive) |
Admin password for login |
insecure_skip_verify |
REMNAWAVE_INSECURE_SKIP_VERIFY |
bool |
Skip TLS certificate verification (true/false) |
request_timeout |
REMNAWAVE_REQUEST_TIMEOUT |
string |
HTTP client timeout (default 30s) |
proxy_headers |
REMNAWAVE_PROXY_HEADERS |
bool |
Send X-Forwarded-For/Proto headers (bypass ProxyCheckMiddleware) |
custom_headers |
REMNAWAVE_CUSTOM_HEADERS |
map(string) (sensitive) |
Additional headers for outer reverse-proxy authentication; the environment value is a JSON object |
The client accepts at most 32 MiB of decoded data in a successful HTTP response. The limit is enforced while reading and after transparent content decompression; responses above it fail with a fixed diagnostic. HTTP error bodies have a zero-byte limit and are never read or included in diagnostics.
Resources
The provider exposes 28 resources across 7 functional areas:
Core VPN Management
| Resource | Description |
|---|---|
remnawave_user |
VPN user with traffic limits, expiration, VLESS/Trojan/Shadowsocks credentials |
remnawave_node |
Xray server node with traffic tracking, tags, consumption multipliers |
remnawave_host |
Connection endpoint (host) for VPN subscriptions |
remnawave_config_profile |
Xray config profile with inbounds, routing, sniffing |
remnawave_node_integration |
Reusable Remnawave 3.3+ node integration configuration |
Access Control & Routing
| Resource | Description |
|---|---|
remnawave_external_squad |
External squad with subscription and host override settings |
remnawave_internal_squad |
Internal squad with inbounds and accessible nodes |
remnawave_subscription_template |
Subscription template (XRAY_JSON, MIHOMO, CLASH, SINGBOX, etc.) |
remnawave_subscription_settings |
Subscription page settings (singleton) |
remnawave_subpage_config |
Subscription page config (i18n, theme, blocks) |
Panel & Branding
| Resource | Description |
|---|---|
remnawave_panel_settings |
Panel branding, auth, passkey settings (singleton) |
remnawave_snippet |
Xray config snippet (reusable JSON fragments) |
remnawave_node_plugin |
Node plugin (e.g. torrent blocker) |
remnawave_shared_list |
Global Remnawave 3.3+ IP/CIDR or ASN list for node plugins |
Infrastructure Billing
| Resource | Description |
|---|---|
remnawave_infra_provider |
Infrastructure billing provider |
remnawave_billing_node |
Infrastructure billing node (recurring billing) |
remnawave_billing_history |
Infrastructure billing history record (one-time payment) |
API & Access Tokens
| Resource | Description |
|---|---|
remnawave_api_token |
API token with scopes and expiration |
remnawave_passkey |
WebAuthn passkey (import-only — cannot be created via Terraform) |
Metadata & Devices
| Resource | Description |
|---|---|
remnawave_user_metadata |
Free-form key-value metadata for a user |
remnawave_node_metadata |
Free-form key-value metadata for a node |
remnawave_hwid_device |
HWID device entry for device-limit enforcement |
Imperative Actions
These resources trigger one-shot operations on terraform apply. Use the
triggers attribute to force re-execution when external values change.
| Resource | Description |
|---|---|
remnawave_node_action |
Enable / disable / restart / reset traffic on a node |
remnawave_user_action |
Enable / disable / reset traffic / revoke subscription / extend expiration on a user |
remnawave_host_bulk_action |
Bulk enable / disable / delete hosts |
remnawave_user_bulk_action |
Bulk reset traffic / revoke subscriptions / delete users / extend expiration |
remnawave_node_bulk_action |
Bulk enable / disable / restart / reset traffic on nodes |
remnawave_drop_connections |
Drop active connections by user identifier or IP address on all or selected nodes |
Data Sources
28 data sources for reading panel state:
Inventory
| Data Source | Description |
|---|---|
remnawave_nodes |
List all nodes with status and online user counts |
remnawave_users |
List all users with status and tags |
remnawave_hosts |
List all hosts |
remnawave_config_profiles |
List all config profiles |
remnawave_internal_squads |
List all internal squads and their assigned inbounds and accessible nodes |
remnawave_external_squads |
List all external squads and their subscription configuration |
remnawave_host_tags |
List all unique host tags |
remnawave_passkeys |
List WebAuthn passkeys for the current admin |
remnawave_node_integrations |
List Remnawave 3.3+ node integrations and their configuration |
remnawave_shared_lists |
List Remnawave 3.3+ global shared-list previews |
remnawave_snippets |
List Xray config snippets |
System & Health
| Data Source | Description |
|---|---|
remnawave_system_health |
Panel system health (raw JSON) |
remnawave_system_stats |
CPU, memory, uptime, user status counts, online stats |
remnawave_system_recap |
Monthly/total summary: users, traffic, nodes, version |
remnawave_system_bandwidth_stats |
System-level bandwidth statistics |
remnawave_system_nodes_stats |
System-level per-node statistics |
Bandwidth & Metrics
| Data Source | Description |
|---|---|
remnawave_nodes_metrics |
Per-node live metrics: users online, inbounds/outbounds |
remnawave_bandwidth_stats |
Per-node bandwidth usage by date range |
remnawave_bandwidth_stats_user |
Per-user bandwidth usage by date range |
remnawave_bandwidth_realtime |
Realtime bandwidth metrics per node |
Subscriptions
| Data Source | Description |
|---|---|
remnawave_subscriptions |
Fetch subscription by user identifier, username, or short UUID |
remnawave_subscription_request_history |
Subscription request history |
remnawave_subscription_request_history_stats |
Subscription request statistics |
remnawave_connection_keys |
Per-protocol connection keys for a user |
Other
| Data Source | Description |
|---|---|
remnawave_keygen |
Panel public key for node setup |
remnawave_user_ips |
Fetch IPs a user is currently connected from |
remnawave_hwid_stats |
HWID device statistics |
remnawave_hwid_top_users |
Top users by HWID device count |
Examples
Start with the standalone examples/getting-started/
configuration, then browse examples/ for 55 focused resource and
data-source examples. Some schema examples reference resources or variables
supplied by the surrounding configuration:
examples/
├── resources/
│ ├── remnawave_user/ # User with traffic limit + tag
│ ├── remnawave_node_action/ # Periodic traffic reset via triggers
│ ├── remnawave_host_bulk_action/# Bulk enable/disable/delete
│ ├── remnawave_user_bulk_action/# Bulk user operations
│ ├── remnawave_node_bulk_action/# Bulk node operations
│ ├── remnawave_user_action/ # Enable/disable/reset/revoke/extend
│ └── remnawave_passkey/ # Import + manage existing passkeys
└── data-sources/
├── remnawave_users/ # List all users
├── remnawave_system_stats/ # Dashboard metrics
└── remnawave_passkeys/ # List passkeys
Full documentation for every resource and data source is on the
Terraform Registry
and in docs/.
Importing Existing Resources
Resources created outside Terraform (e.g. via the web UI) can be imported into Terraform state:
# Import a user by numeric ID on Remnawave 3.0+
terraform import remnawave_user.john 42
# Remnawave 2.x uses the user's UUID instead
# terraform import remnawave_user.john 550e8400-e29b-41d4-a716-446655440000
# Import a node by UUID
terraform import remnawave_node.de_fra_01 550e8400-e29b-41d4-a716-446655440001
# Import a passkey (import-only resource)
terraform import remnawave_passkey.admin 550e8400-e29b-41d4-a716-446655440002
Import IDs are resource-specific. Most stateful resources use a UUID, snippets use their name, and resources with compound identities document their required format on the corresponding resource page.
Versioning & Upgrades
This provider follows Semantic Versioning. Breaking
changes are reserved for major releases (e.g. 1.0.0).
Upgrading
# Upgrade to the latest version
terraform init -upgrade
Review the CHANGELOG for breaking changes and migration notes before upgrading across major versions.
Provider Selection
Copyable examples use a pessimistic patch-line constraint. This accepts patch updates in the current minor line while requiring an explicit configuration change before upgrading to a new minor or major provider release.
terraform {
required_providers {
remnawave = {
source = "batonogov/remnawave"
version = "~> 1.0" # x-release-please-major
}
}
}
Release Integrity
Each platform archive is covered by signed checksums, a matching SPDX 2.3 SBOM, and GitHub/Sigstore build provenance bound to its SHA-256 digest and the release workflow. See Verifying Releases for commands that validate the archive, SBOM, source commit, and workflow identity.
Development
Requirements
| Tool | Version |
|---|---|
| Go | 1.26.6+ (see go.mod) |
| Terraform CLI | 1.12+ |
| Task | Latest (optional, for task commands) |
| Docker | Required for acceptance tests |
Build & Test
# Build the provider binary
go build -o terraform-provider-remnawave
# Format code
gofmt -w provider/*.go
# Lint
golangci-lint run
# Unit tests (no Docker needed)
go test ./provider/... -race -cover
# Reachable vulnerability scan
task test:vuln
# Acceptance tests (starts Remnawave panel via Docker Compose)
task test:acc
See Taskfile.yml and CLAUDE.md for the full
list of commands and development conventions.
Acceptance Tests
Acceptance tests run against a real Remnawave panel via Docker Compose
(docker-compose.yaml). The compose file spins up:
remnawave/backendpanel (port 3000)- PostgreSQL 18
- Valkey (Redis) 9
# Run the complete Docker lifecycle + test suite
task test:acc
# Override the Remnawave version under test
REMNAWAVE_VERSION=3.4.3 REMNAWAVE_DIGEST=sha256:<digest> task test:acc
All compose images are pinned by sha256 digest for reproducibility.
Regenerating Documentation
Documentation is auto-generated from provider schemas via
tfplugindocs:
task docs # Format examples + regenerate Registry docs
task docs:check # Validate and detect stale generated docs (CI gate)
Contributing
Contributions are welcome! Please:
- Open an issue to discuss the change before starting work.
- Follow Conventional Commits for
commit messages (
feat:,fix:,docs:,test:,chore:). - Add or update tests for any changed behavior.
- Run
gofmt,golangci-lint, and the relevant test suite before submitting a pull request. - Ensure CI is green — acceptance tests run against Remnawave v3.4.3, v3.3.2, v3.3.1, v3.2.3, v3.1.0, v3.0.0, v2.8.1, and v2.7.4.
Releases are machine-gated: the release workflow runs only after successful CI
for the exact current main commit, then verifies the CI jobs, tag target, and
embedded Go VCS revision before publishing.
Security
If you believe you have found a security issue, please report it privately rather than opening a public issue.
License
MIT © 2026 Fedor Batonogov
Documentation
¶
There is no documentation for this package.