A fork of privaxy
This reverts it back to v0.3.1, adding more features, dependency updates, an improved UI, and server-friendly configuration options.
Privaxy is a MITM HTTP(s) proxy that sits in between HTTP(s) talking applications, such as a web browser and HTTP servers, such as those serving websites.
This app sees all your plaintext, run it on hardware you trust. DO NOT FACE THIS PUBLICLY
By establishing a two-way tunnel between both ends, Privaxy is able to block network requests based on URL patterns and to inject scripts as well as styles into HTML documents.
Operating at a lower level, Privaxy is both more efficient as well as more streamlined than browser add-on-based blockers. A single instance of Privaxy on a small virtual machine, server or even, on the same computer as the traffic is originating from, can filter thousands of requests per second while requiring a very small amount of memory.
Privaxy is not limited by the browser’s APIs and can operate with any HTTP traffic, not only the traffic flowing from web browsers.
DNS sinkholes can only answer "is this whole domain allowed or not?". Because Privaxy works at the HTTP layer instead of the DNS layer, it can do things a DNS blocker fundamentally cannot:
- Block by full URL, not just domain — individual paths and query strings can be blocked, so first-party and same-domain ads/trackers (served off a domain you otherwise need) are reachable targets.
- Cosmetic filtering — hide page elements and inject uBlock Origin-style scriptlets, instead of leaving broken gaps where a blocked domain used to be.
- Intercept DNS-over-HTTPS (DoH) — DoH is the mechanism that routinely bypasses DNS-level blockers; Privaxy sees it as HTTPS and can block or redirect it.
The trade-off is that Privaxy is a MITM proxy: clients must trust its root CA and route traffic through it, and it sees plaintext. It complements a DNS blocker more than it strictly replaces one.
Upon initial setup, a lot of your websites/apps will break due to cert pinning. This is a one time occurrence, add websites/endpoints to exlcusions as broken websites are encountered
- Suppport for Adblock Plus filters, such as easylist.
- Web graphical user interface with a statistics display as well as a live request explorer.
- Opt-in persistent statistics and request history, with privacy levels, retention limits, and domain exclusions.
- Support for uBlock origin's
jssyntax. - Support for uBlock origin's
redirectsyntax. - Support for uBlock origin's scriptlets.
- Browser and HTTP client agnostic.
- Support for custom filters.
- Support for excluding hosts from the MITM pipeline.
- DNS-over-HTTPS (DoH) interception —
block(default) clients' DoH so they fall back to the system resolver, orredirectqueries to a resolver you configure. Closes the DoH bypass that defeats DNS-level blockers. - Support for protocol upgrades, such as with websockets.
- Automatic filter lists updates.
- Very low resource usage.
- Around 50MB of memory with approximately 320 000 filters enabled.
- Able to filter thousands of requests per second on a small machine.
- PAC generation for easy client setup
- filterlists.com integration
- Ability to add custom filters
Privaxy uses the combined uBlock Origin CDN lists for Ads, Badware risks, Privacy, Quick fixes, and Unbreak. Existing source-list subscriptions migrate automatically, preserving their enabled state. An already configured CDN subscription takes precedence over its old source-list duplicate. Separate mobile and resource-abuse subscriptions are retained for existing choices, although their rules are also included in the combined Ads and Privacy lists.
Filter-list !#if, !#else, and !#endif directives use a portable uBO syntax
profile (ext_ublock enabled, browser-specific flags and unsupported capabilities
disabled). This selects scriptlet fallbacks for !cap_html_filtering; proxy HTML
injection does not implement uBO's HTML-filtering or $replace syntax. Unknown
conditions retain both branches, matching uBO, and are reported in the logs.
Active !#include directives load relative sub-lists from the same directory or
its descendants, following uBO's subscription format. Includes are expanded
before caching; inactive branches are not downloaded. Failed downloads,
malformed conditions, cycles, and includes outside the permitted source leave
the previous complete cache untouched. Downloads are limited to 64 files,
16 include levels, and 64 MiB per subscription. Custom filters have no source URL
and cannot use includes.
Bundled scriptlets come from uBlock Origin 1.76.2. Scriptlets marked
requiresTrust are available to the official uBO filter repositories and manually
entered Custom filters, but not arbitrary remote subscriptions. Unsupported
filter rules are counted by rejection reason alongside the number loaded. These
warnings do not mean the whole list failed to load. Filter syntax and browser
integration still differ from the uBO extension, so list compatibility does not
guarantee identical results on every video site.
You can either utilize the docker image, binary, or the deb avaiable in releases.
Download and install the .deb from the release
Download and install the .rpm from the release
Download and install the deb/rpm/binary with mips in the name
docker run -d --name privaxy --restart unless-stopped \
--log-driver json-file --log-opt max-size=10m --log-opt max-file=3 \
-p 8100:8100 -p 8200:8200 \
-v /path/to/conf:/conf \
ghcr.io/joshrmcdaniel/privaxy:<tag>devis mapped to the develop branchlatestis mapped to the main branch<version>maps to official releases<sha>maps to a specific commit
# 1. Frontend
cd web_frontend
npm i
trunk build --release
# 2. Backend
cd ..
cargo build --releaseThe frontend must be built before the backend — the server embeds web_frontend/dist/ via include_dir! and won't compile without it.
Build requirements:
- Rust 1.87+
- Node.js
- Trunk
services:
privaxy:
image: ghcr.io/joshrmcdaniel/privaxy
ports:
- "8100:8100"
- "8200:8200"
volumes:
- path/to/conf:/conf
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"Tags:
devis mapped to the develop branchlatestis mapped to the main branch<version>maps to official releases<sha>maps to a specific commit
Open http://<host>:8200 in a browser. On first launch, the web UI walks
you through:
- Creating a username and password for the web UI. The
same account is used for every subsequent login. Programmatic clients
can also authenticate via the
X-Api-Keyheader. The key is shown in Settings → Account. - Selecting which filter lists to enable (you can also browse filterlists.com from Settings → Filters).
On first run, privaxy auto-generates a root CA + private key and writes
them to its config directory. If you'd rather use your own CA, replace the
values under [ca] in the config file (or upload via Settings → General)
and restart.
Privaxy is a MITM proxy: clients must trust its root CA, otherwise every HTTPS site will show a certificate error. Download the CA from Settings → General, then install it as a trusted root on each device:
- Linux (Debian/Ubuntu, system-wide): copy the PEM to
/usr/local/share/ca-certificates/privaxy.crtand runsudo update-ca-certificates. Firefox uses its own store — import via Preferences → Privacy & Security → View Certificates → Authorities. - macOS: open the file in Keychain Access → System keychain → mark Always Trust under the certificate's Trust section.
- Windows: double-click the
.crt→ Install Certificate → Local Machine → Place all certificates in the following store → Trusted Root Certification Authorities. - iOS: transfer the file to the device → Settings → General → VPN & Device Management → install the profile → Settings → General → About → Certificate Trust Settings → enable full trust for the Privaxy CA.
- Android: Settings → Security → Encryption & credentials → Install a certificate → CA certificate. Note: most apps on modern Android ignore user-installed CAs unless they opt in via network security config, so privaxy is most useful for browser traffic on mobile.
Two options:
- Manual: configure your browser/OS to use HTTP proxy
<host>:8100for both HTTP and HTTPS. - PAC (recommended): point the client at
http://<host>:8200/proxy.pac. PAC is served unauthenticated so any client on the network can fetch it. Configure direct-bypass rules (internal CIDRs, FQDNs) under Settings → PAC.
Some sites use certificate pinning or strict TLS and will break if their traffic is intercepted. Privaxy handles this two ways:
- An always-on safety net for Apple's published service hosts
(
apple.com,icloud.com, etc., per HT210060). This is hardcoded and not user-editable. - A list of commonly cert-pinned hosts that is pre-populated into your editable exclusions on first config creation. Settings → Inclusions & exclusions shows the list; the Reset to defaults button re-applies the default list. Source
Excluded hosts are still CONNECT-tunneled through the proxy, they're just not decrypted. Filter rules do not apply to their traffic.
If you find a site that breaks under MITM, add its hostname (wildcards
like *.example.com are supported) to Settings → Inclusions & exclusions and click
Save. If it's something common, please open an issue so it can be added
to the recommended list.
Under Settings → Inclusions & exclusions, enable Only filter included
hosts, enter one hostname or wildcard pattern per line, and save. Use both
example.com and *.example.com to cover a domain and its subdomains. Matching
is case-insensitive and uses the destination hostname without its port.
In this mode, only included hosts are filtered. Everything else passes through without ad blocking, HTML/userscript injection, or HTTPS decryption. Explicit exclusions and the built-in Apple exclusions always take precedence. An empty inclusion list filters nothing. Turning the mode off retains the list and restores filtering of all non-excluded hosts.
The generated PAC file uses the same inclusion list and returns DIRECT for
unlisted or excluded hosts; existing PAC bypass rules still take precedence.
Clients configured with a manual proxy continue forwarding through Privaxy,
which tunnels unselected HTTPS hosts and forwards unselected HTTP unchanged.
Clients may need to refresh their PAC file and reopen connections after changes;
an established HTTPS tunnel keeps its original interception decision.
Inclusions select destination hosts, not entire browsing sessions. Third-party ad and resource hosts used by an included site must be included separately if you want their requests filtered too.
The equivalent top-level configuration fields are include_only = true and
inclusions = ["example.com", "*.example.com"]. Existing configurations default
to include_only = false. File edits take effect on SIGHUP or restart.
Privaxy accepts both plain HTTP and HTTPS inside CONNECT tunnels. A compatible HTTP proxy or adapter can use Privaxy as its upstream proxy:
Client → HTTP proxy or adapter → Privaxy → website
Point the preceding proxy at Privaxy's proxy port (default 8100). Privaxy
automatically recognizes HTTP and TLS, including on nonstandard destination
ports, and applies the same filtering, HTML injection, inclusions, and
exclusions used by ordinary proxy clients. No additional Privaxy setting is
needed. This feature supports chains that feed into Privaxy; configuring an
upstream proxy for Privaxy's own outbound traffic is outside its scope.
For an IP CONNECT (including resolved destinations from sing-box/TUN clients), Privaxy reads TLS ClientHello SNI before choosing an interception certificate or applying hostname inclusions and exclusions. A DNS SNI hostname supplies the certificate's DNS SAN, certificate cache identity, HTTP filtering identity, and upstream TLS SNI/verification name. The upstream connection still goes to the original CONNECT IP and port. No sing-box DNS or LAN routing changes are needed. Named CONNECT requests retain their existing behavior.
Without usable DNS SNI, Privaxy retains the CONNECT identity: an intercepted literal IP receives an IP SAN, which cannot authenticate a hidden DNS hostname. Encrypted ClientHello (ECH) does not reveal its inner hostname to Privaxy. Malformed, truncated, oversized (over 64 KiB), or stalled ClientHellos fail closed; protocol detection, ClientHello reading, and the intercepted TLS handshake each have a 10-second timeout. Fragmented ClientHellos and buffered bytes are preserved, including when SNI selects a raw tunnel.
Clients must still trust the existing Privaxy CA; certificate-pinned services need exclusions. Explicit CONNECT-host/IP exclusions bypass protocol probing. For other IP CONNECTs, hostname rules (including include-only mode) are decided after reading SNI. Exclusions take precedence over inclusions.
CONNECT to port 853 is an opaque TCP tunnel for DNS-over-TLS, including
9.9.9.9:853 with SNI dns.quad9.net. TLS stays end-to-end and decrypted DNS
never enters the HTTP parser. Privaxy's DoH policy applies only to HTTP traffic;
it does not filter DoT. Non-HTTP TLS on other ports requires an exclusion.
Filtering covers TCP HTTP/HTTPS. Handle UDP/QUIC and non-HTTP protocols in your routing policy; HTTP/3 traffic allowed directly will bypass filtering. Privaxy's client statistics identify the connecting proxy's address, so devices sharing that address appear together.
Local TLS regression tests can be run with
cargo test -p privaxy --lib proxy::mitm::connect_tests. They create temporary
trusted test CAs in memory and use loopback fixtures, with no production config
or external services. The DoT exchange uses an unprivileged ephemeral port;
port 853's automatic tunnel selection is tested separately.
tun2proxy is one adapter that can send traffic into Privaxy using CONNECT. Configure it to use Privaxy's proxy port and virtual DNS, which preserves destination hostnames in CONNECT. For an already configured TUN interface and Privaxy running on the same machine:
tun2proxy-bin --tun tun0 --proxy http://127.0.0.1:8100 --dns virtualReplace the interface and proxy address for your deployment. You are responsible for creating the TUN interface, selecting client devices, routing their traffic and DNS queries through tun2proxy, and keeping the proxy's outbound connections out of that route to avoid loops. Follow tun2proxy's setup documentation for those steps.
Recovering access: if you lose the web-UI password, delete the
password_hashvalue from the config file and restart. The web UI will force the setup flow again.
Enable collection under Settings → Statistics to show saved totals,
hourly/daily trends, top domains/clients, and a paginated request log directly
on the Dashboard. With persistence off or Anonymous mode selected, the
Dashboard keeps its original layout with the current process's live counters.
Old /history bookmarks redirect to the Dashboard.
Persistence is off by default; enabling it starts collecting new activity,
without importing the live Requests feed.
History records timestamps, destination hostnames, endpoint paths, client IP
addresses, and whether a request was blocked or successfully forwarded, according
to the selected privacy level. The Endpoint column shows only the path, such as
/204, beside the separate domain. The database saves 204, without duplicating
the domain or the first /; that separator is restored for display. Query
strings, URL credentials, headers, cookies, and request/response bodies are never
stored. Long paths are shortened visually; hover over one to see its full value.
Older records without an endpoint show "Not recorded". Domains, endpoints, and
clients each have a shared lookup table: repeated values are saved once and
request rows reference their IDs. Unused values are removed when their last
request expires. Existing history migrates automatically to storage version 3
and is compacted, converting older hostname-prefixed endpoints to paths too.
Earlier builds that only support versions 1 or 2 cannot read the upgraded
history database.
Raw CONNECT tunnels and requests that fail before being forwarded are not
counted. Clients behind another proxy share that proxy's client address.
The privacy levels follow Pi-hole's approach:
| Level | Saved identifiers |
|---|---|
| Show domains and clients | Hostname, endpoint path, and client IP |
| Hide domains | Client IP only |
| Hide domains and clients (default) | Neither; request times and outcomes remain |
| Anonymous | No new history; session counters only |
Hiding domains also hides endpoints. Hidden identifiers are removed before queueing or storage, without retaining a reversible mapping or a hash. While collection is enabled, raising privacy also redacts identifiers in existing history, including when entering anonymous mode. Lowering privacy cannot restore them. These controls apply to statistics; the separate live Requests feed and diagnostic logs can still contain full URLs.
Exclude domains from statistics and request logs skips matching new traffic
in session counters, modified-response counts, saved history, and the live
Requests feed, even with persistence disabled. Filtering and interception still
apply. Use one case-insensitive hostname or wildcard pattern per line:
example.com matches exactly; *.example.com matches its subdomains. Do not
include schemes, ports, or paths. Existing history and diagnostic logs are
unaffected.
Exclude clients from statistics and request logs applies the same exclusions to one exact IPv4 or IPv6 address per line. This works with persistence off too; it does not bypass ad blocking or delete previously saved requests.
Client names assigns friendly names to IP addresses. Names appear in both dashboard layouts and saved history wherever client addresses are visible; hover over a name to see its IP. Renaming updates existing history without changing counts or merging different clients. Names are saved in configuration, so clearing history keeps them. Privacy settings that hide clients also hide their names on the Dashboard.
Defaults are 7 days and 100,000 saved requests. The oldest records expire when either limit is reached; valid limits are 1–365 days and 100–1,000,000 requests. History totals and top lists describe retained requests, so they can decrease as older entries expire. Modified-response totals are separate hourly aggregates retained by age. Time windows include the current hour and preceding hours. Clear saved history removes the database and starts fresh if enabled. Turning collection off stops recording and closes the database; it retains the existing file until cleared or collection is re-enabled and retention runs.
The equivalent configuration section is:
[statistics]
enabled = false
privacy = "hide_domains_and_clients" # full, hide_domains, hide_domains_and_clients, anonymous
retention_days = 7
max_entries = 100000
excluded_domains = ["example.com", "*.example.com"]
excluded_clients = ["192.0.2.10", "2001:db8::10"]
[statistics.client_names]
"192.0.2.20" = "Living room TV"UI changes apply immediately; file edits also apply on SIGHUP. Data stays local
in statistics.redb under PRIVAXY_BASE_PATH (/etc/privaxy by default, /conf
in Docker). Keep /conf on a persistent Docker volume for history to survive
container replacement. No new ports or external database service are required.
On Unix, the database is restricted to its owner (0600). Storage is not
encrypted, and redaction/deletion is not secure erasure of filesystem snapshots,
backups, or recoverable disk pages.
redb transactions run on a dedicated worker, with a bounded queue and batches flushed every five seconds or 512 events. SIGTERM/SIGINT also flush accepted events; an abrupt kill can lose the pending batch. Expired rows are pruned every minute and free space is compacted hourly; the row limit bounds retained data, not an exact file size. Disk/write errors pause recording and appear in Statistics settings without stopping the proxy. Free space and save settings again to retry. Queue overflow drops statistics events and reports their count instead of delaying proxy requests.
The authenticated APIs are GET/PUT /api/settings/statistics and
GET/DELETE /api/history. History accepts hours, limit (1–200), before
(the previous response's next_before cursor), and blocked=true|false.
Pagination and the blocked filter affect request rows, not the window's totals.
Privaxy was originally created by Pierre Barre (Barre/privaxy). This fork stands on top of that work, full credit for the original design and implementation goes to him.
Thanks also to:
- uBlock Origin and Raymond Hill. Privaxy bundles uBlock Origin's scriptlets and web-accessible resources for filter compatibility.
- filterlists.com — for the filter-list directory that powers in-app filter discovery.






