Skip to content
joshrmcdanielPublic
forked from Barre/privaxy

About

Privaxy is the next generation tracker and advertisement blocker. It blocks ads and trackers by MITMing HTTP(s) traffic.

Resources

Stars

32 stars

Watchers

3 watching

Forks

 
 

Repository files navigation

Privaxy

Next generation tracker and advertisement blocker

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.

Table of Contents

About

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.

Compared to DNS-based blockers (Pi-hole, AdGuard Home)

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

Features

  • 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 js syntax.
  • Support for uBlock origin's redirect syntax.
  • 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, or redirect queries 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.

Installation

You can either utilize the docker image, binary, or the deb avaiable in releases.

Debian/Ubuntu

Download and install the .deb from the release

RHEL/Fedora/Rocky

Download and install the .rpm from the release

MIPS

Download and install the deb/rpm/binary with mips in the name

Docker

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>
  • dev is mapped to the develop branch
  • latest is mapped to the main branch
  • <version> maps to official releases
  • <sha> maps to a specific commit

From source

# 1. Frontend 
cd web_frontend
npm i
trunk build --release

# 2. Backend
cd ..
cargo build --release

The 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

Docker Compose

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:

  • dev is mapped to the develop branch
  • latest is mapped to the main branch
  • <version> maps to official releases
  • <sha> maps to a specific commit

Setup

1. First-run web UI

Open http://<host>:8200 in a browser. On first launch, the web UI walks you through:

  1. 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-Key header. The key is shown in Settings → Account.
  2. 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.

2. Install the root CA on your client devices

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.crt and run sudo 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.

3. Point clients at the proxy

Two options:

  • Manual: configure your browser/OS to use HTTP proxy <host>:8100 for 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.

4. Cert-pinned hosts (exclusions)

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.

5. Filter only selected hosts (inclusions)

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.

6. HTTP CONNECT and proxy chaining

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.

Example: tun2proxy

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 virtual

Replace 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_hash value from the config file and restart. The web UI will force the setup flow again.

7. Persistent statistics and privacy

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.

Screenshots

dashboard requests filters filterlists general addfilter

Acknowledgements

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.

About

Privaxy is the next generation tracker and advertisement blocker. It blocks ads and trackers by MITMing HTTP(s) traffic.

Resources

Stars

32 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages