Skip to main content

Migrate from Marzban

This guide walks you through migrating your data from a Marzban panel to a Remnawave panel using the Remnawave migration tool.

1. Server Preparation

1.1. Installing Required Dependencies

Make sure your server has the necessary tools installed by updating the system packages and installing Git and wget.

# Update system packages
sudo apt-get update

# Install Git and wget
sudo apt-get install -y git wget
tip

Run git --version and wget --version to confirm successful installation.

2. Downloading the Migration Tool

2.1. Downloading the Latest Release

Download the precompiled Remnawave migration tool from the GitHub releases page.

# Create and navigate to a working directory
mkdir -p /opt/remnawave && cd /opt/remnawave

# Download the latest version (v2.1.0 as of this guide)
wget https://github.com/remnawave/migrate/releases/download/v2.1.0/remnawave-migrate-v2.1.0-linux-amd64.tar.gz

2.2. Extracting the Tool

Unpack the downloaded archive to access the binary.

# Extract the tarball
tar -xf remnawave-migrate-v2.1.0-linux-amd64.tar.gz
tip

After extraction, you should see the remnawave-migrate binary in your directory. Use ls -l to verify.

3. Configuration Setup

3.1. Setting Up Migration Parameters

The migration tool uses command-line flags for configuration. Below is an example command with all the required parameters. Replace the placeholder values with your actual server details.

./remnawave-migrate \
--panel-type=marzban \
--panel-url=https://your-marzban-server \
--panel-username=admin \
--panel-password=your-admin-password \
--remnawave-url=https://your-remnawave-server \
--remnawave-token=your-remnawave-token \
--preserve-status

3.2. Configuration Options

The tool supports the following flags and their corresponding environment variables:

FlagEnvironment VariableDescriptionDefault
--panel-typePANEL_TYPESource panel type (marzban or marzneshin)marzban
--panel-urlPANEL_URLSource panel URL (e.g., https://marzban.example.com)-
--panel-usernamePANEL_USERNAMESource panel admin username-
--panel-passwordPANEL_PASSWORDSource panel admin password-
--remnawave-urlREMNAWAVE_URLDestination panel URL (e.g., https://remnawave.example.com)-
--remnawave-tokenREMNAWAVE_TOKENDestination panel API token (used as Authorization Bearer)-
--batch-sizeBATCH_SIZENumber of users to process per batch100
--last-usersLAST_USERSMigrate only the last N users (0 = all users)0
--preferred-strategyPREFERRED_STRATEGYTraffic reset strategy (NO_RESET, DAY, WEEK, MONTH)-
--source-headersSOURCE_HEADERSAdditional headers for source panel-
--dest-headersDEST_HEADERSAdditional headers for Remnawave (e.g., X-Api-Key)-
--preserve-statusPRESERVE_STATUSPreserve user status from source panelfalse
--preserve-subhashPRESERVE_SUBHASHPreserve user subscription URL hash from source panelfalse
--internal-squadINTERNAL_SQUADUUID(s) of internal squad(s) to assign (comma-separated)-
--external-squadEXTERNAL_SQUADUUID of external squad to assign to all created users-
tip

If you’re using Remnawave with additional security provided by Caddy, you need to follow these steps:

  1. Log in to the Auth Portal and navigate to API Keys
  2. Issue a new API key
  3. Pass this key using the --dest-headers flag in the following format:
    --dest-headers="X-Api-Key:api-key-from-auth-portal"
tip
  • Use --last-users=5 for a test migration with a small subset of users.
  • Obtain your Remnawave API token from the Remnawave panel settings (e.g., under API or Integrations).
  • If --preferred-strategy is not specified, the original strategy from Marzban will be used. YEAR strategy is converted to NO_RESET.

3.3. Assigning Users to Squads

You can automatically assign all migrated users to internal and/or external squads:

# Assign to a single internal squad
./remnawave-migrate \
[other flags...] \
--internal-squad=e5201a6a-c50e-4b58-9ecb-a4c26c5e74c8

# Assign to multiple internal squads
./remnawave-migrate \
[other flags...] \
--internal-squad=uuid1,uuid2,uuid3

# Assign to an external squad
./remnawave-migrate \
[other flags...] \
--external-squad=f6302b7b-d61f-5c69-0fdc-b5d37d6e85d9

# Assign to both internal and external squads
./remnawave-migrate \
[other flags...] \
--internal-squad=uuid1,uuid2 \
--external-squad=uuid3

4. Post-Migration Verification

4.1. What to Check

After migration, verify the following on the Remnawave panel:

  1. User Count: Ensure the number of migrated users matches the source.
  2. Data Integrity:
    • Usernames
    • Passwords (Trojan, VLESS UUID, Shadowsocks)
    • Traffic limits and reset strategies
    • Expiration dates
    • User statuses (if --preserve-status was used)
    • Subscription URL hashes (if --preserve-subhash was used)
    • Squad assignments (if --internal-squad or --external-squad was used)
tip

Log in to the Remnawave panel and spot-check a few users to confirm data accuracy.

5. Supporting Legacy Subscription Pages

After migrating to Remnawave, the only way to ensure support and rendering of legacy Marzban subscription pages is by using the repository at https://github.com/remnawave/subscription-page/.

5.1. Default Docker Compose Configuration

By default, the docker-compose.yml file for the subscription page service looks like this:

services:
remnawave-subscription-page:
image: remnawave/subscription-page:latest
container_name: remnawave-subscription-page
hostname: remnawave-subscription-page
restart: always
environment:
- APP_PORT=3010
- REMNAWAVE_PANEL_URL=http://remnawave:3000
ports:
- '127.0.0.1:3010:3010'
networks:
- remnawave-network
networks:
remnawave-network:
driver: bridge
external: true

5.2. Adjusting for Marzban Compatibility

During migration, you need to ensure compatibility with Marzban subscription paths and enable decryption of Marzban subscription links. Replace the environment section in the default configuration with the following:

services:
remnawave-subscription-page:
image: remnawave/subscription-page:latest
container_name: remnawave-subscription-page
hostname: remnawave-subscription-page
restart: always
environment:
- APP_PORT=3010
- REMNAWAVE_PANEL_URL=http://remnawave:3000
- MARZBAN_LEGACY_LINK_ENABLED=true
- MARZBAN_LEGACY_SECRET_KEY=secret
- REMNAWAVE_API_TOKEN=token
- CUSTOM_SUB_PREFIX=sub
ports:
- '127.0.0.1:3010:3010'
networks:
- remnawave-network
networks:
remnawave-network:
driver: bridge
external: true

Configuration Options Explained

VariableDescriptionExample Value
REMNAWAVE_PANEL_URLRemnawave Panel URL, can be http://remnawave:3000 or https://panel.example.comhttp://remnawave:3000
APP_PORTThe port on which the subscription page service runs.3010
MARZBAN_LEGACY_LINK_ENABLEDEnables support for legacy Marzban subscription links. Must be true to use the options below.true
MARZBAN_LEGACY_SECRET_KEYThe secret key from your Marzban database, required for decrypting legacy links.secret
REMNAWAVE_API_TOKENThe API token generated from your Remnawave panel dashboard (under "API Tokens").token
CUSTOM_SUB_PREFIXA custom prefix for subscription URLs to match your Marzban setup (e.g., sub).sub
tip
  • If MARZBAN_LEGACY_LINK_ENABLED is set to true, all subsequent variables (MARZBAN_LEGACY_SECRET_KEY, REMNAWAVE_API_TOKEN, and CUSTOM_SUB_PREFIX) must be provided.
  • To retrieve the MARZBAN_LEGACY_SECRET_KEY, query your Marzban database with:
    SELECT secret_key FROM jwt LIMIT 1;
    For example, if your Marzban database is in a Docker container named marzban-mysql, connect to it using:
    docker exec -it marzban-mysql mysql -uroot -pPassword
    Replace marzban-mysql and Password with your actual container name and root password.
  • Generate the REMNAWAVE_API_TOKEN from the Remnawave dashboard under the "API Tokens" section.

5.3. Verifying Legacy Support

After deploying the updated configuration:

  1. Restart the remnawave-subscription-page service:
    docker compose up -d --force-recreate
  2. Test an old Marzban subscription link to ensure it resolves correctly and displays user data on the Remnawave subscription page.