Audit Logs
The Audit Logs page is your forensic record. Every state change made by a human user — admin, reseller, sub-reseller, support, or collector — is recorded in the audit_logs table with a precise description of what changed, what the old value was, what the new value is, who did it, from where, and when. Read-only after the fact: rows are append-only and not editable from the UI.
This page is the answer to “who deleted that subscriber?”, “did anyone change the price?”, “when did this reseller’s permission group change?”, and the regulatory question “show me a record of every action taken in your billing system for the last 12 months.”
How to get here
Section titled “How to get here”- Sidebar → Audit (clipboard icon).
- Direct URL:
/audit.
Admins only by default. A reseller can be granted audit.view to see entries scoped to their own subscribers/sub-resellers — useful for parent companies that need to oversee a sub-reseller without giving them full admin rights.
Layout
Section titled “Layout”| Column | Source |
|---|---|
| Time | created_at — UTC, rendered in panel timezone. |
| User | username + user_type (admin / reseller / collector). |
| Action | One of create, update, delete, restore, login, logout, login_failed, bulk_action, impersonate. |
| Entity | The thing that changed: subscriber, service, nas_device, reseller, invoice, user, permission_group, etc. |
| Description | Human-readable summary, with old/new values inline where applicable. |
| IP | Real client IP (see below). |
| User-agent | Browser or API client. |
Filters at the top: user, action type, entity type, date range, IP address, free-text search across the description. The CSV export emits exactly what the table shows after filtering.
Real client IP — X-Real-IP header
Section titled “Real client IP — X-Real-IP header”ProxPanel runs behind nginx in production. Without configuration, Fiber would see the Docker bridge IP (172.18.0.x) on every request, making audit logs useless for forensic work. The fix is two-part:
- Fiber config in
cmd/api/main.gosetsProxyHeader: "X-Real-IP"and trusts the internal Docker subnets (172.16.0.0/12,10.0.0.0/8,192.168.0.0/16). - nginx forwards the real client IP via
X-Real-IPandX-Forwarded-Forheaders.
Result: audit log entries show the actual public IP of the operator’s browser, even when the panel is behind multiple proxies (e.g., Cloudflare → nginx → API). The middleware also parses X-Forwarded-For to handle chained proxies correctly.
If you ever see Docker IPs in audit logs, either nginx is misconfigured or the API container started before its config was updated — restart the API after fixing nginx.
What gets recorded
Section titled “What gets recorded”The audit middleware is in backend/internal/middleware/audit.go. It wraps every authenticated handler and:
- Captures the request path, method, and parameters before
c.Next(). - Lets the handler complete normally.
- Reads three locals the handler may have set:
audit_description,audit_entity_id,audit_entity_name. - Builds a single
audit_logsrow with the user identity, IP, user-agent, description, and entity reference.
Why locals and not automatic struct diffs? Because Go’s empty strings ("") are rejected by PostgreSQL jsonb columns, an early attempt at auto-diffing silently dropped audit rows. The handler now constructs the human-readable change message itself — e.g., for a subscriber update:
Updated subscriber "info1633": Status: Active → Inactive, Price: $0.00 → $25.00The diff helper (buildSubscriberChanges()) compares old vs. new field by field and renders only the changed fields. This same pattern is used for services, resellers, NAS devices, permission groups, and settings.
Sensitive-value handling
Section titled “Sensitive-value handling”Passwords are never written to the audit log in plaintext. Subscriber password changes log only "Password changed" (and only when the value actually differs from the previously-stored encrypted value — a fix from v1.0.256 stopped logging “Password changed” on every save). Reseller and admin passwords are handled the same way.
API keys, two-factor secrets, RADIUS secrets, and MikroTik API passwords are likewise replaced with *** in audit descriptions.
Bulk action logging
Section titled “Bulk action logging”Bulk operations from the Bulk Operations page log a single audit row, not one per subscriber. The row’s description summarises the action and the counts:
ChangeBulk: Bulk renewed for 124 subscribers (3 failed)Each affected subscriber’s last_modified_by field is updated, so you can trace from the subscriber back to the bulk action’s audit row by timestamp + user. This keeps the audit table manageable at scale — a single bulk action on 50,000 subscribers writes one row, not 50,000.
Login and authentication events
Section titled “Login and authentication events”login, logout, and login_failed actions are written by the auth handler:
| Event | When it fires |
|---|---|
login | Successful authentication. Description includes 2FA usage if enabled. |
login_failed | Wrong password, expired account, deactivated reseller, or token blacklist hit. The reason is in the description so you can spot brute-force attempts. |
logout | User clicks logout. The JWT is blacklisted (see Settings → Security). |
impersonate | An admin uses Login as Reseller to assume that reseller’s session. The original admin identity is preserved in the row so you can trace the chain. |
A flood of login_failed from a single IP is a red flag — combine with the brute-force lockout in Settings → Security for automated mitigation.
Filtering and search
Section titled “Filtering and search”The handler supports:
- User filter — exact username; combine multiple by Ctrl-clicking.
- Action filter — multi-select.
- Entity filter —
subscriber,service,nas_device,reseller,invoice,user,permission_group,settings,backup,cluster, etc. - Date range — defaults to last 7 days; widen for compliance pulls.
- IP filter — exact match or CIDR (
192.168.1.0/24matches a whole subnet). - Search — case-insensitive substring across the description.
The query is index-backed on (user_id, created_at) and (entity_type, entity_id, created_at) so filters return in milliseconds even at multi-million-row scale.
Export
Section titled “Export”The Export CSV button runs the same query without pagination and streams the result. Columns match what is shown on screen. The filename embeds the filter — e.g., audit-2026-04-15_to_2026-05-12_user-admin.csv — so compliance archives don’t blur together.
For very long retention windows, see Backups — full audit history is backed up alongside the rest of the database.
Common workflows
Section titled “Common workflows””Who deleted my subscriber?”
Section titled “”Who deleted my subscriber?””- Open Audit, filter
action = deleteand entity type =subscriber. - Search the username in the description box.
- The row tells you the operator, IP, and time.
- If it was a bulk delete, the row says
ChangeBulk: Delete for N subscribers— the bulk action history table inside the subscriber’s archived record points back here.
Confirm a price change wasn’t tampered with
Section titled “Confirm a price change wasn’t tampered with”- Filter entity =
serviceand action =update. - Scan descriptions for
Price:— every price change shows the old and new value inline. - If a row shows an unexpected actor or IP, cross-reference with the same window’s
loginevents to confirm the session was genuine.
Investigate a possible brute-force attempt
Section titled “Investigate a possible brute-force attempt”- Filter action =
login_failed. - Group by IP (sort descending by count). High counts from a single IP is the signal.
- Check whether the security lockout in Settings is enabled.
- If needed, add the offending IP to the API rate-limit denylist (nginx) and to the security alerts in the license server.
Permissions
Section titled “Permissions”| Permission | What it gates |
|---|---|
audit.view | Open the page and run queries. |
audit.export | Use the CSV export button. |
A reseller granted audit.view sees only entries where the actor is themselves or one of their sub-resellers, and where the entity belongs to their scope. Admins see everything.
Related pages
Section titled “Related pages”- Users & Permissions — defines which users can do what.
- Settings → Security — JWT blacklist, brute-force lockouts, IP whitelist.
- Bulk Operations — bulk actions write a single audit row each.
- Backups — audit history is included in every backup.
- Tickets — ticket replies and assignments are also audited.