OxidProxy — User Guide
Document version: 1.15
Software version: 2.6.2
Website: https://oxidproxy.com
Support: support@oxidproxy.com · Telegram
Contents
- Product overview
- System requirements
- Installation
- 3.1 Automatic installation (recommended)
- 3.2 Manual installation
- 3.3 What the installer creates
- 3.4 Upgrading and removing - Initial setup
- Configuration (config.yaml)
- 5.1 Licensing
- 5.2 Network settings
- 5.3 OS fingerprinting
- 5.4 IPv6 subnets and rotation
- 5.5 API and user management
- 5.6 DNS resolver
- 5.7 Traffic shaping
- 5.8 Timeouts
- 5.9 Service account
- 5.10 Blacklist
- 5.11 Logging
- 5.12 MITM mode (mitm_listeners)
- 5.13 Dashboard branding - HTTP API
- Web dashboard
- Service management
- Logging and monitoring
- Command line (CLI)
- Troubleshooting
- FAQ
- Changelog
1. Product overview
OxidProxy is a high-performance SOCKS5(h) / HTTP CONNECT IPv6 proxy server written in Rust. It is meant to run on your own dedicated or virtual server and acts as a gateway: it accepts incoming connections over IPv4, authenticates them and routes traffic through the IPv6 subnets you configure.
Key features
| Feature | Description |
|---|---|
| SOCKS5 / SOCKS5h | Full protocol support, including remote DNS, which prevents DNS leaks |
| HTTP CONNECT | HTTP(S) proxy mode via the CONNECT method, for browsers, curl and system proxy settings |
| Hybrid Mode | Automatic protocol detection (SOCKS5 or HTTP) on a single port |
| UDP / QUIC | Full UDP proxying, including QUIC (HTTP/3) |
| Multiple subnets | Attach several IPv6 subnets and balance between them by weight |
| Lazy rotation | The IP changes only on re-authentication; active sessions are never dropped |
| Smart DNS Failover | Built-in resolver with balancing and automatic failover to backup servers |
| OS Fingerprinting (p0f) | TCP/IP fingerprint masking as Linux, Android, Windows, iOS or macOS |
| Traffic Shaping | Total bandwidth limiting with burst support |
| HTTP(S) API | Instant credential generation, statistics and management over a REST API |
| IP Whitelist | Manage allowed addresses and subnets from the dashboard; system entries are protected |
| Web dashboard | Built-in monitoring panel with traffic charts, connection analytics and selectable themes |
| Blacklist | Block domains and IP addresses through blacklist.txt |
| Service account | A restricted account intended for checkers and monitoring |
How it works
Your software OxidProxy Server IPv6 subnets Internet
(SOCKS5h/HTTP) → Auth • DNS • Shaping → Subnet A (w:100) → Target
Subnet B (w:50)
2. System requirements
| Item | Minimum | Recommended |
|---|---|---|
| OS | Debian 12 (Bookworm) or Debian 13 (Trixie) x64 | Debian 12 (Bookworm) or Debian 13 (Trixie) x64 |
| CPU | 1 core | 2 or more cores |
| RAM | 512 MB | 1 GB or more, depending on max_accounts |
| IPv6 | a /64 subnet | a /48 or /32 subnet |
| Privileges | root, for installation | The service itself runs as the oxidproxy user |
| Virtualization | A KVM, VMware, Xen or Hyper-V virtual machine, or a dedicated server | Containers such as OpenVZ or LXC are not supported: they cannot boot a kernel of their own |
| Boot loader | GRUB | The installer sets up the custom kernel through GRUB and stops without it |
| Disk | About 1 GB free, and 150 MB in /boot if it is a separate partition |
5 GB or more, to leave room for the journal and logs |
6.1.187-oxidproxy-p0f on Debian 12, 6.12.111-oxidproxy-p0f on Debian 13. The installer picks the right one. Without the custom kernel every other feature still works normally.3. Installation
3.1. Automatic installation (recommended)
Run as root. If the server has no curl (a minimal Debian 13 image does not), install it first:
apt-get update && apt-get install -y curlChecking the server before installing (optional). The check script looks at whether the server is suitable: the system, kernel, boot loader, IPv6 network, free ports, and whether your subnet reaches the server. It installs nothing and does not change any settings. At the end it prints a verdict — ready, ready with warnings, or not ready — and what to fix.
If you already have your IPv6 subnet, put it in place of the example; for several subnets, give one --subnet per subnet:
curl -fsSL -o v6check.sh https://oxidproxy.com/downloads/v6check.sh
bash v6check.sh --subnet 2a01:1234::/48If you do not have a subnet yet, run it without one: bash v6check.sh. The result will be incomplete: the main requirement — whether the subnet reaches the server — stays untested, and the verdict will be at best "READY, with warnings". Run the check again once you have the subnet.
Then start the installer:
curl -fsSL https://oxidproxy.com/downloads/install.sh | bash
The script will:
1. Check that it runs as root on Debian 12 or 13 for x86_64, and refresh the package lists
2. Download and install the custom Linux kernel with the p0f patch, built for your Debian release, after checking that Secure Boot is off. A kernel the server already has is not downloaded again
3. Configure GRUB to boot the new kernel by default, and warn if another kernel would boot instead
4. Install logrotate and nftables, then the OxidProxy package. The package creates the oxidproxy system user and the systemd service, enabled at boot but not started yet
5. Ask whether to install the login banner. On every SSH login as root it shows the service state, the kernel, the networks and the dashboard address; Debian's own login text is turned off, and the original is kept as /etc/motd.debian-backup
6. Offer to reboot the server when needed: after a kernel was installed, or if the server runs a different kernel
Without a terminal (from a script, or an agent over SSH) the script answers "no" to both questions. To answer in advance, set OXID_MOTD for the banner and OXID_REBOOT for the reboot to y or n:
curl -fsSL https://oxidproxy.com/downloads/install.sh | OXID_MOTD=y OXID_REBOOT=n bash3.2. Manual installation
- Download the .deb package from the downloads page
- Install the package together with its dependencies,
logrotateandnftables, which minimal images may lack:
apt-get update
apt-get install -y logrotate nftables
dpkg -i oxidproxy_*.deb
# If dependencies fail:
apt-get install -f -y
- Optionally, install the custom kernel for p0f. The commands below are for Debian 12; on Debian 13 use the automatic installer (section 3.1): it installs
6.12.111-oxidproxy-p0fand keeps it first in GRUB, ahead of Debian's own kernels.
dpkg -i linux-image-6.1.187-oxidproxy-p0f_6.1.187-oxidproxy-p0f-1_amd64.deb
update-grub
6.1.187-oxidproxy-p0f (Debian 12) is available on the downloads page. It is needed to build out-of-tree kernel modules.3.3. What the installer creates
| Path | Purpose |
|---|---|
/usr/bin/oxidproxy |
The executable |
/etc/oxidproxy/config.yaml |
Main configuration file |
/etc/oxidproxy/blacklist.txt |
List of blocked domains and addresses |
/etc/oxidproxy/whitelist.txt |
Whitelist of allowed addresses and subnets |
/etc/rc.local |
Boot script with a commented-out subnet route line for you to fill in (section 4, step 2); created if absent, an existing file gets the line added |
/etc/sysctl.d/90-oxidproxy.conf |
Kernel settings (sysctl) |
/etc/sysctl.d/89-oxidproxy-ra.conf |
Only on servers whose IPv6 default route comes from router advertisements: keeps that route working once forwarding is on |
/usr/lib/systemd/system/oxidproxy.service |
systemd unit file |
/etc/logrotate.d/oxidproxy |
Log rotation configuration |
/usr/lib/systemd/system/oxidproxy-logrotate.timer |
Rotates the log files in /var/log/oxidproxy every hour, together with oxidproxy-logrotate.service |
/var/log/oxidproxy/ |
Log directory |
/var/lib/oxidproxy/ |
Data directory: exported account files, the server CA (ca/) and the HTTPS API certificate (api-tls/), created by the service |
/usr/share/doc/oxidproxy/README.txt |
Short reference to the settings |
/boot/vmlinuz-*-oxidproxy-p0f |
The custom kernel, package linux-image-<version>-oxidproxy-p0f |
/etc/default/grub.d/99-oxidproxy-p0f.cfg |
Debian 13 only: keeps the newest custom kernel first in GRUB, ahead of Debian's own kernels |
/etc/profile.d/99-oxidproxy-status.sh/usr/local/sbin/oxidproxy-status/usr/local/sbin/dashboard-url |
The login banner and the dashboard-url command, which prints the full dashboard link; only if you agreed to the banner |
3.4. Upgrading and removing
Upgrading. Run the installer again, with the same command as in section 3.1. It replaces the package and downloads a kernel only if the release has one this server does not have yet. It asks about the login banner again; answering "no" leaves an installed banner in place. To upgrade by hand, install the new package over the old one:
dpkg --force-confold -i oxidproxy_*.deb
# If dependencies fail:
apt-get install -f -y- Your
config.yamlstays as it is. If the new version's template differs, dpkg puts it next to yours asconfig.yaml.dpkg-dist. To see the settings of the new version, write a fresh example withoxidproxy -g(section 10);oxidproxy -tshows warnings about your config. - Logins, passwords and the server CA stay the same.
- A running service is restarted once, at the end; a stopped one stays stopped. Autostart stays as you set it.
- A reboot is needed only when a new kernel was installed; the installer then says so and offers to reboot.
Removing.
apt remove oxidproxystops the service, takes it out of autostart and removes the program. The configuration, account files, the CA and the logs stay: installing the package again picks them up, with the same logins and passwords. If the service was enabled at boot before, it is enabled again; start it with systemctl start oxidproxy.
apt purge oxidproxyremoves the rest as well: /etc/oxidproxy (the config with the license key and tokens, the lists), /var/lib/oxidproxy (account files, the CA), /var/log/oxidproxy, the files /etc/sysctl.d/90-oxidproxy.conf and 89-oxidproxy-ra.conf, and the profile delays (latency_base_ms) on the network interfaces. Account files and logs that the config put elsewhere (accounts_file, log.dir) are not touched.
Not removed even by purge:
- the custom kernel: it stays installed and remains the default in GRUB (on Debian 13 together with
/etc/default/grub.d/99-oxidproxy-p0f.cfg); - the
oxidproxysystem user; - the subnet route line in
/etc/rc.local(block# --- oxidproxy-ipv6-setup ---): purge reminds you of it; comment it out if nothing else on the server uses that subnet; - the login banner, if you installed it:
/etc/profile.d/99-oxidproxy-status.sh,/usr/local/sbin/oxidproxy-statusand/usr/local/sbin/dashboard-url.
apt purge a new installation starts from scratch: the config is the template again, and all logins, passwords and the server CA are new. Clients need new account files, and devices that use MITM ports must trust the new CA.4. Initial setup
Right after installation the service is not running yet. It is enabled at boot and starts by itself after the reboot in step 4, so complete steps 1 to 3 first:
The server checks these values when it starts. If license_key,
master_token or public_ip still hold the values from the
template, the service says which one and does not start, so a half-configured
server never goes live.
Step 1. Edit the configuration
nano /etc/oxidproxy/config.yaml
These must be filled in:
- license_key — your license key, obtained via Telegram
- public_ip — the external IPv4 address of your server
- ipv6_subnets — your IPv6 subnets
- master_token — the secret API token; replace the default
Step 2. Set up IPv6 routing
Edit /etc/rc.local and uncomment the route line:
nano /etc/rc.local
Replace the placeholder with your real subnet:
ip -6 route add local 2a01:xxxx::/32 dev lo
Step 3. Validate the configuration
oxidproxy -t /etc/oxidproxy/config.yaml
It should print ✅ Syntax OK. followed by a summary of the main settings.
Step 4. Reboot the server
reboot
Required to activate the custom kernel and the routing rules.
Step 5. Check the service
After the reboot the service is already running. Check it:
systemctl status oxidproxy
The status should read active (running). If you did not reboot (for example, the custom kernel was already running), start it yourself: systemctl start oxidproxy.
5. Configuration (config.yaml)
Every setting lives in /etc/oxidproxy/config.yaml. Restart the service to apply changes.
oxidproxy -t before restarting.5.1. Licensing
license_key: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# license_servers: optional - see the note below
| Parameter | Description |
|---|---|
license_key |
Required. The license key you received on purchase |
license_servers |
Optional. The regular endpoints are built into the server, so there is nothing to set here. Fill it in only with an address given to you by support, for instance when the usual endpoints are unreachable from your network. Entries here are tried first. |
About the license server addresses. They no longer belong in the config: they are built into the server and spread across two independently registered domains, so that one becoming unreachable does not stop license checks.
The license_servers parameter is needed in one case only: support has given you
a dedicated address. Such addresses are tried before the built-in ones, so a working
endpoint takes effect immediately, without waiting through timeouts on unreachable ones.
license_servers:
- "address.given.by.support:8000"Order of attempts: addresses from the config first, if any, then the built-in ones. A server that answers successfully moves to the front of the list and is used for subsequent checks.
oxidproxy --ping address:port — useful when support has given you a dedicated address and you want to confirm it is reachable from your network. The built-in endpoints need no manual checking; the server walks them itself.5.2. Network settings
listen_ip: "0.0.0.0"
public_ip: "123.45.67.89"
# ext_iface: "eth0" # optional
| Parameter | Description | Default |
|---|---|---|
listen_ip |
Required. IPv4 address the proxy ports accept connections on; 0.0.0.0 means all addresses, and the template sets it. IPv6 is not accepted here, and the server does not start with it: clients connect over IPv4. The API always listens on all interfaces |
— |
public_ip |
Required. Public IPv4 address of the server, used by the API and in generated links | — |
ext_iface |
External interface used for QoS. Detected automatically when not set | auto |
5.3. OS fingerprinting
The listeners section defines several ports, each with its own TCP/IP fingerprint profile. Anti-fraud systems that inspect traffic with tools such as p0f will see the parameters characteristic of the chosen operating system.
Use ports 1024 and above. The packaged service runs as the oxidproxy user and cannot open a port below 1024: such a port stays closed, and oxidproxy -t warns about it. The same applies to mitm_listeners, api_port and api_port_https.
Every listener supports a mode field that selects the protocol:
| Mode | Description |
|---|---|
socks |
SOCKS5 / SOCKS5h (default) |
http |
HTTP CONNECT proxy, using the CONNECT method with Proxy-Authorization: Basic |
hybrid |
Auto-detect: SOCKS5 and HTTP on one port. The protocol is determined from the first byte |
listeners:
- port: 1080
os_preset: "linux"
# mode: "socks" # the default, may be omitted
- port: 1081
os_preset: "android"
mode: "hybrid" # accepts both SOCKS5 and HTTP CONNECT
- port: 1082
os_preset: "windows"
mode: "http"
- port: 1083
os_preset: "ios"
mode: "socks"
- port: 1084
os_preset: "macos"
mode: "http"
hybrid is the recommended mode. One port serves both protocols, so no extra ports need opening, and detection is instant, from the first byte.Available profiles:
| Profile | TCP MSS | Congestion Control | Description |
|---|---|---|---|
linux |
System default | cubic | A standard Linux server |
android |
Common for the platform | bbr | A mobile Android client |
windows |
Common for the platform | cubic | A Windows machine |
ios |
Common for the platform | cubic | iPhone / iPad |
macos |
Common for the platform | cubic | A macOS desktop |
linux profile emulates nothing and keeps the system value.Fine-tuning (for experienced users only):
Each profile sets these values by itself, and the configuration template does not list them under the ports. To change one, add the key under the port; without the key the profile's value applies.
| Key | What it sets | Values | Default |
|---|---|---|---|
tcp_congestion |
Congestion control algorithm | Any algorithm loaded in the kernel: cubic, bbr and so on. Case does not matter: BBR is read as bbr |
bbr for android, cubic for the rest |
tcp_window_size |
Socket buffer in bytes, a speed ceiling for one connection | A number. Values above 8388608 (8 MB) add nothing: the package sets the system limit for socket buffers to 8 MB | 1 MB android, 2 MB ios, 4 MB windows and macos; not set for linux |
latency_base_ms |
Extra delay of the access network, ms. Applies per profile, not per port: give every port of one os_preset the same value, counting a port without the key as 5. If the values differ, only one of them takes effect, and oxidproxy -t warns |
0 and up; 0 means none | 5 for android, ios, windows and macos; not applied to linux |
tos |
IPv6 traffic class | auto; a number from 0 to 255 pins one value on the whole port; a list such as [0, 32, 40] is your own mix |
auto: every outgoing address gets its own value, the way real devices differ by access network |
- port: 1081
os_preset: "android"
mode: "hybrid"
tos: [0, 32, 40] # your own mix instead of autoThe old template values tos: 10 (android) and tos: 8 (ios, macos) work as auto. Any other single number except 0 puts the same class on every connection of the port. Real devices do not behave like that, so oxidproxy -t warns about it. An empty list (tos: []) is an error. The two lowest bits of a value are not sent on TCP connections: the kernel controls them. UDP is marked only when a single number is set.
os_preset is a configuration error: that port is not opened, the other ports keep working, and oxidproxy -t reports it with exit code 1. Valid profiles: windows, macos, ios, android, linux. A port without os_preset gets linux.5.4. IPv6 subnets and rotation
ipv6_subnets:
- cidr: "2a01:xxxx::/32"
weight: 427
- cidr: "2a02:yyyy:yyyy:yyyy::/64"
weight: 16
rotation_interval: 10
| Parameter | Description |
|---|---|
cidr |
IPv6 subnet in CIDR notation. Any size is supported: /29, /32, /48, /64 and so on |
weight |
Subnet weight, required for every subnet. The higher the value, the more often the subnet is used for outgoing addresses. A subnet with weight 0 is not used; at least one subnet needs a weight above 0 |
rotation_interval |
Rotation interval in minutes. 0 disables rotation, and the IP is kept until restart |
How rotation works:
- The IP address is bound to a login and password pair (a sticky session)
- On every authentication the server checks whether rotation_interval has elapsed
- If it has, a new IPv6 is generated from the subnets, honoring their weights
- Active connections are never dropped — the IP changes only on a new connection
- A session idle for more than 5 minutes is closed; this is tunable via timeouts.idle
5.5. API and user management
api_port: 8080
api_port_https: 8443 # optional - HTTPS API
master_token: "my-secret-token"
max_accounts: 100000
accounts_file: "/var/lib/oxidproxy/accounts_[os]_[mode]_[port].txt"
account_seed: 8312921461266748753 # written by the installer
| Parameter | Description | Default |
|---|---|---|
api_port |
Required. HTTP API port, 1024 or above; the template sets 8080 | — |
api_port_https |
HTTPS API port, 1024 or above. Optional: without it there is no HTTPS API; the template sets 8443 | — |
master_token |
Required. Secret token used to authenticate API requests. At least 12 bytes (12 Latin letters or digits), and the template value is refused: otherwise the server does not start. Use Latin letters, digits and - . _ ~. Any other character has to be URL-encoded in a ?token= link you write by hand, and oxidproxy -t warns about it. The dashboard and the links in the log encode the token themselves |
— |
max_accounts |
Required. Maximum number of unique accounts generated, each with its own IPv6; the template sets 100000 | — |
accounts_file |
Path for exported accounts. Supports the [port], [os] and [mode] macros. When the configuration produces more than one file — several ports, a hybrid port or a MITM port — all three macros are required, otherwise the server does not start |
accounts.txt (a single file only). The path is relative, so the packaged service puts the file in /var/log/oxidproxy. Set an absolute path, as the template does |
account_seed |
Generation seed: logins and passwords derive from it and stay the same across restarts. Without the key, every start draws new logins and passwords, and the ones already handed out stop working. Changing it regenerates all accounts | set by the installer, unique per server |
account_seed as the installer set it: logins and passwords stay the same across restarts and updates, so external systems never need new credentials. When you copy the configuration to another server, give that server its own value.5.6. DNS resolver
Names are resolved by the server itself. One cache is shared by all
listeners, so a name looked up for one client is already resolved for the
next. Servers carrying a weight share the load between them;
those without one are used only when the weighted ones do not answer. The
resolver keeps track of how each server behaves and stops sending queries to
one that stopped answering, returning to it a little later. When a primary
server has not answered within 300 ms, a failover server (one without a
weight) is queried in parallel and the first answer wins, so a single
slow server does not hold the client up. Keep at least one failover server:
with weighted servers only there is nothing to fall back to.
timeout is what a single server is given to answer, not what
the client waits: with the parallel query above the client normally gets an
answer sooner. Its job is to decide how quickly a server that went quiet is
taken out of rotation, so it is kept short. Raise it to 2 if healthy servers
are being taken out. What each server is doing right now is visible in
/stats, section 6.2.
dns:
timeout: 1
cache_size: 8000
suppress_aaaa_not_found: true
servers:
# primary servers (balanced)
- address: "[2606:4700:4700::1111]:53"
weight: 53
- address: "1.1.1.1:53"
weight: 7
# failover servers
- address: "[2620:fe::fe]:53"
- address: "9.9.9.9:53"
| Parameter | Description | Default |
|---|---|---|
timeout |
Time a single server is given to answer, in seconds | 1 |
cache_size |
Number of cached DNS records | 1024 |
suppress_aaaa_not_found |
Suppress warnings about domains that have no AAAA record | false |
servers |
Required, at least one server. address is an IP address and a port only, IPv6 in square brackets: [2606:4700:4700::1111]:53; a host name is not accepted. A server with a weight above 0 is a primary; one without a weight, or with weight: 0, is a failover |
— |
How it works:
1. The query goes to one of the primary servers, chosen by weight — the higher the weight, the more often that server is used. There may be several primaries.
2. If a primary does not answer, whether by timeout or error, the query falls through to the failover pool. There may be several of those too; they are listed without a weight.
3. A domain that exists but has no AAAA record is not a connection error, so failover is not triggered
/etc/resolv.conf, or can be obtained from the provider's support. Provider resolvers typically answer in 1–2 ms, against 10–50 ms for Google or Cloudflare.5.7. Traffic shaping
shaping:
rate: 12500000 # 100 Mbit/s
burst: 25000000 # 200 Mbit/s burst
| Parameter | Description |
|---|---|
rate |
Maximum rate in bytes per second. 0 means unlimited |
burst |
Burst allowance in bytes, letting transfers briefly exceed rate. Recommended: rate × 2. 0 means one second of rate. A value below 65536 is raised to 65536 (or to rate, if rate is lower). Ignored when rate is 0 |
shaping section is optional; when it is present, both rate and burst are required.Cheat sheet:
| Speed | rate value |
|---|---|
| 1 Mbit/s | 125 000 |
| 10 Mbit/s | 1 250 000 |
| 100 Mbit/s | 12 500 000 |
| 1 Gbit/s | 125 000 000 |
5.8. Timeouts
timeouts:
shutdown: 10 # how long to wait for sessions to finish on shutdown, sec
idle: 300 # maximum session idle time, sec
| Parameter | Description | Default |
|---|---|---|
shutdown |
How long, in seconds, to wait for active connections to finish when the service stops or restarts. Keep it under 85: systemd kills the service if it is still running 90 seconds after the stop request | 15 |
idle |
Maximum idle time of a TCP or UDP session before it is closed, in seconds. At least 1: with 0 the server does not start | 300 (5 min) |
5.9. Service account
service_account:
enabled: true
login: "checker"
password: "strong_password"
allowed_domain: "ifconfig.me"
The service account is meant for monitoring and health checks. Its properties:
- Static login and password: neither generated nor rotated
- Can reach only one domain, set by allowed_domain
- Does not consume rotation limits
- Never changes its IP
The section is required with all four keys, even when you do not need the account: set enabled: false to turn it off. Without the section or one of its keys the server does not start.
allowed_domain: "ifconfig.me" and verify the outgoing IP with curl --proxy socks5h://checker:password@IP:PORT ifconfig.me. Use socks5h: the name has to reach the proxy, because the account only accepts allowed_domain by name.5.10. Blacklist
blocked_file: "/etc/oxidproxy/blacklist.txt"
The file lists blocked domains (subdomains included), IP addresses and IPv4 or IPv6 networks in CIDR notation, one per line. Comments start with #. The list applies in every mode, including the browser mode.
The blocked_file key is required. If the file itself is missing, the server starts without a blacklist and reports it in the log.
# blocking ad domains
doubleclick.net
ads.google.com
# blocking private IPv6 networks
fc00::/7
fe80::/10
google.com is blocked, then mail.google.com and ads.google.com are unreachable as well.5.11. Logging
By default the server logs to the systemd journal only; read it with journalctl -u oxidproxy. In the template the dir line is commented out:
log:
# dir: "/var/log/oxidproxy"
level: "info"| Level | Description |
|---|---|
error |
Critical errors only |
warn |
Errors and warnings |
info |
Main events: connections, DNS, rotation — recommended |
debug |
Verbose debugging: every session, every DNS query |
Uncomment dir to write files as well. Two files are produced:
- oxidproxy.log — the full log, at the verbosity set by level
- error.log — errors only, always
The package rotates files only in /var/log/oxidproxy: logrotate, installed together with the package, does it every hour and keeps the last three. Files in any other directory are not rotated and grow without limit; oxidproxy -t and the service at startup warn about it. Keep dir: "/var/log/oxidproxy", or set up logrotate for your directory yourself.
To check where the log goes: oxidproxy -t reports Log Dir: not set, logging to journal only or the directory in use.
5.12. MITM mode (mitm_listeners)
In MITM mode OxidProxy terminates the client TLS connection with its own certificate authority and rebuilds the request to the target with the network fingerprint of a real browser: JA4 in full, HTTP/2 and Akamai. The profile is chosen automatically from the client User-Agent, so one port serves every OS and browser — Chrome, Firefox and Safari on Windows, macOS, Linux, Android and iOS.
MITM listeners are declared in their own mitm_listeners section and run alongside the regular listeners:
mitm_listeners:
- port: 9443
mode: "hybrid" # socks (default) / http / hybrid
profiles:
- "windows-chrome"
- "windows-firefox"
- "macos-safari"
- "android-chrome"
- "ios-safari"
default: "windows-chrome" # used when no listed profile matches the client's OS
# strict: false # true = refuse instead of substituting anything
# allow_unknown_ua: false # true = also serve clients with no browser UAProfiles are named <os>-<browser>. The available ones are:
| OS | Profiles |
|---|---|
| Windows | windows-chrome, windows-firefox, windows-edge |
| macOS | macos-chrome, macos-firefox, macos-safari |
| Linux | linux-chrome, linux-firefox |
| Android | android-chrome, android-firefox |
| iOS | ios-safari |
Listener fields:
| Parameter | Description |
|---|---|
port |
Port of the MITM listener |
profiles |
List of allowed <os>-<browser> profiles. The right one is chosen from the client User-Agent |
default |
Fallback profile. A recognized browser that is not in profiles first gets another profile of the same OS from the list; only when the list has none for that OS does it get default. Must be one of profiles, otherwise the server does not start |
strict |
When true, refuse any browser that is not in profiles instead of substituting another profile or default. Defaults to false |
allow_unknown_ua |
Clients with no recognizable browser User-Agent — curl, scripts, your own software — are refused by default with a short page explaining why. true serves them with the default profile (not with strict: true). Defaults to false |
mode |
Listener protocol: socks (the default), http or hybrid, exactly as for regular listeners. When omitted, the port runs in socks mode. |
/ca endpoint.5.13. Dashboard branding
The web dashboard can carry your own mark instead of ours. This exists for resellers: a customer's panel has no business advertising the vendor behind it.
brand:
name: "Acme"
accent: "Proxy"
url: "https://acme.example"
| Parameter | Description |
|---|---|
name |
First half of the wordmark, shown in the main text color. Omitted, it is Oxid |
accent |
Second half, painted in the accent color of whichever theme is selected. Leave it empty (accent: "") for a single-color wordmark; omitted, it is Proxy |
url |
Where the wordmark links to. An empty value (url: "") drops the link entirely; omitted, it links to https://oxidproxy.com. Only http:// and https:// are accepted |
The setting reaches further than the header. It also renames the browser tab, the theme label in the selector, and the file name of the CA certificate the dashboard offers for download. The full name (name + accent) also goes into the certificates the server creates from then on: a new MITM CA reads AcmeProxy MITM Root CA with the example above, and the HTTPS API certificate carries the same name. Certificates that already exist keep their names: a renamed CA would be a new CA, and every client would have to install it again.
6. HTTP API
The API is reachable at http://YOUR_IP:PORT or https://YOUR_IP:PORT, where PORT comes from api_port and api_port_https in the configuration — 8080 and 8443 in the template. Without api_port_https there is no HTTPS API.
Requests are authenticated with the token parameter, whose value is master_token.
Every endpoint answers with a standard HTTP status: 200 when the
request succeeded, 401 when the token is missing or wrong,
400 when the request cannot be satisfied as written — for example a
mode the listener does not serve, or a whitelist change that is refused (section 6.6) —
and 500 when the server could not carry out a valid request, such as a
whitelist change that could not be saved. 403 means the
caller's address is not on the IP whitelist (section 7.4), which guards the API and the
dashboard too. /ca answers 404 when the server has neither a
MITM port nor the HTTPS API, so no CA was created (with api_port_https from the
template the CA exists); /list answers 404 as described in section 6.5,
whitelist changes can answer 409 (section 6.6), and /health
answers 503 while the license is not confirmed. The
explanation is in the body; the status is what a script should branch on.
6.1. New credentials — GET /allocate
Returns a connection string with a fresh login and password. The listener is selected by port — the same URL the dashboard produces (section 7.6):
curl "http://YOUR_IP:8080/allocate?token=YOUR_TOKEN&port=1080&mode=socks5"
Response:
socks5://user_AbCdEfGh:KlMnOpQrSt@YOUR_IP:1080
Query parameters:
| Parameter | Description | Default |
|---|---|---|
token |
Required. The secret master_token | — |
port |
Port of the listener the credentials are for, a MITM port included. Identifies the listener unambiguously | — |
mode |
Protocol: socks5 or http; socks is the same as socks5, https the same as http. Any other value gives socks5, with a warning in the log |
socks5 |
os |
OS profile: linux, android, windows, ios, macos. Selects the listener by profile; ignored when port is given |
linux |
The port identifies the listener unambiguously. os selects it by profile;
when several listeners share that profile, the first one in the configuration is
returned. A call with neither port nor os takes the first
linux listener, or the first listener in the configuration when there is no
linux one. A port or profile that is not in the configuration is answered with 400 and
a list of what is configured.
Examples:
# Credentials for the listener on port 1081
curl "http://YOUR_IP:8080/allocate?token=YOUR_TOKEN&port=1081&mode=socks5"
# response: socks5://user_xxx:pass_xxx@YOUR_IP:1081
# Same listener, HTTP proxy (listener in http or hybrid mode)
curl "http://YOUR_IP:8080/allocate?token=YOUR_TOKEN&port=1081&mode=http"
# response: http://user_xxx:pass_xxx@YOUR_IP:1081
# By OS profile
curl "http://YOUR_IP:8080/allocate?token=YOUR_TOKEN&os=android"
Error: no listener on port 1090. Configured ports: 1080, 1081, 1082Error: http mode is not enabled for 'linux' profile (port 1080). Listener mode: sockshybrid mode both mode=socks5 and mode=http work on the same port.6.2. Statistics — GET /stats
Returns full server statistics as JSON.
curl "http://YOUR_IP:8080/stats?token=YOUR_TOKEN"
Example response, abridged: the listeners and mitm_listeners fields are omitted, being an echo of the matching config sections, the DNS server list is cut to two and the hourly and daily arrays to one point.
{
"version": "2.6.2",
"uptime_seconds": 9980,
"active_connections": { "tcp": 56, "udp": 4 },
"mitm": { "active": 3, "total": 512 },
"traffic": {
"ingress_bytes": 264074969,
"egress_bytes": 35524983
},
"dns": {
"total_queries": 18905,
"cache_hits": 17106,
"cache_stale_served": 12,
"cache_misses": 1799,
"cache_entries": 1420,
"inflight_joined": 63,
"hedge_fired": 208,
"hedge_won_by_backup": 41,
"all_failed": 3,
"bad_names": 0,
"all_down_queries": 0,
"servers": [
{
"address": "[2606:4700:4700::1111]:53",
"weight": 58, "state": "healthy",
"queries": 1502, "failures": 7,
"timeouts": 6, "servfails": 1, "net_errors": 0,
"consecutive_failures": 0,
"last_failure_ago_s": 1840, "last_error": "Timeout"
},
{
"address": "9.9.9.9:53",
"weight": null, "state": "healthy",
"queries": 36, "failures": 0,
"timeouts": 0, "servfails": 0, "net_errors": 0,
"consecutive_failures": 0,
"last_failure_ago_s": null, "last_error": null
}
]
},
"events": {
"total_connections": 18963,
"connection_errors": 12,
"access_denied": 0,
"blocked_domains": 0,
"dns_no_ipv6": 4,
"firewall_blocks": 0
},
"license": {
"expires_at_unix": 1770484105,
"days_left": 29,
"seconds_left": 2530000,
"is_active": true,
"servers": { "node-1": "online", "node-2": "online", "node-3": "online" }
},
"telemetry": {
"cpu_cores": 2,
"total_mem_mb": 2048,
"latest_cpu_usage": 4.8,
"latest_mem_mb": 96,
"open_fds": 412,
"max_fds": 1000000,
"hourly": [ { "ts": 1770480000, "rx": 1843200, "tx": 245760 } ],
"daily": [ { "ts": 1770393660, "rx": 1650000, "tx": 231000 } ]
},
"kernel_p0f": true,
"whitelist_fallback": null,
"server_ip": "1.2.3.4",
"shaper": {
"enabled": false,
"rate_bytes": 0,
"burst_bytes": 0,
"up_throttled": 0, "down_throttled": 0,
"up_wait_ms": 0, "down_wait_ms": 0,
"up_oversize": 0, "down_oversize": 0
}
}
Key fields:
| Field | Description |
|---|---|
mitm |
Active and cumulative connections through MITM ports since startup |
dns |
Cache hit and miss counts, how often the parallel query to a backup was
used, and a line per server: its state, weight, how many queries it served
and how its failures break down into timeouts, SERVFAILs and network errors.
A server that stopped answering shows up here first. all_down_queries counts queries sent while every server was marked down. |
whitelist_fallback |
null while the IP whitelist from the file is in force; otherwise the reason the file could not be used |
events.dns_no_ipv6 / firewall_blocks |
Domains without an AAAA record, and connections refused by the IP whitelist — the same categories as the dashboard chart in section 7.3 |
license.seconds_left |
Seconds until the license expires, by the server's clock: negative once it has expired, 0 while the expiry date is not yet known, right after startup. days_left gives whole days and is never negative: it is 0 after expiry, while the date is not yet known and during the last day |
license.is_active |
Whether the license is currently confirmed. When false, the proxy ports stay closed until the next check |
license.servers |
State of each built-in license node |
kernel_p0f |
true when the server runs the OxidProxy kernel and OS fingerprint substitution works. false means traffic leaves with the host's own TCP signature — install the kernel and reboot |
telemetry |
Server CPU and RAM usage — the data behind the hardware panel in section 7.1 — open file descriptors against the limit, and the traffic history behind the charts in section 7.2: hourly and daily, one point per minute (60 and 1440 points) |
server_ip |
The public_ip value from the configuration |
shaper |
Current shaping limits and how often and for how long the limit held traffic back, see section 5.7 |
listeners / mitm_listeners |
The port settings from the matching config.yaml sections, as the server applied them — handy for scripts that need the list of active ports and profiles |
6.3. Health and version — GET /health, GET /version
Both answer without a token; the IP whitelist still applies. /health returns 200 while the license is confirmed and 503 when it is not, which suits an external monitor. /version returns the version as plain text.
curl "http://YOUR_IP:8080/health"
{"status":"ok","license_active":true,"uptime_seconds":9980,"version":"2.6.2"}
curl "http://YOUR_IP:8080/version"
2.6.2
6.4. Metrics — GET /metrics
Most counters from /stats in the Prometheus text format: one name value per line, lines starting with # are HELP and TYPE. Any Prometheus-compatible collector reads it. The token is required, except for requests from the server itself.
curl "http://YOUR_IP:8080/metrics?token=YOUR_TOKEN"
Names start with oxidproxy_: for example oxidproxy_active_tcp, oxidproxy_bytes_egress_total, oxidproxy_license_active, oxidproxy_kernel_p0f_active, oxidproxy_dns_queries_total. oxidproxy_api_unauthorized_total counts API requests refused for a wrong or missing token (401). Requests and connections refused by the IP whitelist are not exported; they are counted in events.firewall_blocks of /stats and in the Recent Blocks panel of the dashboard.
6.5. Account files — GET /list
Downloads the account file of a port for one protocol — the file the TXT buttons of the dashboard download (section 7.8). mode is socks5 or http. The answer is 404 for a port that is not in the configuration and until the file has been exported; without port, or with any other mode, it is 400.
curl -OJ "http://YOUR_IP:8080/list?token=YOUR_TOKEN&port=1081&mode=socks5"
6.6. IP whitelist — GET /whitelist
The list the dashboard panel manages (section 7.4), changed on the fly without a restart. IPv4 addresses and subnets only. GET returns the entries, totals and recent blocks as JSON; adding and removing answer OK: ….
curl "http://YOUR_IP:8080/whitelist?token=YOUR_TOKEN"
curl -X POST "http://YOUR_IP:8080/whitelist/add?token=YOUR_TOKEN&cidr=203.0.113.0/24"
curl -X DELETE "http://YOUR_IP:8080/whitelist/remove?token=YOUR_TOKEN&cidr=203.0.113.0/24"
A change the request itself rules out is refused with 400 and the reason in the body: no cidr given, the address is invalid or IPv6, the entry is already in the list, the entry to remove is not in it, it is a protected entry (lock icon in the dashboard), it is the last entry, removing it would block your own address as the server sees it, or the list already holds 1000 entries.
If the whitelist file was changed outside the service — by hand, by a script or by copying another file over it — after the service read it, adding and removing answer 409 and leave the file untouched. Run systemctl restart oxidproxy to load the file, then repeat the request. 500 means the change could not be saved: the file could not be written, or it could not be read when the service started (whitelist_fallback in section 6.2).
7. Web dashboard
The built-in dashboard is available at:
http://YOUR_IP:8080/dashboard?token=YOUR_TOKEN
7.1. Live monitoring
The dashboard lives at http://YOUR_IP:PORT/dashboard?token=YOUR_TOKEN, where PORT is the configured api_port. HTTPS is supported too, via api_port_https.
It refreshes every 10 seconds and shows:
- Hardware Health — CPU load, with the core count, RAM usage and Open Sockets: open file descriptors, mostly sockets, against the service's limit. That bar turns yellow above 70% and takes the theme's alert color above 90%; a busy proxy runs out of sockets long before it runs out of CPU
- Network Traffic — total traffic received and sent (Rx / Tx), total connections since startup, active TCP and UDP connections, MITM Sessions (active now and total since startup), Traffic Shaper — Disabled, or the rate in Mbit/s and the burst in MB (section 5.7) — and Shaper Throttle: how many times the limit held back traffic from the client (↑) and to the client (↓)
- Recent Blocks — section 7.5
- License & Status — the status (table below), Days Left, Expires (the expiry date) and the state of each license server: online, offline, or checking... right after startup. Days Left and Expires show — until the license server has answered; during the last day Days Left shows < 1 in yellow
- DNS Resolver — the cache hit rate (hover over it for the counts), a row per weighted server with its weight and state (healthy, probing — getting a trial query after failures — or down), one Backup Pool row with how many failover servers are healthy, Hedged Requests — how many times a failover server was queried in parallel and how many times it answered first — and Stale / Failed: answers served from expired cache entries, and queries no server answered (section 5.6)
- Version and uptime — in the header
| Status | Meaning |
|---|---|
| ✅ Active | The license is confirmed and the proxy ports are open. It stays Active during the last day too: the ports close only when the license server reports that the term is over |
| ❌ Expired | The license term is over; the proxy ports are closed |
| ❌ BLOCKED / ERROR | The proxy ports are closed for another reason: the license is not confirmed. The service log says why: journalctl -u oxidproxy |
| ⛔ Your IP is not whitelisted | The server now refuses this address with 403: it is not on the IP whitelist, for example after the list changed while the page was open. Hover over the status for the address as the server sees it — behind a router that rewrites addresses, the router's |
| 🔑 Token rejected | The token the page was opened with no longer matches master_token: it was changed and the service restarted. Open the dashboard with the new token |
| 💀 OFFLINE / DEAD | The dashboard got no data from the server: the service is stopped or the server cannot be reached |
The title and icon of the browser tab follow the status.
A red ! sign warns about one of two degraded modes; hover over it, or move to it with Tab, for the cause and the fix:
- next to AUTO-ROTATION API LINKS, in place of "(p0f Profiled)" — the server is not running on the OxidProxy kernel, so connections leave with the server's own TCP fingerprint instead of the profile's (kernel_p0f in section 6.2). The output of uname -r must contain oxidproxy-p0f; if that kernel is installed but not booted, the startup log says how to fix the boot entry
- next to the IP Whitelist panel title — whitelist.txt could not be read, so every address is allowed, on the proxy ports and in the dashboard alike, and whitelist editing is off. The sign gives the reason; the file must be readable by the oxidproxy user. Fix it, then run systemctl restart oxidproxy
7.2. Traffic charts
Two throughput charts (Rx/Tx in Mbit/s) with average labels:
- Hourly — the last 60 minutes, one point per minute
- Daily — the last 24 hours, one point per minute
7.3. Connection Analytics
The doughnut chart breaks all connections down by category:
| Category | Color | Description |
|---|---|---|
| Success | green | Successfully proxied connections |
| Auth Denied | yellow | Refused because authentication failed |
| Conn Errors | purple | Network errors while establishing the connection |
| Blocked (DPI) | gray | Blocked by the domain blacklist |
| No IPv6 | blue | Domains with no AAAA record, that is, no IPv6 |
| Firewall Blocks | red | Refused by the IP whitelist |
7.4. IP whitelist management
The IP Whitelist panel manages allowed addresses and subnets straight from the browser, without restarting the service:
- View — lists every entry, with a lock icon marking system entries that cannot be removed
- Add — the input accepts a single IPv4 address or an IPv4 subnet such as 192.168.1.0/24; IPv6 is not supported in the whitelist. Press + Add Entry or Enter
- Remove — the button next to each entry. Removing 0.0.0.0/0 asks for confirmation, to avoid locking yourself out. The last entry cannot be removed — its button is disabled, since an empty list would block everyone, the dashboard included — and neither can an entry without which your own address, as the server sees it, would be blocked: the dashboard shows the reason instead
- Import — the Import button loads a .txt file of addresses or CIDRs, one per line, with # comments supported
The list refreshes every 10 seconds, so changes made from another device or by another administrator appear without reloading the page. It is kept in the file set by whitelist_file, /etc/oxidproxy/whitelist.txt by default.
Changes made to the file itself — by hand, by a script or by copying another file over it — take effect after systemctl restart oxidproxy. Until then the dashboard does not save whitelist edits over them: it reports that the file was changed outside the dashboard and suggests restarting the service.
The list holds up to 1000 entries. While whitelist.txt could not be read, every address is allowed, a red ! appears next to the panel title (section 7.1) and editing is off: fix the file, then run systemctl restart oxidproxy.
0.0.0.0/0 entry allows connections from every address. Remove it and only the addresses explicitly listed will reach the proxy — make sure your own is among them first.7.5. Recent Blocks
The Recent Blocks panel shows the last blocked connections, up to ten, with the address and the time. The Total counter gives the number of blocks since the service last started.
7.6. AUTO-ROTATION API LINKS
At the top of the dashboard a card is generated for every listener port, labeled with its OS profile. The API SOCKS5 and API HTTP buttons are disabled when the port does not serve that protocol.
Clicking a button copies the credential-request URL (GET /allocate?port=...&mode=...) to the clipboard and shows a toast in the corner.
Below them, the TXT SOCKS5 and TXT HTTP buttons download the account file for that profile and mode directly. A button is disabled when the port does not serve that mode; if the file has not been exported yet, the dashboard says so.
Each MITM listener gets its own card, first in the row, with the same buttons plus Download CA certificate. The client installs that CA once, after which all HTTPS traffic goes out under the selected browser profile. Without mitm_listeners a dimmed MITM card reads not configured.
7.7. Themes
The dashboard offers eight themes, switchable without reloading. The choice is remembered in the browser:
| Theme | Description |
|---|---|
| OxidProxy Dark | The brand dark theme, used by default |
| Light Classic | Classic light |
| Dracula | Dark, purple and pink |
| Deep Ocean | Deep navy |
| Teal Lagoon | Dark teal |
| Gruvbox Retro | Warm retro palette |
| Baroque Bling 💰 | Baroque, with gold accents |
| Hacker | Monochrome green on black |
7.8. Downloading account files
The TXT buttons on each port's card download its account files. With the accounts_file path from the template they are named like this:
| File | Contents |
|---|---|
accounts_linux_socks5_1080.txt |
SOCKS5 accounts for the Linux profile, port 1080 |
accounts_android_socks5_1081.txt |
SOCKS5 accounts for the Android profile |
accounts_android_http_1081.txt |
HTTP accounts for the Android profile in hybrid mode |
accounts_windows_http_1082.txt |
HTTP accounts for the Windows profile |
Each file holds lines of the form protocol://login:password@IP:PORT, ready to import anywhere.
listeners and accounts_file settings. Hybrid listeners produce two files, SOCKS5 and HTTP.8. Service management
OxidProxy installs as a systemd service and runs as the oxidproxy user.
# start
systemctl start oxidproxy
# stop, gracefully - waits for active sessions to finish
systemctl stop oxidproxy
# restart
systemctl restart oxidproxy
# status
systemctl status oxidproxy
# enable at boot
systemctl enable oxidproxy
# follow the logs
journalctl -u oxidproxy -f
systemctl start oxidproxy: systemd switches to the oxidproxy user declared in the unit file.9. Logging and monitoring
Log files
By default the server logs to the systemd journal only: journalctl -u oxidproxy. Files are written when log.dir is set (section 5.11):
| File | Description |
|---|---|
/var/log/oxidproxy/oxidproxy.log |
Main log, at the verbosity set in the config |
/var/log/oxidproxy/error.log |
Errors only |
Log rotation
File logs in /var/log/oxidproxy are rotated every hour by logrotate, installed together with the package; the package's own oxidproxy-logrotate.timer starts it. The last three generations are kept, older ones are compressed with gzip. Files in any other directory set by log.dir are not rotated, and oxidproxy -t warns about it.
System telemetry
Every 10 seconds a [SYS_STATS] line is written to the log:
[SYS_STATS] System Mem: 524/2048 MB | App Mem: 85 MB | CPU: 12.3% | Conns: 42 / 4096
10. Command line (CLI)
oxidproxy [OPTIONS]
Options:
-c, --config <PATH> Path to the configuration file (default: /etc/oxidproxy/config.yaml)
-t, --test [<PATH>] Validate the configuration syntax
-g, --generate Write a sample config.yaml.example
-b, --blacklist Write a sample blacklist.txt.example
-H, --hwid Show the server HWID
-p, --ping <HOST:PORT> Check that a license server is reachable
-V, --version Show the version
-h, --help Show this help
oxidproxy -t without a path checks the installed configuration, prints the same warnings the server writes to the log at startup, and exits with code 1 on any configuration error.
Without -t, -g, -b, -H, -p, -V or -h, oxidproxy starts the proxy itself, with the config given by -c or the default one. On an installed server that is the service's job (section 8): systemd runs oxidproxy -c /etc/oxidproxy/config.yaml as the oxidproxy user, with the network permissions the proxy needs. Do not start the proxy from the shell. The oxidproxy commands below (-t, -g, -p, -H) do not start it and are safe to run as root.
Examples:
# start the proxy - through systemd, not by hand
systemctl start oxidproxy
# validate the config before deploying
oxidproxy -t /etc/oxidproxy/config.yaml
# write a fresh sample config, config.yaml.example, to the current directory
oxidproxy -g
# check that a license server is reachable
oxidproxy --ping address.given.by.support:8000
# show the HWID
oxidproxy -H
11. Troubleshooting
The service will not start
-
Validate the configuration:
oxidproxy -t /etc/oxidproxy/config.yaml -
Check the logs:
journalctl -u oxidproxy --no-pager -n 50 -
Check the license server:
oxidproxy --ping <HOST:PORT>
Use an address given to you by support. The regular endpoints are built into the server and need no manual checking.
No connection through the proxy
- Make sure the client speaks a supported protocol: SOCKS5, SOCKS5h or HTTP CONNECT. SOCKS5h is recommended for maximum anonymity, as DNS is then resolved on the server.
- Check IPv6 routing — inspect
/etc/rc.localand the output of:
ip -6 route show - Confirm the port is reachable from outside:
ss -tlnp | grep oxidproxy - Check the firewall (iptables or nftables)
Some sites do not open
OxidProxy works over IPv6 only. A target site without an AAAA record simply cannot be reached.
To check for an AAAA record:
dig AAAA example.com
The IP does not change
OxidProxy uses sticky sessions: the IP is bound to a login and password pair. To change it:
- Request new credentials via GET /allocate
- Or wait for rotation_interval to elapse and reconnect
The wrong kernel booted
At startup the journal says Native kernel mimicking DISABLED, and uname -r does not end in -oxidproxy-p0f: the server booted another kernel, and OS fingerprinting does not work.
- See which kernel is running and which are installed:
uname -r
ls /boot/vmlinuz-* - Check that
/etc/default/grubhasGRUB_DEFAULT=0. On Debian 12 that is enough: our kernel is numbered above Debian's own and comes first, unless a newer kernel, for example from backports, is installed next to it. - On Debian 13, Debian's own kernels follow the upstream numbering and can sort above ours after an update, so the installer keeps ours first with the file
/etc/default/grub.d/99-oxidproxy-p0f.cfg. If the file is missing, run the installer again (section 3.1): it writes the file back. - Apply the change and reboot:
update-grub
reboot
12. FAQ
Q: What is SOCKS5h, and how does it differ from SOCKS5?
A: With SOCKS5h, DNS queries are resolved on the proxy server. Plain SOCKS5 resolves them on the client, which can leak DNS traffic and expose the real address. Always prefer SOCKS5h.
Q: Do logins and passwords change after a restart or an update?
A: No. They derive from account_seed, which the installer sets once, unique for every server. Changing account_seed regenerates all accounts; when you copy the configuration to another server, give that server its own value.
Q: Can I use several IPv6 subnets?
A: Yes. Add them to ipv6_subnets with different weights, and traffic is distributed in proportion.
Q: How do I find my server HWID?
A: Run oxidproxy -H. The HWID binds the license to the machine.
Q: How much does OxidProxy consume?
A: With 100,000 accounts and around 50 active connections, roughly 80–120 MB of RAM and about 5% of one CPU core. Being written in Rust gives it high throughput and predictable memory use.
Q: Is the API secure?
A: With api_port_https enabled the API is served over HTTPS. The certificate is signed by the server's own CA, the one served at /ca; until that CA is trusted, curl needs -k (or --cacert with the file from /ca). Access is protected by master_token; of the API endpoints, without it only /health, /version and /ca (the CA certificate, which holds no secret) answer, and /stats and /metrics only to the server itself. With the IP whitelist on, the API and the dashboard answer only to listed addresses.
Q: What is HTTP CONNECT mode?
A: HTTP CONNECT is the standard way to proxy HTTPS. The client sends CONNECT host:443 HTTP/1.1 with a Proxy-Authorization: Basic header, and an encrypted tunnel is established. Every browser supports it, as do curl, system proxy settings and most software.
Example with curl:
curl -x http://user_xxx:pass_xxx@YOUR_IP:1081 https://ifconfig.me
Q: How does hybrid differ from separate socks and http ports?
A: In hybrid mode one port accepts both. The protocol is detected from the first byte of the connection: 0x05 means SOCKS5, the first letter of an HTTP method (CONNECT, GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS) means HTTP, and anything else is dropped. It saves opening extra ports, and every feature — p0f, shaping, DNS, limits — behaves identically for both.
Q: What does the [mode] macro do in accounts_file?
A: On export, [mode] is replaced by the protocol, either socks5 or http. Hybrid listeners produce two files: one with socks5:// links and one with http://. For example accounts_android_socks5_1081.txt and accounts_android_http_1081.txt.
Q: The browser reports a certificate error on a MITM port.
A: A MITM port terminates TLS with its own certificate authority, so the client device must add the server CA to its trust store once; otherwise the browser treats the connection as insecure. The certificate is available from the /ca endpoint, or via the download button on the MITM card in the dashboard, described in section 7.6.
Q: What happens if the client User-Agent matches none of the mitm_listeners profiles?
A: It depends on the client. A recognized browser that is not in profiles gets another profile of the same OS from the list, or the default profile when the list has none for that OS; with strict: true it is refused instead. A client with no browser User-Agent at all — curl, a script — is refused with a short page explaining why; allow_unknown_ua: true serves it with the default profile.
13. Changelog
v2.6.2 — October 2026
- More efficient bandwidth limiting — when clients try to transfer more than the limit (
shaping), the server uses many times less CPU on it, and the load no longer grows with the number of such connections.
v2.6.1 — October 2026
- Reliable startup after a reboot — the service waits until the server's network is up, and the license activates on the first try.
- Whitelist via the API — a change refused because of the request itself (an invalid or duplicate address, no such entry, a protected or the last entry, a removal that would lock out your own address, the limit of 1000 entries) is answered with code 400 instead of 500; the response text is unchanged.
- DNS resolver — the default wait for a server's answer is 1 second (
dns.timeout), as in the configuration template. oxidproxy -twarns if log files are written anywhere other than/var/log/oxidproxy: the package does not rotate them in another directory.- Documentation — the user guide, the package README and the AI agent instruction are clarified and expanded: upgrading and removal, server requirements, required configuration keys, API response codes, the dashboard description.
- Configuration validator and builder on the site check addresses, ports and empty values the same way the server does, and warn about the same things
oxidproxy -twarns about.
v2.6.0 — October 2026
- OS profiles — network parameters adjusted.
- Simpler configuration template — ports list only the port, the OS profile and the mode; the other keys work as before, and their list and defaults are in the user guide and in the package README.txt.
- Debian 13 (Trixie) support — on par with Debian 12, same package; the installer picks the matching kernel (6.12.111 on Debian 13, 6.1.187 on Debian 12) and checks that Secure Boot is off, as the kernel requires.
- Predictable removal and upgrades —
apt removestops the service and keeps your data;apt purgeremoves everything, including the configuration and the profile delays; an upgrade no longer enables autostart if you disabled it. - Browser mode closer to real browsers — improved accuracy for current versions of Chrome, Edge and Firefox.
- Browser mode more compatible and stable — better compatibility with clients that check certificates strictly, such as Python 3.13+, and with HTTPS sites addressed by IPv6; downloads show their size, long uploads are supported, and memory use stays lower over long runs.
- Certificates — the HTTPS API certificate is created at first start and kept across restarts in
/var/lib/oxidproxy/api-tls/; a new server CA carries the name frombrand, and an existing CA is not changed. - Blacklist — understands internationalized domain names and entries such as
*.example.comand.example.com(a domain together with its subdomains);oxidproxy -tshows what was loaded. oxidproxy -twarns in advance about settings that will not work and suggests a fix — ports below 1024, a missing IPv6 default route, log files owned by another user; an IPv6 address inlisten_ipis rejected both by the check and at startup.- Clearer configuration errors — if
config.yamlcontains an error,oxidproxy -tshows the line number and, for an invalid value, the key name as well. - Dashboard — editing the whitelist keeps its comments and won't let you lock yourself out; clearer access-denied messages and license status; a red sign with the cause and the fix when the server is not running on the OxidProxy kernel or the whitelist could not be read.
- Whitelist — if
whitelist.txtwas changed by hand or by a script while the service is running, the dashboard won't overwrite those changes and suggests restarting the service. - HTTP mode — refined request handling, better compatibility with requests to IPv6 addresses.
- DNS resolver — more robust server health checks; large answers are retrieved in full over TCP; a site with several IPv6 addresses stays reachable even if one of them does not respond.
- UDP mode — better compatibility when the proxy server has several IPv4 addresses; lower resource use over long runs; Connection Analytics counts UDP by session.
- Reliable installation and startup — better compatibility with servers that get IPv6 via SLAAC; the service starts reliably after a reboot on servers with netplan, NetworkManager or a slow DHCP; if a proxy port did not open at startup, the service keeps retrying until it opens.
- Logs and API — file logs (
log.dir) are rotated hourly; fewer repeated lines, and every rejection is visible in/metrics(new counteroxidproxy_api_unauthorized_total).
v2.5.1 — September 2026
- OS fingerprints closer to real devices — connection parameters vary from address to address, like real devices on different networks; the macOS profile is improved. Full accuracy requires kernel 6.1.187, which the installer sets up for you.
- Traffic class chosen automatically —
tos: auto; a number or a list still sets your own value. - New configuration template — each port shows its profile's defaults: uncomment a line to change a value.
- Strict profile check — a port with an unknown
os_presetis not opened, the other ports keep working, andoxidproxy -treports the error. - Lower memory use — idle connections take about ten times less memory.
- Improved stability — errors while handling one connection do not affect the others.
- Blacklist — supports IPv4 and IPv6 subnets; matching rules refined.
- Browser mode — checks refined; idle connections are closed on timeout.
- DNS resolver — a failing domain does not affect other queries, and the resolver keeps trying when all servers are down; new counters in
/statsand/metrics. - Whitelist — processing rules refined; its state is shown in
/stats. - Logs — new installations log to systemd-journald; file logging is enabled with
log.dir, with rotation by logrotate. oxidproxy -tshows the same warnings as the server at startup.- TLS libraries updated.
When updating:
- Reboot the server: kernel 6.1.187 takes effect after a reboot.
- Check
os_preseton every port: a port with a misspelled profile name will not open after the update. Verify withoxidproxy -t. - Your configuration and logs are not changed by the update.
v2.4.1 — September 2026
- Config check without a path —
oxidproxy -tchecks the installed configuration at/etc/oxidproxy/config.yaml; no path required.
v2.4.0 — September 2026
- Network profiles improved — connection characteristics are closer to real networks and profile behavior is more natural.
v2.3.7 — September 2026
- Listener selected by port in
/allocate— theportparameter names the listener exactly, which matters when several listeners share one profile and differ in their settings. Theosparameter keeps working. - Parameter checking in
/allocate— a port or profile that is not in the configuration is answered with 400 and a list of what is configured.
v2.3.6 — September 2026
- New DNS resolver — a shared cache across all listeners, per-server availability tracking and a parallel query to a backup. Resolution is faster and steadier, and the range of supported names is wider.
- Detailed DNS statistics in
/stats— each server reports its state, weight and the kind of failures it returns, which makes diagnosis straightforward. - UDP mode optimized — session reliability and network compatibility improved.
- SOCKS5: precise reply codes — the client is told the actual reason a connection did not happen.
- HTTP mode optimized — better resilience under load and wider client compatibility.
- iOS profiles brought in line with real devices.
- Browser mode improved — higher accuracy and better site compatibility; large uploads use less memory.
- Configuration is checked at startup — the service points at unfilled mandatory fields at once and will not start while
license_key,master_tokenorpublic_ipstill hold template values. - Standard API status codes —
/allocate,/statsand/dashboardreturn 401 for a wrong token and 400 for an invalid request. - Dashboard: dark theme readability improved.
v2.2.0 — September 2026
- Lower latency on the profiles — mobile profiles (
android,ios) add 30 ms, desktop ones (windows,macos) 10 ms, andlinuxadds nothing. The previous values were set too high and cost tens of milliseconds on every request. A value set explicitly inconfig.yamltakes precedence: the defaults apply only where the key is absent. jitter_msis no longer applied — the key is still accepted and configurations carrying it keep working, but the service notes it in the log at startup.- Throughput ceiling removed — it showed up under heavy load.
v2.1.2 — August 2026
- The MITM port refuses non-browser clients — curl, scripts and anything else that sends no browser
User-Agentget a short page explaining why, instead of going out under a profile they do not match. Addallow_unknown_ua: trueto the listener to restore the previous behavior. Browsers are unaffected. - Login banner over SSH — optional, offered during installation. It shows whether the service is running, which kernel is booted, which subnets are configured and where the dashboard lives, and warns when
master_tokenis still the template value. The token itself is deliberately left out; thedashboard-urlcommand prints the full link on request. - Browser version matches the handshake — for traffic with no browser
User-Agent, the server now claims the version whose TLS handshake the emulation actually reproduces.
v2.1.0 — August 2026
- Updated custom kernel —
6.1.177-oxidproxy-p0freplaces 6.1.170, with the matchinglinux-headerspackage published beside it on the downloads page. - License endpoints are built in —
license_serversis no longer a required key. The field is now Additional license servers and is normally left out of the configuration entirely; fill it in only when support gives you an address. - Journal-only logging — leave
log.dirempty and the server writes to the systemd journal instead of files, which suits hosts where logs are collected centrally. - Dashboard branding — the
brandsection replaces the wordmark above the dashboard and the address it links to: the dashboard can be handed to your own clients under your own name. - More varied TCP fingerprint — the MSS is no longer a single fixed value shared by every connection. It is drawn from a realistic distribution and stays stable for a given outgoing address, so a profile no longer stands out by repeating the same value everywhere.
- Readable log-directory errors — an unwritable directory is reported at startup instead of crashing the service.
- Honest p0f status — when the kernel cannot supply the fingerprint, the server says so plainly rather than falling back quietly.
v2.0.0 — June 2026
- MITM mode — a new mode of operation: the server terminates TLS with its own CA and rebuilds traffic to the target with the network fingerprint of a real browser (Chrome, Firefox, Safari) across every layer — JA4 in full, HTTP/2 and Akamai. The profile is chosen automatically from the client
User-Agent, so one port serves every OS. - The
mitm_listenerssection — dedicated MITM ports alongside the regularlisteners, with a list of allowed<os>-<browser>profiles and a default one. - MITM card in the dashboard — the connection link and a button to download the CA certificate for the client trust store.
v1.8.1 — May 2026
- RFC-conformant QUIC — a substantial rework of the UDP/QUIC implementation: deviations from the specification removed, client compatibility improved, packet loss under high load reduced.
- New custom kernel 6.1.170-oxidproxy-p0f — greatly improved hardware compatibility. It now boots on any hypervisor and hardware configuration as reliably as the stock Debian 12 kernel; the previous build failed to boot on some setups.
- Account file downloads from the dashboard — the AUTO-ROTATION API LINKS block gained TXT SOCKS5 and TXT HTTP buttons that download the account lists directly, grouped by OS profile and mode.
v1.7.1 — April 2026
- Dashboard: IP whitelist — full whitelist management from the browser: add, remove, import from a file. Changes take effect immediately, without restarting the service.
- Dashboard auto-refresh — IP whitelist and Recent Blocks now update every 10 seconds along with the rest of the statistics.
- Connection analytics — a new doughnut chart breaking connections down by category: successful, blocked, errored, IPv4-only domains.
- Recent Blocks — a panel listing the latest blocked connections with timestamps and a running total.
- Themes — five dashboard themes added, with the choice remembered in the browser.
- HTTP CONNECT support — a full HTTP proxy mode with
Proxy-Authorization: Basicand correct handling of every header variant. - Improved robustness — a busy dashboard API port no longer causes a silent crash; the error is now reported explicitly in the log.
- Dashboard layout fix — widget column edges no longer misalign at different screen resolutions.
v1.7.0 — March 2026
- Hybrid mode — automatic protocol detection (SOCKS5 or HTTP CONNECT) on a single port, from the first byte of the connection.
- HTTP proxy — the
httpmode added to thelistenerssection. - The
[mode]macro inaccounts_file— hybrid listeners export two account files, one with SOCKS5 links and one with HTTP.
v1.6.0 — January 2026
- HTTPS API — an optional
api_port_httpsport. - Smart DNS failover — a backup pool of resolvers with automatic switching when a primary is unreachable.
- Service account — a static account restricted to a single domain, for checkers and monitoring.