Remnawave¶
Updated: Mar 2026 Status: 25 nodes deployed, backend integration live, config delivery in PR
Quick Facts¶
| Panel URL | https://market-plus.vip/ (nginx on 212.70.189.60:443 → 10.99.87.249:3000) |
| Admin creds | Vaultwarden → Infrastructure → Remnawave Panel |
| Nodes | 25 active, xray 25.12.8, VLESS+TCP+Reality, port 443 |
| Subscription URL | https://market-plus.vip/api/sub/{shortUuid} |
| Node container | remnanode (network_mode: host, port 2222 mTLS) |
| Replaced | Hiddify (7 Feb 2026) — haproxy + PROXY protocol v2 broke Streisand iOS Reality handshake |
1. Subscription URL¶
Structure¶
market-plus.vip— neutral domain (not VPN-branded), proxied through 3 API proxies (DE/AT/NL)shortUuid— 36-char user identifier stored inaccount.remna_short_uuid- Public endpoint — no authentication required
Response Format¶
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Encoding: base64
dmxlc3M6Ly81ZDM2MjZmNC1jYzk5LTQ3YzctOTE4YS1hZTQ4MjM5ZmRhZWRAMTMwLjE5NS4yMjIu...
Decoded — one VLESS URL per line, 25 total:
vless://UUID@130.195.222.205:443?encryption=none&flow=xtls-rprx-vision&reality=1&pbk=OISHrR3ONyKjXNjVv277fMwBt28kjEARIgFWftG-Rkk&sid=&sni=dl.google.com&fp=chrome&type=tcp&host=dl.google.com#AT-Vienna-Reality
vless://UUID@146.70.121.16:443?encryption=none&flow=xtls-rprx-vision&reality=1&pbk=OISHrR3ONyKjXNjVv277fMwBt28kjEARIgFWftG-Rkk&sid=&sni=dl.google.com&fp=chrome&type=tcp&host=dl.google.com#GB-Manchester-02-Reality
Key properties:
- One subscription URL covers all 25 nodes
- Base64 encoding (standard subscription format for V2rayNG/Streisand/Hiddify Next)
- Remnawave tracks usedTrafficBytes per user
- Client apps typically re-poll every hour
User Model in Remnawave¶
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"shortUuid": "550e8400-e29b-41d4",
"username": "1234567890123456", // account.account (16-digit)
"status": "ACTIVE",
"trafficLimitBytes": 0, // 0 = unlimited
"trafficLimitStrategy": "NO_RESET",
"expireAt": "2026-12-31T23:59:59Z",
"userTraffic": {
"usedTrafficBytes": 1073741824,
"lifetimeUsedTrafficBytes": 5368709120,
"onlineAt": "2026-02-17T14:30:00Z"
},
"subscriptionUrl": "https://market-plus.vip/api/sub/550e8400-e29b-41d4"
}
2. Backend API¶
GET /api/v2/remna/subscription¶
Returns the subscription URL for the authenticated user. Auto-provisions user in Remnawave on first call if not yet created.
Response:
{
"subscriptionUrl": "https://market-plus.vip/api/sub/550e8400-e29b-41d4",
"shortUuid": "550e8400-e29b-41d4",
"active": true,
"remainingDays": 47
}
Implementation: RemnaController.java (backend-refactoring/src/main/java/com/karmavpn/rest/)
Key logic:
- Returns remnaShortUuid from account.remna_short_uuid
- Calculates remainingDays from account.validTo
- Returns 404 if auto-provision fails
3. Config Delivery: Two Paths¶
Status: feature/remnawave-config-delivery PR (not merged as of Feb 2026).
Path A: Transport Params (fast path)¶
Used for: XUI servers (84) + WL proxy servers (11) + Remnawave nodes with transport_params populated.
Flow:
ConfigDeliveryService.buildConfigs()
→ parse server.transport_params from MySQL
→ generate VLESS on-the-fly
→ return in /api/v2/countries response
No XUI HTTP calls or database lookups — pure in-memory construction from cached transport_params.
Path B: Subscription Fallback¶
Used for: Remnawave nodes without transport_params.
Flow:
if (account.remnaShortUuid != null)
→ remnaService.fetchSubscription(shortUuid)
→ parse 25 VLESS URLs
→ add to /api/v2/countries response
Code in ConfigDeliveryService.java:
// Path 1: transport_params (XUI, WL, Remnawave with transport_params)
for (var entry : byCountry.entrySet()) {
CountryConfigResponse config = buildCountryConfig(
entry.getKey(), entry.getValue(), account, lang);
if (config != null) {
countryConfigs.add(config);
coveredCountries.add(entry.getKey());
}
}
// Path 2: Remnawave subscription (for servers without transport_params)
if (remnaService != null && account.getRemnaShortUuid() != null) {
List<CountryConfigResponse> remnaConfigs = buildRemnaSubscriptionConfigs(
account, lang, coveredCountries);
countryConfigs.addAll(remnaConfigs);
}
4. RemnaServiceImpl Methods¶
File: backend-refactoring/src/main/java/com/karmavpn/service/integration/impl/RemnaServiceImpl.java
| Method | HTTP | Purpose |
|---|---|---|
createUser() |
POST /api/users |
Create user (username, trafficLimitBytes, expireAt) |
getUserByUsername() |
GET /api/users/username/{n} |
Fetch user by account number |
getUserByUuid() |
GET /api/users/{uuid} |
Fetch user by UUID |
updateUser() |
PATCH /api/users/{uuid} |
Update expireAt, trafficLimitBytes |
enableUser() |
PATCH /api/users/{uuid}/enable |
Re-activate user |
disableUser() |
PATCH /api/users/{uuid}/disable |
Suspend user |
deleteUser() |
DELETE /api/users/{uuid} |
Remove user |
listAllUsers() |
GET /api/users |
Fetch all users (for reconciliation) |
fetchSubscription() |
GET /api/sub/{shortUuid} |
Get base64-encoded VLESS URLs |
Configuration (application-prod.properties)¶
remna.enabled=true
remna.api-url=http://10.99.87.249:3000
remna.api-token=${REMNA_API_TOKEN}
remna.subscription-base-url=https://market-plus.vip/api/sub
remna.connect-timeout=5s
remna.read-timeout=15s
Usage Example¶
// Create user (on first claim)
RemnaUserResponse user = remnaService.createUser(
"1234567890123456", // account number
0, // unlimited traffic
"2026-12-31T23:59:59Z"
);
account.setRemnaUuid(user.getUuid());
account.setRemnaShortUuid(user.getShortUuid());
accountRepository.save(account);
// Get subscription URL
String url = "https://market-plus.vip/api/sub/" + user.getShortUuid();
// Update expiry on renewal
remnaService.updateUser(user.getUuid(), null, "2026-06-30T23:59:59Z");
// What the client sees
List<String> vlessUrls = remnaService.fetchSubscription(user.getShortUuid());
// ["vless://UUID@130.195.222.205:443?...", ...]
5. Implementation Phases¶
Phase 1: Backend Integration (COMPLETE, in production)¶
RemnaServiceImpl— 8 methods, full CRUDRemnaController—GET /api/v2/remna/subscriptionRemnaSyncScheduler— daily reconciliation at 3:30 AM- DB columns:
account.remna_uuid,account.remna_short_uuid(V102 migration) panel_type= REMNAWAVE enum (V103 migration)- Properties:
remna.api-url,remna.api-token
Phase 2: Telegram Bot — Shiva Light (PLANNED)¶
- Python RemnaService (REST client to Remnawave API)
- User mapping:
telegram_user_id → remna_uuid, remna_short_uuid - Payment integration (
/prolong {days}) - Traffic display (
/status)
Estimated: 2-3 days.
Phase 3: Config Delivery Merge (IN PROGRESS)¶
- Branch:
feature/remnawave-config-delivery(4 commits) - V108 migration: add
transport_paramsto 23 Remnawave servers ConfigDeliveryService: both paths (transport_params + subscription fallback)NodeSyncService: on-demand user creation (ensureRemnaUser())- Blocker: waiting for
feature/backend-refactoringmerge first (145 commits, 431 tests)
Phase 4: Full XUI Migration (FUTURE)¶
- Migrate 84 XUI servers to Remnawave nodes
- Bulk user creation (400K users)
- Decommission VPN Config Service
fill_configsandcleanup_expired - Centralized traffic tracking via Remnawave API
Estimated: 2-4 weeks full-time.
6. Known Limitations¶
1. Subscription URL Leakage Risk¶
URL is public — anyone who knows shortUuid can fetch all VLESS links.
Current mitigation: shortUuid is 36 characters; brute-force is impractical. This is the standard risk model for subscription-based VPN.
Future: time-limited token rotation, IP-based whitelist on Remnawave panel.
2. No Bulk User Creation Endpoint¶
Creating 400K users one by one = 400K API calls. Known Remnawave issue: rate limit kicks in after ~20 requests before JWT expires.
Workaround: batch groups of 100 with retry logic.
Future: Remnawave /api/users/bulk endpoint, or direct PostgreSQL INSERT.
3. Single Panel Instance (SPOF)¶
One Remnawave panel at 10.99.87.249 manages all 25+ nodes. If down → no config updates.
Current mitigation: daily PostgreSQL backups, panel restarts quickly (Docker).
Future: Remnawave HA clustering.
4. Traffic Limits Not Enforced in Real Time¶
trafficLimitBytes is stored but Remnawave doesn't enforce per-user bandwidth cap in real time (XUI does via xray config). Only expireAt is enforced via scheduler.
Future: traffic limiter in Remnawave node xray config.
5. Dual UUID per User¶
Users with both XUI and Remnawave subscriptions have two separate UUIDs:
- account.xray_uuid (XUI path)
- account.remna_uuid (Remnawave path)
Future: unified ID, migrate XUI users to Remnawave UUID.
7. Client App Subscription Flow¶
-
User imports URL into V2rayNG / Streisand / Hiddify Next:
-
App fetches and decodes:
-
App parses 25 VLESS URLs, creates one connection profile per server
-
User selects a profile and connects
-
Periodic re-poll (typically hourly):
- Detects new nodes added in Remnawave
- Removes decommissioned nodes
- Updates traffic counters (if shown)
Advantages: - Zero backend dependency for config updates - Instant propagation: add node to Remnawave → clients auto-detect at next poll - Works with any VLESS-compatible client
8. Related Files¶
| File | Purpose |
|---|---|
RemnaServiceImpl.java |
Remnawave REST client (8 methods) |
RemnaController.java |
/api/v2/remna/subscription endpoint |
RemnaSyncScheduler.java |
Daily reconciliation (3:30 AM) |
ConfigDeliveryService.java |
On-the-fly config (both delivery paths) |
V102__remnawave_integration.sql |
DB migration: remna_uuid columns |
V103__remnawave_panel_type.sql |
DB migration: panel_type enum + REMNAWAVE |
V108__remnawave_transport_params.sql |
DB migration: transport_params for 23 nodes |
planning/PLAN-XUI-TO-REMNAWAVE-MIGRATION.md |
Per-server migration plan (6-week timeline) |
planning/PLAN-REMNAWAVE-FULL-MIGRATION.md |
Strategic roadmap (all 4 phases) |
См. также: Ansible · Репозитории · VPN Config Service · Инвентарь серверов