Speed Limits Wrong
A customer complains their speed is wrong. You look at the MikroTik simple queue and the rate is 1000x too high or too low. This is almost always the kb-vs-Mbps unit bug that bit several installs across v1.0.140 through v1.0.149. This page tells you how to confirm it, how to fix it on a live server, and how to verify it stays fixed.
Symptoms
Section titled “Symptoms”- Customer on a “2M” plan is getting 2 Gbps in the MikroTik queue (
/queue simple printshowsmax-limit=2000000k/2000000k) - Customer on a “10M” plan is getting 10 kbps — barely usable
- “Reset FUP” or “Renew” bulk action makes the problem appear; before the action, speed was correct
- New subscribers get the right speed; existing subscribers who were touched by a bulk action get the wrong speed
- Bandwidth Rules (time-based boosts) applied — the boost is fine but the base value is suddenly 1000x
Background — speed storage format
Section titled “Background — speed storage format”Since v1.0.95, ProxPanel stores speeds in kilobits (k) end to end. The integer column download_speed = 2000 means 2000 kbps = 2 Mbps. The string column download_speed_str = '2000k' is the human-readable form.
When the RADIUS server replies, it builds Mikrotik-Rate-Limit: 2000k/1200k. MikroTik interprets that as 2 Mbps down / 1.2 Mbps up. No multiplication anywhere.
The bug appeared in several places where a developer assumed download_speed was in Mbps and multiplied by 1000 — turning 2000 (=2 Mbps) into 2,000,000k = 2 Gbps.
Diagnostic flow
Section titled “Diagnostic flow”-
Check the service definition — is the stored kb value correct?
Terminal window docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \"SELECT id, name, download_speed, upload_speed, download_speed_str, upload_speed_str \FROM services WHERE id = SERVICE_ID;"For a 2 Mbps plan, expect
download_speed = 2000,download_speed_str = '2000k'. If you see2, that’s a Mbps-stored-as-integer bug (very old data) and the service needs editing. -
Check radreply for the user.
Terminal window docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \"SELECT username, attribute, value FROM radreply \WHERE username='USERNAME' AND attribute='Mikrotik-Rate-Limit';"You want something like
2000k/1200k(up to 4 fields with burst). If you see2000000k/1200000k, a bulk action wrote the wrong rate. If radreply has nothing, RADIUS is falling back to the service default — check step 1. -
Check the live MikroTik queue.
/queue simple print where name~"USERNAME"max-limit=2M/2Mis fine.max-limit=2G/2Gconfirms the live rate is wrong. -
Cross-check FUP tier. If the subscriber has crossed a FUP threshold, the radreply value is from the FUP tier, not the service default.
Terminal window docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \"SELECT username, fup_level, monthly_fup_level, daily_quota_used, monthly_quota_used \FROM subscribers WHERE username='USERNAME';"
Cause 1 — Bulk action wrote 1000x value
Section titled “Cause 1 — Bulk action wrote 1000x value”Versions before v1.0.149 had handlers for Renew, Reset FUP, and bandwidth-rule application that multiplied download_speed by 1000 before formatting. After running one of those actions on a live install, radreply ended up with 2000000k/1200000k.
Diagnostic:
# How many users have absurdly high speeds in radreply?docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \ "SELECT username, value FROM radreply \ WHERE attribute='Mikrotik-Rate-Limit' \ AND (value LIKE '%000000k/%' OR value LIKE '%G/%') \ LIMIT 50;"Fix — rewrite radreply from the service definition:
-
Back up radreply first.
Terminal window docker exec proxpanel-db pg_dump -U proxpanel -d proxpanel -t radreply > /tmp/radreply-backup.sql -
Identify affected users (e.g. anything ending in
000000k):Terminal window docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \"SELECT COUNT(*) FROM radreply \WHERE attribute='Mikrotik-Rate-Limit' AND value LIKE '%000000k/%';" -
Rewrite the values from each user’s service:
UPDATE radreply rSET value = s.upload_speed_str || '/' || s.download_speed_strFROM subscribers subJOIN services s ON s.id = sub.service_idWHERE r.username = sub.usernameAND r.attribute = 'Mikrotik-Rate-Limit'AND r.value LIKE '%000000k/%';Run it inside
docker exec proxpanel-db psql -U proxpanel -d proxpanel. -
Push the corrected rate to anyone currently online via CoA so they don’t have to disconnect.
The cleanest way is the bulk Reset FUP action in the UI after you’ve upgraded to v1.0.149 or later (the fix is in
internal/handlers/subscriber.goandinternal/services/quota_sync.go). On the subscribers list → select all affected → Reset FUP. The handler re-applies the correct rate via MikroTik API and CoA. -
Confirm. Pick a user:
/queue simple print where name~"USERNAME"You want
2M/2Mnot2G/2G.
Cause 2 — Service stored in Mbps as integer
Section titled “Cause 2 — Service stored in Mbps as integer”Very old services from before v1.0.95 may have download_speed = 2 (meaning 2 Mbps) instead of 2000. The current code expects kb.
Diagnostic:
docker exec proxpanel-db psql -U proxpanel -d proxpanel -c \ "SELECT id, name, download_speed, upload_speed FROM services WHERE download_speed < 100;"Any service with download_speed < 100 is suspicious — that’s less than 100 kbps. Either it’s truly that low (rare) or the row is in stale Mbps.
Fix:
In the Services page, open the affected service and re-enter the speed. The handler converts user input (2M, 1.5M, 2000) into kb via convertSpeedForMikrotik() and saves both the integer and string columns consistently. Save the service. New connections inherit the new value; for existing online users, the bulk Reset FUP action re-pushes the rate.
Cause 3 — Bandwidth rule multiplier wrong
Section titled “Cause 3 — Bandwidth rule multiplier wrong”A per-time-window bandwidth rule (e.g. “NIGHT” doubles speed during 22:00–06:00) is applied as a percentage multiplier. Versions before v1.0.149 multiplied by 1000 in bandwidth_rule_service.go. After v1.0.149 the math is base × multiplier / 100.
Diagnostic:
docker logs proxpanel-api 2>&1 | grep -E "BandwidthRule|SubscriberRule" | tail -50A correct log line looks like:
BandwidthRule: Applied NIGHT to john@example.net: 2000k/1200k -> 4000k/2400k (200%/200%)A buggy one looks like:
BandwidthRule: Applied NIGHT to john@example.net: 2000k/1200k -> 4000000k/2400000kFix:
Upgrade to v1.0.149 or later. After upgrading, anyone who got the 1000x value during the window keeps it until the rule’s end time triggers the restore — or until you run the bulk Reset FUP to push the correct rate immediately.
How to verify without disconnecting anyone
Section titled “How to verify without disconnecting anyone”-
Don’t kick users from RADIUS. That just sends Access-Reject and the customer reconnects to whatever radreply currently says — if radreply is wrong, the new session is wrong too.
-
Use CoA via Reset FUP or bulk Renew. Both re-compute the rate from the (now-correct) service / radreply and push it to MikroTik via CoA. The user stays connected, their queue updates in place, no PPPoE renegotiation.
-
Spot-check on MikroTik:
/queue simple print where name~"USERNAME"/ppp active print where name="USERNAME"The queue
max-limitshould match the radreply value byte-for-byte. -
Watch CPU during bulk Reset FUP. Each CoA is a round-trip to the NAS. For 5,000 users, expect 30–60 s of MikroTik API/CoA traffic and a brief CPU bump. Stagger across multiple NASes if you have them.
The v1.0.387 quota over-count incident
Section titled “The v1.0.387 quota over-count incident”A different but related bug: in v1.0.387 the quota counter was reading bytes from MikroTik’s /queue/simple/print, which is a cumulative figure that persists across PPPoE sessions for the same queue. After a service change moved a subscriber to a new pool, the inherited queue’s old bytes were credited as a fresh +60 GB delta. 79 subscribers ended up with counters 3–33x over reality.
If your customer’s speed is correct but their monthly quota looks impossibly high, that’s the same family of bug. The fix landed in v1.0.387 — bytes now come from /ppp/active (per-session) and a sanity cap in quota_sync.go rejects any single delta over 1 GB / 30 s. Reconciliation SQL is at tools/fix_inflated_quotas.sql in the repo.
Still stuck?
Section titled “Still stuck?”Send:
# Service definitiondocker exec proxpanel-db psql -U proxpanel -d proxpanel -c \ "SELECT * FROM services WHERE id=SERVICE_ID;" > /tmp/service.txt
# All radreply rows for the userdocker exec proxpanel-db psql -U proxpanel -d proxpanel -c \ "SELECT * FROM radreply WHERE username='USERNAME';" > /tmp/radreply.txt
# What MikroTik thinks# (run on the MikroTik): /queue simple print where name~"USERNAME"# (run on the MikroTik): /ppp active print where name="USERNAME"
# Versioncurl -s http://localhost:8080/healthEmail info@proxrad.com.
Related pages
Section titled “Related pages”- FUP Not Resetting — when daily counters don’t zero at midnight and rate stays at the FUP tier.
- Services — where the kb format is set and the
convertSpeedForMikrotik()helper normalises input. - Bandwidth Rules — time-windowed boost percentages and how they stack.