Skip to content

External access to API

The Remnawave panel can serve its API to external clients: bots, management panels, statistics tools. By default the script hides everything behind the cookie gate or a login page, so only what you open yourself goes outside: one location in nginx.conf, and from then on every request passes the same protection as the browser, plus the panel token (Settings → API tokens).

Step 1. Public path

The /api/ route in nginx.conf or the Caddyfile: the API goes public, the panel token stays.

Step 2. Your login method

A cookie, the TinyAuth login page, or a Caddy key: whatever was chosen at panel installation.

Token everywhere

Authorization: Bearer … travels in every request regardless of the login method.

The block is the same for every install type (“panel only” and “panel + node”).

  1. Open the nginx configuration:

    root@server: ~
    nano /opt/remnawave/nginx.conf
  2. In the server block with the panel domain (server_name panel.example.com;) add a location: it takes /api/ out from under the cookie check and hands requests straight to the panel:

    nginx.conf
    location ^~ /api/ {
    proxy_http_version 1.1;
    proxy_pass http://remnawave;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;
    }
  3. Restart the web server so the configuration applies:

    root@server: ~
    cd /opt/remnawave && docker compose restart remnawave-nginx
  4. Check that it answers from the outside (a request without a token must return an authorization error, not a timeout):

    root@server: ~
    curl -i https://panel.example.com/api/nodes

From here everything depends on how the panel is protected, chosen at installation. The panel token (Authorization: Bearer …) travels in every request on top of any of the options below.

The script generates a random NAME=VALUE pair. Open nginx.conf and find the map $http_cookie $auth_cookie line; your data is the value after ~*:

map $http_cookie $auth_cookie {
default 0;
"~*zIdejKtN=MWbLrRzx" 1;
}

Here the cookie is zIdejKtN=MWbLrRzx.

Using the HTTP client httpx and the COOKIES entry in your project’s .env as an example:

.env
COOKIES={"zIdejKtN":"MWbLrRzx"}
token = os.getenv("API_TOKEN", "")
base_url = os.getenv("REMNAWAVE_BASE_URL", "")
cookies = json.loads(os.getenv("COOKIES", "{}"))
async def get_all_nodes():
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + token
}
async with httpx.AsyncClient(cookies=cookies) as async_client:
response = await async_client.get(f"{base_url}/api/nodes", headers=headers)
return response.json()

A quick curl check:

root@server: ~
curl -b "zIdejKtN=MWbLrRzx" -H "Authorization: Bearer <token>" https://panel.example.com/api/nodes

The “subscription page only” install asks how the panel is protected and fills everything in for you: with TinyAuth you just enter the login page username and password, the installer assembles the base64 pair itself.

Panel protection What the installer asks Variable in the sub page compose
Cookie gate the NAME=VALUE cookie EGAMES_COOKIE=NAME=VALUE
Login page (TinyAuth) username and password CADDY_AUTH_API_TOKEN=Basic base64(...)
Caddy with MFA the login page API key CADDY_AUTH_API_TOKEN=<key>