bolt Valebyte VPS from $4/mo — NVMe, 60s deploy.

Get a VPS arrow_forward
eco Beginner Tutorial/How-to

Marzban on VPS: Multi-Protocol Xray Panel with Subscriptions

calendar_month Sep 13, 2026 schedule 20 min read visibility 16 views
Marzban на VPS: мультипротокольная панель Xray с подписками
info

Need a server for this guide? We offer dedicated servers and VPS in 50+ countries with instant setup.

Need a server for this guide?

Deploy a VPS or dedicated server in minutes.

Marzban on a VPS: a multi-protocol Xray panel with subscriptions

TL;DR

Marzban is a self-hosted Xray management panel that lets you create users, issue subscriptions, and manage multiple proxy protocols through a web interface. In this guide, you will deploy Marzban on Ubuntu Server 24.04 LTS, secure the server with basic security rules, enable HTTPS through Caddy, create the first administrator, and configure backups.

  • For a small team or personal use, a VPS with 1–2 vCPU, 2 GB RAM, and 20 GB SSD is sufficient.
  • Marzban runs in Docker and uses Xray as its transport engine.
  • The panel will be available only over HTTPS on a separate subdomain, for example panel.example.com.
  • Users receive a single subscription link that can be imported into a compatible client.
  • Key backup data: SQLite/PostgreSQL, the Marzban directory, the environment file, and the Caddy configuration.
  • For a public server, it is important to secure SSH, restrict access to the panel, and regularly update Docker images.

What we are configuring and why

Marzban is an administration panel for Xray. Xray itself handles incoming network connections, traffic routing, user accounting, and supported protocols. Marzban adds a convenient management layer: a web interface, API, users, traffic limits, expiration dates, subscriptions, multiple nodes, and statistics.

The practical scenario looks like this: you have a VPS with a public IP address and a domain. You install Marzban, connect a TLS certificate, create Xray inbound configurations, and add users. The user then receives a personal subscription link. They paste it into a compatible client application, and the application automatically retrieves the connection list and updates it when changes are made.

The panel is especially convenient when you need to serve more than a single manually configured connection, such as multiple devices or users. Instead of generating individual JSON files and links manually, you set the username, access duration, traffic limit, and number of devices in the panel. If necessary, access can be stopped with one click or through the API.

What will work after setup

  • The administrator web panel at https://panel.example.com.
  • An automatic Let’s Encrypt TLS certificate through Caddy.
  • A built-in or manually defined Xray configuration with multiple inbound connections.
  • Users with UUIDs, traffic limits, expiration dates, and personal subscription links.
  • Basic host protection: SSH keys, UFW, Fail2ban, and disabled root login over SSH.
  • Automatic archiving of the configuration and database to external S3 storage or another server.

Which protocols can be used

Marzban does not limit you to a single connection method: the available set is determined by the Xray configuration. Servers typically deploy several inbound profiles for different clients and network conditions. In practice, VLESS with TLS transports, Trojan with TLS, and Shadowsocks are often used. The specific choice must comply with your legislation, data center rules, and the requirements of the network where clients will operate.

Technology Role When it is needed
Marzban Panel, API, users, subscriptions When centralized access management is required
Xray Network core and connection processing Always: Marzban manages its configuration
Caddy HTTPS reverse proxy for the web panel To avoid publishing the panel over HTTP and managing certificates manually
Docker Compose Container startup and updates For reproducible deployment and service isolation

Self-hosted or managed service

With a cloud-managed service, you do not need to maintain a server, update the system, or configure backups. The drawbacks are less control over data, network configuration, the domain, available protocols, and logs. In addition, the service terms may change without your involvement.

Self-hosted Marzban on a VPS requires several hours of initial setup, but provides control over the infrastructure. You choose the region, IP address, Xray configuration, firewall rules, backup method, and administrator access policy. For a VPS owner, this is usually the optimal balance between flexibility and effort.

Do not use the panel as an “open proxy.” Create personal accounts, enable traffic limits, and regularly remove unused users. This reduces the risk of abuse, compromise, and IP address blocking.

What VPS configuration is needed for this task

Marzban itself puts almost no load on the CPU. The main load is generated by Xray: encryption, the number of simultaneous connections, the volume of passing traffic, and the selected transports. For personal use and a small team, a stable network and a good monthly traffic allowance matter more than a high core count.

Scenario CPU RAM Disk Network
1–10 users, personal devices 1 vCPU 2 GB 20 GB NVMe/SSD 100 Mbps, from 1 TB of traffic
10–50 users 2 vCPU 4 GB 40 GB NVMe/SSD 1 Gbps, from 3–5 TB of traffic
50–150 active users 4 vCPU 8 GB 80 GB NVMe/SSD 1 Gbps, high or unlimited allowance
Multiple nodes and high load 8+ dedicated cores 16+ GB 160+ GB NVMe 1–10 Gbps, DDoS protection

A practical starting option is 2 vCPU, 4 GB RAM, 40 GB NVMe, and a 1 Gbps port. It leaves room for Docker, Caddy, logs, updates, and several dozen moderately active users. You can choose a VPS with the specified characteristics, but when selecting any plan, check the monthly traffic allowance, IPv4 availability, network service policies, and hosting region in advance.

When a VPS is sufficient

A VPS is suitable for nearly all initial scenarios: personal infrastructure, a small team, configuration testing, and up to several dozen regularly active users. With a virtual server, it is easier to change plans, quickly reinstall the OS, create a snapshot, and move the service to another region.

When a dedicated server is needed

A dedicated server is justified if the CPU is consistently loaded by encryption, you serve hundreds of active clients, run multiple nodes on one machine, or need predictable performance. A dedicated server is also useful when you need a large disk, a network port above 1 Gbps, or special network filtering requirements. Do not switch to one solely because of the number of registered users: focus on simultaneous connections, speed, and actual CPU/RAM graphs.

How server location affects performance

Location affects latency, speed to target resources, IP address availability, and traffic costs. If users are located in one region, choose a data center with minimal latency to them. Before purchasing, you can compare public looking glass services, test IPs, or routes through mtr.

Do not choose a location based on geography alone. Sometimes a server in a neighboring country has better peering and noticeably higher real-world speed. After launch, measure latency from several client networks, not only from your home Wi-Fi.

Server preparation

Ubuntu Server 24.04 LTS x86_64 is used below. As of early 2026, it is a stable LTS release with long-term support and current Docker packages. Start with a clean server that has a public IPv4 address and an A DNS record for the panel domain.

Before installing Marzban, create DNS records. At minimum, you need panel.example.com for the admin panel and, optionally, sub.example.com for issuing subscriptions. At the first stage, you can use one domain for both purposes. The record must point to the server’s IPv4 address, and proxying at the DNS provider must be disabled if it interferes with TLS certificate issuance or the selected transport.

Connecting and updating the system

Log in to the server as the root user provided by the hosting provider and immediately update the base system. A reboot is needed only if the kernel was updated or the system explicitly indicates that a restart is required.

apt update && apt full-upgrade -y
apt autoremove --purge -y
reboot

After rebooting, connect again. Next, create a separate administrator user. This example uses the name deploy; replace it with your own.

adduser deploy
usermod -aG sudo deploy
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh

Copy the public SSH key from your local computer. Run the command locally, not on the VPS. After successfully testing the new login, do not close the current root session until you verify that the key works.

ssh-copy-id deploy@SERVER_IP
ssh deploy@SERVER_IP

Securing SSH

Open the SSH configuration and disable direct root login, password authentication, and empty passwords. First, ensure that the deploy user actually has a working key.

sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 3
AllowUsers deploy

Check the syntax and apply the settings. If the sshd -t command produces no output, the configuration is correct.

sudo sshd -t && sudo systemctl restart ssh
sudo systemctl status ssh --no-pager

Basic utilities, UFW, and Fail2ban

Install diagnostic tools, a firewall, and protection against SSH password brute-force attacks. Even with password authentication disabled, Fail2ban is useful as an additional layer of protection and for future services.

sudo apt install -y ca-certificates curl gnupg lsb-release \
  ufw fail2ban jq unzip nano htop tmux \
  dnsutils mtr-tiny chrony

Open only SSH, HTTP, and HTTPS. There is no need to expose the Marzban panel port 8000 externally: Caddy will access it locally. Xray inbound ports are added separately after you define the configuration.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

Check time synchronization. Incorrect system time breaks TLS, tokens, and connection diagnostics.

sudo systemctl enable --now chrony
timedatectl status
sudo fail2ban-client status sshd

If you manage the server through a non-standard SSH port, first add a UFW rule for the new port and verify the new connection. Only then remove the rule for the default port 22.

Software Installation — Step by Step

Marzban is distributed as a Docker image, and the official installation script creates a directory, downloads the Compose configuration, and starts the service. As of 2026, Docker Engine 27 or newer and Docker Compose Plugin v2 are preferred. In a production environment, do not rely on the unverifiable latest tag: after a successful launch, pin a specific image tested on your machine.

Step 1. Installing Docker Engine from the Official Repository

Remove conflicting old Docker packages if they are present. This prevents conflicts between Ubuntu packages and the official Docker Engine.

sudo apt remove -y docker.io docker-compose docker-compose-v2 \
  docker-doc podman-docker containerd runc 2>/dev/null || true

Add the Docker key and repository for Ubuntu 24.04. The command automatically uses the system architecture and the codename of the current release.

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

Install Docker Engine, CLI, containerd, and the modern Compose Plugin. After installation, enable the service so that containers start after the VPS is rebooted.

sudo apt install -y docker-ce docker-ce-cli containerd.io \
  docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker version
sudo docker compose version

Add the deploy user to the Docker group. You must then exit the SSH session and reconnect for the group changes to take effect. Remember: membership in the docker group effectively grants root privileges on the server.

sudo usermod -aG docker deploy
exit

Step 2. Installing Marzban

Reconnect as the deploy user. The official Marzban installation script creates the /opt/marzban working directory, downloads Docker Compose, and prepares the service. Before running it, it is useful to review the downloaded script in a browser or using curl, especially on a production server.

ssh deploy@SERVER_IP
curl -fsSL https://github.com/Gozargah/Marzban-scripts/raw/master/marzban.sh \
  -o /tmp/marzban.sh
less /tmp/marzban.sh

If the contents match the project's official repository, run the installation. The script will request the user's sudo password and download the required containers.

bash /tmp/marzban.sh install

After installation, check the container status. Names may differ slightly between releases, but the Marzban panel container should be running, and the logs should not contain recurring errors.

cd /opt/marzban
sudo docker compose ps
sudo docker compose logs --tail=100 marzban

Step 3. Creating the First Administrator

Marzban must not have publicly known administrator credentials. Create a separate long username and password through the CLI inside the container. Do not use the same password for the panel as for the Linux user, email, or domain registrar.

cd /opt/marzban
sudo docker compose exec marzban marzban cli admin create \
  --sudo

The command will interactively ask for a username and password. You can generate a secure password locally or on the server.

openssl rand -base64 32

Step 4. Checking the Local Interface

Before connecting Caddy, make sure that the application is listening on a local port. In a typical installation, Marzban uses port 8000. If the command does not show a listening port or the container is continuously restarting, proceed to the troubleshooting section rather than exposing the panel to the internet.

sudo ss -ltnp | grep 8000
curl -I http://127.0.0.1:8000

The response may be 200, 302, 401, or 404 depending on the interface version and route. The key point is to receive an HTTP response from the local process rather than a connection error.

Marzban, Xray, and HTTPS Configuration

In this section, the panel remains available only locally at 127.0.0.1:8000, while Caddy handles external HTTPS. This setup does not require exposing the Marzban port externally, simplifies certificate issuance, and allows HTTP headers to be configured centrally.

Checking Marzban Files

After installation, check the directory contents. It typically contains docker-compose.yml, .env, a data directory, and an Xray configuration template. Do not publish the .env file to Git, cloud notes, or messengers: it may contain credentials and the database address.

cd /opt/marzban
sudo ls -la
sudo find . -maxdepth 2 -type f | sort
sudo chmod 600 .env

Open the environment file and check the key parameters. Variable names may differ slightly in a specific Marzban release, so do not remove automatically generated lines. Below is a reference for the built-in SQLite database and local Uvicorn server.

sudo nano /opt/marzban/.env
# Панель доступна только локальному reverse proxy
UVICORN_HOST=127.0.0.1
UVICORN_PORT=8000

# Локальная SQLite-база подходит для одного сервера
SQLALCHEMY_DATABASE_URL=sqlite:////var/lib/marzban/db.sqlite3

# Путь к конфигурации Xray внутри контейнера
XRAY_JSON=/var/lib/marzban/xray_config.json

# Не храните пароли и токены в открытом репозитории
# SUDO_USERNAME и SUDO_PASSWORD задаются через CLI или переменные окружения

If the installation Compose file publishes 8000:8000 on all interfaces, change the port publication to localhost. Create a copy of the file before editing. Some Marzban versions use host network mode; in that case, apply the restriction through UVICORN_HOST=127.0.0.1.

cd /opt/marzban
sudo cp docker-compose.yml docker-compose.yml.bak
sudo nano docker-compose.yml
services:
  marzban:
    ports:
      - "127.0.0.1:8000:8000"

Do not insert this fragment over the entire file: it shows only the required section. Preserve the remaining volumes, environment, restart policy, and image settings created by the installer. After making changes, validate the YAML and restart the stack.

cd /opt/marzban
sudo docker compose config > /dev/null
sudo docker compose up -d
sudo docker compose ps
curl -I http://127.0.0.1:8000

Installing Caddy

Caddy automatically obtains and renews Let's Encrypt certificates if the domain points to the server and ports 80 and 443 are available externally. Install Caddy from the official repository. Before doing so, make sure that no other web server occupies port 80 or 443.

sudo apt install -y debian-keyring debian-archive-keyring \
  apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
  sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
  sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy

Create a Caddyfile. Replace the domain, email address for Let's Encrypt notifications, and, if necessary, the list of allowed IP addresses. IP restrictions are useful protection for the panel if you have a static home IP or VPN exit point.

sudo nano /etc/caddy/Caddyfile
{
    email [email protected]
}

panel.example.com {
    encode zstd gzip

    @admin {
        path /dashboard /api/admin /docs /openapi.json
    }

    reverse_proxy 127.0.0.1:8000

    header {
        -Server
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
    }

    log {
        output file /var/log/caddy/marzban-access.log
        format json
    }
}

The @admin block in this minimal example is not applied separately: it is included as a placeholder for future IP restrictions. Do not add unconsidered Caddy authentication on top of the panel until you verify that it does not interfere with API and subscription routes.

Validate the configuration and reload Caddy. On the first launch, it will request a certificate; this usually takes a few seconds.

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl enable --now caddy
sudo systemctl status caddy --no-pager

Check HTTPS from the VPS itself and from a local computer. A response without certificate errors is expected. If the certificate is not issued, first check DNS, open ports, and Caddy logs.

curl -I https://panel.example.com
sudo journalctl -u caddy -n 100 --no-pager
sudo ufw status numbered

Configuring an Inbound in the Panel

Open https://panel.example.com, sign in as the created administrator, and navigate to the host/inbound configuration management section. The interface changes between releases, but the logic is the same: Marzban stores an Xray JSON template, runs Xray, and inserts users into the appropriate inbound objects.

For the initial setup, create one clear inbound, test it on one device, and only then add additional ones. Do not mix a dozen transports in the first configuration: if an error occurs, it will be difficult to determine whether the issue is DNS, TLS, the port, JSON, or the client.

The simplified Xray JSON example below shows the structure of a VLESS inbound. Marzban usually inserts user UUID values automatically, so the client list is left with a template variable supported by your panel version. Use the JSON generated by the Marzban interface as the primary source of truth.

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "tag": "vless-main",
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "certificates": [
            {
              "certificateFile": "/var/lib/marzban/certs/fullchain.pem",
              "keyFile": "/var/lib/marzban/certs/privkey.pem"
            }
          ]
        }
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    }
  ]
}

This example is not a universal production template: TLS files, template clients, transport, and port must align with your specific setup. If Caddy already occupies 443/tcp, Xray cannot simultaneously listen on the same IP and port in this form. Use a separate port for Xray or design a shared entry point based on the selected transport and Marzban/Xray documentation.

User, Subscription, and Verification

In the panel, create a test user with a short expiration period, for example 24 hours, a 1 GB limit, and one device. After creation, copy the subscription URL from the user's card. A typical route looks like https://panel.example.com/sub/ТОКЕН, but the exact path depends on the configuration and Marzban version.

Open the link in a browser or check it using curl. Do not publish this URL in screenshots or tickets: the token provides access to the user's configuration.

curl -fsS "https://panel.example.com/sub/USER_SUBSCRIPTION_TOKEN" | head
sudo docker compose -f /opt/marzban/docker-compose.yml logs --tail=100 marzban
sudo docker stats --no-stream

Import the subscription URL into a client that supports the format you use. Then connect and verify that the external IP address changes to the server IP and that the traffic counter increases in the panel. For diagnostics on the VPS, use Marzban logs, Xray logs, and the list of listening ports.

curl -4 https://ifconfig.me/ip ; echo
sudo ss -lntup
sudo docker compose -f /opt/marzban/docker-compose.yml logs --tail=200

Backups and Maintenance

A Marzban backup is needed not to restore Docker images, but to restore state: the user database, limits, tokens, Xray configuration, environment variables, and reverse proxy TLS settings. If only the container is lost, it can be recreated. If the database is lost, you will have to recreate users and issue new subscription links.

What to include in a backup

  • /opt/marzban — Compose files, .env, Xray data and templates.
  • The SQLite database file, if SQLite is used; it is usually located inside the Marzban data directory.
  • A PostgreSQL dump, if you moved the database to a separate container or server.
  • /etc/caddy/Caddyfile — HTTPS reverse proxy configuration.
  • /var/lib/caddy — Caddy certificates and state, if you want fast recovery.
  • A list of open ports and recovery instructions, stored separately from the server.

Do not keep the only copy on the same VPS. Deleting the virtual machine, a disk failure, root account compromise, or an accidental rm command will destroy both the service and the local archive. A good approach is an encrypted restic backup to S3-compatible storage, Backblaze B2, or a separate VPS via SFTP.

Installing restic and preparing the repository

An example using S3-compatible storage is shown below. Do not put key values into shell history unless necessary. It is better to store them in a file with 600 permissions.

sudo apt install -y restic
sudo install -d -m 700 /root/.config/restic
sudo nano /root/.config/restic/marzban.env
sudo chmod 600 /root/.config/restic/marzban.env
export RESTIC_REPOSITORY="s3:https://s3.example.net/marzban-backups"
export RESTIC_PASSWORD="CHANGE_TO_A_LONG_UNIQUE_BACKUP_PASSWORD"
export AWS_ACCESS_KEY_ID="S3_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="S3_SECRET_KEY"

Initialize the empty repository once. If the command completes successfully, restic will create its internal structure in the specified bucket.

sudo bash -c 'source /root/.config/restic/marzban.env && restic init'

Backup script

Before copying SQLite, it is advisable to briefly stop the container or create a consistent database copy. For a small server, it is safer to make a short Marzban pause: users with already established connections may briefly lose the ability to change their subscription, but the backup will be consistent. If you use PostgreSQL, use pg_dump instead of copying database files.

sudo nano /usr/local/sbin/backup-marzban.sh
sudo chmod 700 /usr/local/sbin/backup-marzban.sh
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/var/backups/marzban"
STAMP="$(date +%F-%H%M%S)"

mkdir -p "$BACKUP_DIR"

cd /opt/marzban
docker compose stop marzban

tar --xattrs --acls -czf "$BACKUP_DIR/marzban-$STAMP.tar.gz" \
  /opt/marzban \
  /etc/caddy/Caddyfile \
  /var/lib/caddy 2>/dev/null || true

docker compose start marzban

source /root/.config/restic/marzban.env
restic backup "$BACKUP_DIR" --tag marzban --tag "$(hostname)"
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

find "$BACKUP_DIR" -type f -mtime +2 -delete

Test the script manually before automating it. After execution, a snapshot should appear in restic, and the Marzban container should return to the Up state.

sudo /usr/local/sbin/backup-marzban.sh
sudo bash -c 'source /root/.config/restic/marzban.env && restic snapshots'
sudo docker compose -f /opt/marzban/docker-compose.yml ps

Automatic execution via cron

Run the backup nightly. Cron is suitable for a simple single task; if you have many servers, it is more convenient to switch to a systemd timer, Ansible, or a centralized backup system.

sudo crontab -e
25 3    /usr/local/sbin/backup-marzban.sh >> /var/log/backup-marzban.log 2>&1

At least once per quarter, perform a test restore on a separate temporary machine. A backup that has never been tested through restoration cannot be considered reliable. To inspect archive contents, use restic restore to a test directory without overwriting production files.

Marzban and system updates

For a single VPS, update Marzban during a maintenance window: notify users, create a backup, download the image, restart the container, and check the logs. This is safer than blindly updating at night via cron. Automatically applying Ubuntu security updates is acceptable, but major Docker, Marzban, and Xray updates are better controlled manually.

cd /opt/marzban
sudo /usr/local/sbin/backup-marzban.sh
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune -f
sudo docker compose logs --tail=100 marzban

With multiple nodes, use a rolling update: first update one of the least loaded nodes, check connections and metrics, then update the rest. Do not update the panel, all Xray nodes, the OS, and the database schema simultaneously: if a problem occurs, it will be impossible to quickly determine the cause.

Troubleshooting and FAQ

Why does the panel not open over HTTPS, and Caddy reports a certificate acquisition error?

First check DNS: dig +short panel.example.com A must return your VPS IP. Then make sure ports 80 and 443 are open in UFW and in the provider's network firewall. Check whether another web server is running: sudo ss -ltnp | grep -E ':80|:443'. The journalctl -u caddy logs often state the cause directly: incorrect DNS, a closed port, a rate limit, or a service conflict.

Why does Caddy return 502 Bad Gateway?

A 502 error means Caddy is running but cannot connect to Marzban. Check the container: sudo docker compose -f /opt/marzban/docker-compose.yml ps. Then run curl -I http://127.0.0.1:8000 on the VPS itself. If there is no response, inspect the container logs and check the UVICORN_HOST/UVICORN_PORT values in .env. After making changes, apply docker compose up -d and reload Caddy.

Why does the Marzban container keep restarting?

Run sudo docker compose logs --tail=200 marzban. Common causes include a syntax error in .env, corrupted Compose YAML, an unavailable Xray JSON file, a port conflict, or insufficient disk space. Check free space with df -h and memory with free -h. Before changing the configuration, restore a backup copy of the file, such as docker-compose.yml.bak, then run docker compose config for validation.

A user imported a subscription URL, but connections do not work. What should be checked?

Check three layers separately: the subscription URL, the Xray inbound, and the client. First open the link with curl: it must return configurations, not an HTML error page. Then make sure the required inbound port is listening using ss -lntup and is allowed by UFW. Finally, check the domain, SNI, UUID, TLS certificate, and the client's system time. Start with one simple inbound and one test user.

Why is user traffic not counted, or why does the limit not disable access?

First verify that the user connects through an inbound managed by Marzban, not through an old manually started Xray process. Check the logs and statistics in the panel after a new connection. The error often occurs if the Xray JSON was edited manually and lost the Marzban client template. Do not change the structure of user objects without understanding the panel templates. After changing the configuration, create a new test user and verify it separately.

What is the minimum suitable VPS configuration?

The minimum for personal use is 1 vCPU, 2 GB RAM, 20 GB SSD/NVMe, a public IPv4 address, and a port speed of at least 100 Mbps. Such a server is suitable for several devices and 1–10 moderately active users. For more comfortable operation, it is better to start with 2 vCPU and 4 GB RAM: this provides headroom for Docker, logs, Caddy, updates, and short load spikes. The most important factors are the traffic limit and network quality.

What should I choose for this task — a VPS or dedicated server?

For most Marzban installations, choose a VPS: it is cheaper, deploys faster, scales easily through the plan, and is suitable for up to dozens of active users. A dedicated server is needed for sustained high CPU load, hundreds of simultaneous clients, multiple nodes on one server, requirements for large storage, or a port above 1 Gbps. Base the decision on metrics: if CPU remains above 70%, latency increases, and speed losses appear, it is time to consider a dedicated server or horizontal scaling.

Can the panel be opened directly on port 8000?

Technically, yes, but it is poor practice for production. Direct HTTP access transmits the administrator password without protection, and an open port makes scanning and attacks easier. Keep Marzban on 127.0.0.1:8000, and expose only Caddy externally over HTTPS on 443. If temporary access is needed for diagnostics, use an SSH tunnel: ssh -L 8000:127.0.0.1:8000 deploy@SERVER_IP, then open the local address in the browser.

How do I restore Marzban after moving to a new VPS?

Install Docker, Marzban, and Caddy on the new server, but do not create new users on top of the old database. Copy the archive from restic to a temporary directory, stop Marzban, restore /opt/marzban and the Caddyfile, set the correct permissions, then start Compose. After changing the IP, update DNS and wait for the record to propagate. Check the panel, subscription URL, certificate, and a test connection before finally shutting down the old VPS.

Conclusions and Next Steps

You now have Marzban on a VPS with Docker, secured SSH, an HTTPS panel, basic network filtering, and automatic backups. The panel lets you centrally create Xray users, issue subscriptions, and control expiration dates or traffic limits.

  1. Add one or two additional inbound profiles only after testing the basic configuration with a real client.
  2. Set up monitoring for CPU, RAM, disk space, HTTPS availability, and the date of the last successful backup.
  3. As load grows, move PostgreSQL and Xray nodes to separate servers while keeping Marzban as the central management panel.

Regularly check logs, test restoration from backups, and update components during a scheduled maintenance window. This is more important than a complex configuration: a stable, documented, and recoverable server is more reliable than a set of untested settings.

Was this guide helpful?

Your feedback helps us improve our guides.

Share this post:

Send this guide to someone who may find it useful.

Telegram VKVK WhatsApp Facebook LinkedIn XX

marzban on VPS: multi-protocol Xray panel with subscriptions
support_agent
Valebyte Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.