IP Restriction (allowed_ips)
allowed_ips is a per-reseller comma-separated allowlist that restricts where the reseller can log in from and where their API calls can come from. If set, every login attempt and every authenticated request is checked: if the client IP isn’t in the list, the request is rejected with 403 “Access denied from this IP address”.
This is the simplest tool ProxPanel has for hardening a high-trust account like a partner / read-only role. It’s also the easiest to get wrong, because client IPs aren’t always what you’d expect.
How to get here
Section titled “How to get here”Admin only. From the Resellers page:
- Resellers → click reseller → edit form → Allowed IPs field.
- Or directly:
/resellers/{id}/edit.
The field is part of the admin-only field whitelist on the reseller update endpoint — a reseller cannot change their own allowed_ips, only an admin can.
Format
Section titled “Format”The field accepts a comma-separated list. Each entry is either:
| Format | Example | Matches |
|---|---|---|
| Single IPv4 | 203.0.113.42 | Exactly this address. |
| IPv4 CIDR | 203.0.113.0/24 | Any address in the /24 range. |
| Single IPv6 | 2001:db8::1 | Exactly this address. |
| IPv6 CIDR | 2001:db8::/32 | Any address in the /32 range. |
The list is OR’d — if any entry matches, the request is allowed. Whitespace around entries is trimmed.
Empty field = no restriction (the default). All IPs are allowed.
When the check runs
Section titled “When the check runs”The check fires on every authenticated request, not just login. Specifically:
| Trigger | What happens |
|---|---|
POST /auth/login | If credentials are valid but IP isn’t allowed → 403, no token issued. |
| Any authenticated API call | Middleware re-checks every request → 403 if IP isn’t allowed. Existing JWT is not enough. |
| Customer portal (subscriber login) | The check applies only to reseller-type users. Subscribers (user_type=1) have no IP restriction. |
This means rotating a reseller’s IP allowlist takes effect immediately — there’s no need to wait for sessions to expire. A reseller whose IP just got removed from the list is logged out on their next API call.
Since v1.0.548 the check was widened from “resellers only” to all non-admin user types — so support staff, collectors, and read-only accounts can also be IP-restricted using their owning reseller’s allowed_ips. Admins are still exempt (locking out the only admin would be unrecoverable).
The gotcha — which IP is “your” IP?
Section titled “The gotcha — which IP is “your” IP?”This is where most setups go wrong. The check looks at the HTTP client IP, which is the public IP your browser appears to come from — not the IP the panel sees on its private network and not the PPPoE IP your customers see.
| Source | What gets sent to the API | What allowed_ips should contain |
|---|---|---|
| Browser at the office | The office’s public NAT egress IP, e.g. 203.0.113.42 | 203.0.113.42 |
| Browser on home WiFi | The home ISP’s public IP (often dynamic) | A wide CIDR, or the customer’s static IP |
| Browser through a VPN | The VPN exit node’s public IP | The VPN endpoint IP |
| Mobile app on cell network | The carrier’s public NAT IP (often shared across thousands of users) | A CIDR of the carrier’s range — or don’t restrict |
| Reseller logging in from inside a PPPoE subscriber session | The public IP of the NAS, not the PPPoE Framed-IP — because the request egresses via NAT | The NAS’s public IP, not 10.180.96.x |
How to find your real client IP
Section titled “How to find your real client IP”The easiest test: from the network you want to allowlist, open https://api.ipify.org/ in a browser. Whatever IP it returns is what the panel will see in c.IP() from that network.
For internal testing on the panel host itself, set X-Real-IP or check the nginx access log:
docker logs proxpanel-api 2>&1 | grep "your-username" | tailThe IP in the log entry is what the middleware checks against.
How the check is implemented
Section titled “How the check is implemented”The middleware calls IsIPInList(reseller.AllowedIPs, c.IP()). The function:
- Returns true immediately if the allowlist is empty.
- Splits the list by comma, trims each entry.
- For each entry: parses as a CIDR if it contains
/, otherwise as a single IP. - Returns true on first match. Returns false if no entry matches.
The check is O(n) in the number of entries. Lists with more than ~50 entries should use a CIDR instead.
Common workflows
Section titled “Common workflows”Locking a partner role to one office
Section titled “Locking a partner role to one office”- From the partner’s office, browse to
https://api.ipify.org/. Note the IP, e.g.203.0.113.42. - Resellers → partner row → edit → Allowed IPs =
203.0.113.42. Save. - From the partner’s office: log in. Success.
- From your phone hotspot: log in. 403 “Access denied from this IP address”. Restriction confirmed.
Allowing office + home VPN
Section titled “Allowing office + home VPN”- Get the office’s public IP:
203.0.113.42. - Get the home VPN’s public IP:
198.51.100.10. - Allowed IPs =
203.0.113.42, 198.51.100.10. Save. - Verify from both networks.
Wider net — allow any address from a known ISP
Section titled “Wider net — allow any address from a known ISP”- Find the ISP’s IP range. For a small ISP, this might be
203.0.113.0/24. - Allowed IPs =
203.0.113.0/24. Save. - Anyone on that ISP’s network can log in. Anyone outside cannot.
- Note: this is less secure than pinning to one IP, but useful for resellers who roam within a known network.
Recovering from “I locked myself out”
Section titled “Recovering from “I locked myself out””A reseller who locks themselves out cannot fix it themselves — they can’t log in to edit the field. The recovery path is admin-only:
- Admin logs in (admins are exempt from
allowed_ips). - Resellers → the locked-out reseller → edit → clear or widen the Allowed IPs field. Save.
- The reseller can log in immediately.
Caveats and edge cases
Section titled “Caveats and edge cases”- Cloudflare / load balancers in front of the panel rewrite the source IP. The panel reads
X-Real-IP(set by the bundled nginx) — make sure your upstream proxy sets that header correctly, or the check will see the proxy’s IP instead of the real client. - IPv6. If the panel is reachable on IPv6 and a reseller’s browser prefers IPv6, the client IP will be an IPv6 address. The allowlist must include the IPv6 form too.
- Dynamic IPs. Many home ISPs rotate IPs every 24h. Locking down by IP only works if the network you’re allowlisting has a static IP — usually office, datacenter, or a VPN endpoint.
- WireGuard / custom VPN. A nice pattern is to issue the reseller a WireGuard config — their traffic always egresses from your VPN endpoint, so you allowlist that one IP.
Permissions
Section titled “Permissions”| Permission | Effect |
|---|---|
| Admin-only field | Only admins can edit allowed_ips. Resellers cannot edit their own (or anyone else’s) value. |
resellers.edit | Required to save changes (admin already has it). |
Related pages
Section titled “Related pages”- Partner / Read-Only Role — where IP restriction is most commonly used.
- Sub-Resellers — sub-resellers inherit nothing from the parent’s
allowed_ips; they’re independent. - Reseller Onboarding — setting
allowed_ipson initial create. - Audit Log — every blocked login is recorded with the source IP.
- Settings → Security — system-wide rate-limit and brute-force settings.