## Getting SSL

### ACME

To manage SSL certificates using ACME:

1. Ensure your domain is correctly resolved to the server.
2. Run the `x-ui` command in the terminal, then choose `SSL Certificate Management`.
3. You will be presented with the following options:

   - **Get SSL (Domain):** Obtain SSL certificates for a domain.
   - **Revoke:** Revoke existing SSL certificates.
   - **Force Renew:** Force renewal of SSL certificates.
   - **Show Existing Domains:** Display all domain certificates available on the server.
   - **Set Cert paths for the panel:** Specify the certificate for your domain to be used by the panel.
   - **Get SSL for IP Address:** Issue a short-lived (6-day, auto-renewing) certificate for a bare IP address.

### Certbot

To install and use Certbot:

```sh
apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```

### Cloudflare

The management script includes a built-in SSL certificate application for Cloudflare (uses DNS validation, so it also works for wildcard certificates and servers behind Cloudflare's proxy). To use it you need:

- A **Cloudflare API Token** (recommended) scoped to `Zone:DNS:Edit` for your zone, **or** your Cloudflare registered email + **Global API Key**
- The domain must be managed by Cloudflare (its nameservers point to Cloudflare)

Run the `x-ui` command in the terminal, then choose `Cloudflare SSL Certificate`. The script will ask whether you are using an **API Token** (`t`, default) or **Global API Key** (`g`), then prompt for the domain.

**How to create a scoped API Token (recommended):**

1. Visit [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens).
2. Click **Create Token → Edit zone DNS** template, scope it to your zone, and create it.
3. Copy the token and paste it into the script when prompted.

**How to get the Global API Key (alternative):**

1. Visit the link: [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens).
2. Click on "View Global API Key" (see the screenshot below):

![](https://github.com/MHSanaei/3x-ui/raw/main/media/APIKey1.PNG)

3. You may need to re-authenticate your account. After that, the API Key will be shown (see the screenshot below):

![](https://github.com/MHSanaei/3x-ui/raw/main/media/APIKey2.png)

When using the Global API Key, enter your `domain name`, `email`, and `API KEY`. The flow is as follows:

![](https://github.com/MHSanaei/3x-ui/raw/main/media/DetailEnter.png)

## Available environment variables

These are read at startup. On a systemd install you can set them in the service environment file (`/etc/default/x-ui`, `/etc/conf.d/x-ui`, or `/etc/sysconfig/x-ui`, depending on distro); for Docker, pass them with `-e` / the `environment:` block.

### `XUI_DB_TYPE`
* **Description**: Database backend
* **Type**: `string`
* **Acceptable values**: `sqlite` | `postgres`
* **Default value**: `sqlite`

### `XUI_DB_DSN`
* **Description**: PostgreSQL connection string (used when `XUI_DB_TYPE=postgres`)
* **Type**: `string`
* **Default value**: — (empty)
* **Example**: `postgres://xui:password@127.0.0.1:5432/xui?sslmode=disable`

### `XUI_DB_FOLDER`
* **Description**: Path to the folder holding the SQLite database file
* **Type**: `string`
* **Default value**: `/etc/x-ui`

### `XUI_DB_MAX_OPEN_CONNS`
* **Description**: Maximum number of open database connections
* **Type**: `integer`
* **Default value**: `25` (PostgreSQL) / `8` (SQLite)

### `XUI_DB_MAX_IDLE_CONNS`
* **Description**: Maximum number of idle database connections
* **Type**: `integer`
* **Default value**: `25` (PostgreSQL) / `4` (SQLite)

### `XUI_BIN_FOLDER`
* **Description**: Path to the folder with xray-core, geosite & geoip databases
* **Type**: `string`
* **Default value**: `bin`

### `XUI_LOG_LEVEL`
* **Description**: Default log level
* **Type**: `string`
* **Acceptable values**: `debug` | `info` | `notice` | `warning` | `error`
* **Default value**: `info`

### `XUI_LOG_FOLDER`
* **Description**: Path to the logs
* **Type**: `string`
* **Default value**: `/var/log/x-ui`

### `XUI_DEBUG`
* **Description**: Whether debug mode should be enabled (forces the log level to `debug`)
* **Type**: `boolean`
* **Default value**: `false`

### `XUI_ENABLE_FAIL2BAN`
* **Description**: Should [fail2ban](https://github.com/fail2ban/fail2ban) enforce per-client IP limits
* **Type**: `boolean`
* **Default value**: `true`

### `XUI_SKIP_HSTS`
* **Description**: Skip sending the `Strict-Transport-Security` (HSTS) header — useful when terminating TLS at a reverse proxy
* **Type**: `boolean`
* **Default value**: `false`

## Reverse Proxy

### Nginx

To configure the reverse proxy, add the following paths to your nginx config

```nginx
location / {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Range $http_range;
    proxy_set_header If-Range $http_if_range;

    proxy_redirect off;
    proxy_pass http://127.0.0.1:2053;
}

```

> [!NOTE]
> The URL in the panel settings needs to end with /.

For the subscriptions (the default subscription port is `2096`)

```nginx
location /sub {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Range $http_range;
    proxy_set_header If-Range $http_if_range;

    proxy_redirect off;
    proxy_pass http://127.0.0.1:2096;
}

```

> [!NOTE]
> Ensure that the "URI Path" in the /sub panel settings is the same.

### Caddy

> [!IMPORTANT]
> A huge thanks to [@Gill-Bates](https://github.com/Gill-Bates) for providing the config

> [!IMPORTANT]
> This configuration will work when the "WebSocket" transport is set inbound

Before configuring `caddyfile`, make sure that the following parameters are set in the panel setup

![Screenshot](https://github.com/user-attachments/assets/c1914df0-ec9f-47ad-8f56-fb872be75fa0)

After customizing the panel, modify the caddyfile as follows

```caddyfile
vpn.example.com {

    encode gzip

    # TLS 1.3 mandatory!
    tls {
        protocols tls1.3
    }

    # Protect your GUI with Basic Auth
    route /admin* {
        basic_auth {
            admin ******
        }
        reverse_proxy xx.xx.xx.xx:2053
    }

    # Obfuscate the Endpoint
    route /api/v1* {
        @websockets {
            header Connection *Upgrade*
            header Upgrade websocket
        }
        reverse_proxy @websockets xx.xx.xx.xx:54321
        respond "Forbidden" 403
    }

    # Security Header
    header {
        header_up Authorization { >Authorization }
        header_up Content-Type { >Content-Type }
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options nosniff
        X-Frame-Options SAMEORIGIN
        Referrer-Policy strict-origin-when-cross-origin
        -Server
        -X-Powered-By
    }

    # Fallback
    respond "Not found!" 404
}
```

The following data must be replaced in the config:

* `vpn.example.com` -> your domain.
* `admin *****` -> replace the asterisks with your password.

If you do not need HTTP Auth, remove the following lines

```caddyfile
basic_auth {
    admin ******
}
```

* `reverse_proxy xx.xx.xx.xx` -> replace the `xx.xx.xx.xx` with your IP
* `reverse_proxy @websockets xx.xx.xx.xx:54321` -> replace `54321` with your inbound port

## Setting Fail2Ban

The bundled Fail2ban integration enforces per-client **IP limits**: when a client exceeds its allowed number of simultaneous IPs, the offending address is temporarily banned.

> [!NOTE]
> The IP limit won't work correctly when using an IP Tunnel.

To set it up, run the `x-ui` command in the terminal and choose `IP Limit Management`. You will see the following options:

- **Install Fail2ban and configure IP Limit:** Install Fail2ban and set up the 3x-ui jail.
- **Change Ban Duration:** Adjust how long bans last.
- **Unban Everyone:** Lift all current bans.
- **Ban Logs:** View the ban history.
- **Ban an IP Address:** Manually ban an IP.
- **Unban an IP Address:** Manually unban an IP.
- **Real-Time Logs:** Follow the live Fail2ban log.
- **Service Status:** Check the status of `fail2ban`.
- **Service Restart:** Restart the `fail2ban` service.
- **Uninstall Fail2ban and IP Limit:** Remove Fail2ban and its configuration.

Then enable the access log so Fail2ban can read client connections: in the panel go to `Xray Configs`, set the `log → Access log` path to `./access.log`, save, and restart Xray.

> [!TIP]
> On Docker, Fail2ban is bundled and enabled by default (`XUI_ENABLE_FAIL2BAN=true`). It needs the `NET_ADMIN` (and `NET_RAW` for IPv6) capabilities to apply bans with iptables — see the Docker installation section.

---

# API Documentation

3X-UI ships an interactive **Swagger UI** built into the panel. Open the panel and click **API Docs** in the sidebar to browse every endpoint, see request/response schemas, and try calls live against your server.

- The OpenAPI 3 specification is served at `<your-panel-url>/panel/api/openapi.json`.
- API requests authenticate with your panel session cookie, or with an **API token** created under `Settings`.
- [API Documentation (Postman)](https://documenter.getpostman.com/view/5146551/2sBXwnsBko) — community-maintained collection.

## Geosites

### What is it?

The Geosites in Xray-core play a key role in traffic routing, enabling flexible control over traffic distribution based on the geographical location of IP addresses and domains. Here are their main files:

* `geoip.dat` contains a database of IP addresses classified by country (e.g., `geoip:cn` for China or `geoip:private` for private networks). This allows:

  * Redirecting traffic for specific countries (e.g., Chinese IPs via direct, others via proxy).

  * Blocking or allowing access to IPs from certain regions.

* `geosite.dat` includes domain lists grouped by categories (e.g., `geosite:cn` for Chinese domains, `geosite:google` for Google services). This is used for:

  * Granular traffic control (e.g., ad domains → block, streaming → proxy).

### Sources

3X-UI uses multiple geofiles sources for flexible traffic routing:

| Repository | Files | Available geosites |
| - | - | - |
| [Loyalsoldier/v2ray-rules-dat](https://github.com/Loyalsoldier/v2ray-rules-dat) | `geoip.dat` <br /> `geosite.dat` | [View](https://github.com/v2fly/domain-list-community/tree/master/data) |
| [🇮🇷 chocolate4u/Iran-v2ray-rules](https://github.com/chocolate4u/Iran-v2ray-rules) | `geoip_IR.dat` <br /> `geosite_IR.dat` | [View](https://github.com/chocolate4u/Iran-v2ray-rules?tab=readme-ov-file#page_with_curl-categories) |
| [🇷🇺 runetfreedom/russia-v2ray-rules-dat](https://github.com/runetfreedom/russia-v2ray-rules-dat) | `geoip_RU.dat` <br /> `geosite_RU.dat` | [View](https://github.com/runetfreedom/russia-v2ray-rules-dat?tab=readme-ov-file#%D0%BA%D0%B0%D0%BA%D0%B8%D0%B5-%D0%BA%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D0%B8-%D1%81%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D1%82%D1%81%D1%8F-%D0%B2-%D1%84%D0%B0%D0%B9%D0%BB%D0%B0%D1%85) |

### Updating geofiles

1. Open panel and click to Xray version

![image](https://github.com/user-attachments/assets/1f86d21b-c1a7-4268-9031-1cb5179dc38d)

2. Open `Geofiles` dropdown and update the needed geofile

![image](https://github.com/user-attachments/assets/6765b54d-3858-4fd3-b75b-17b5bc2b983f)

## Custom GeoSite / GeoIP DAT sources

Administrators can add custom GeoSite and GeoIP `.dat` files from URLs in the panel (same workflow as updating built-in geofiles). Files are stored under the same directory as the Xray binary (`XUI_BIN_FOLDER`, default `bin/`) with deterministic names: `geosite_<alias>.dat` and `geoip_<alias>.dat`.

**Routing:** Xray resolves extra lists using the `ext:` form, for example `ext:geosite_myalias.dat:tag` or `ext:geoip_myalias.dat:tag`, where `tag` is a list name inside that DAT file (same pattern as built-in regional files such as `ext:geoip_IR.dat:ir`).

**Reserved aliases:** Only for deciding whether a name is reserved, the panel compares a normalized form of the alias (`strings.ToLower`, `-` → `_`). User-entered aliases and generated file names are not rewritten in the database; they must still match `^[a-z0-9_-]+$`. For example, `geoip-ir` and `geoip_ir` collide with the same reserved entry.
