A Linux control panel for the zapret / zapret2 DPI-bypass engines —
systemd service, nftables rules, encrypted DNS, gateway mode and a GTK4 interface
that never runs as root.
English · Türkçe
Unwall wraps zapret and
zapret2 — the DPI (Deep Packet Inspection)
bypass engines by @bol-van — into something you can
actually run on a desktop. On Linux the engine is native: instead of a
WinDivert-style driver, the kernel's netfilter/NFQUEUE subsystem does the
interception and nfqws does the work. Unwall adds carrier presets, hostlist
management, encrypted DNS and diagnostics on top.
Carrier presets currently ship for Turkey (the project grew out of
zapret-win-turkey); adding your
own country is a one-line change in
lib/strategies.conf — see
CONTRIBUTING.md.
- Features
- Installation
- Usage
- Encrypted DNS
- Sharing with other devices
- Differences from the Windows version
- Project layout
- Troubleshooting
- Contributing
- License
- Credits
| Two engines | the classic nfqws (zapret) and the new LUA-based nfqws2 (zapret2) |
| Ready-made strategies | carrier presets (currently TR · Türk Telekom, Superonline, Kablonet, Vodafone, Turkcell/Telekom mobile) plus carrier-independent generic profiles — usable without running blockcheck |
| Blockcheck | searches for a strategy that works on your ISP and writes the result straight into the configuration |
| Hostlist / excludelist | only blocked domains go through the engine, the rest of your traffic is untouched; com.tr and gov.tr are excluded by default |
| systemd service | starts at boot and keeps running with the GUI closed |
| Gateway mode | routes devices on your LAN (console, smart TV) through this machine — the replacement for go-pcap2socks + Npcap on Windows |
| Encrypted DNS | one switch sets up DoH (dnscrypt-proxy, port 443) or DoT (systemd-resolved, port 853) — replaces the YogaDNS recommendation from the Windows version, fully reversible |
| Diagnostics | DNS interference check, conflicting-tool and queue detection |
| English and Turkish | picks your locale automatically, switchable from the menu (UW_LANG=en/tr overrides it) |
| Privilege separation | the GUI runs as your normal user; privileged work goes through a single helper script via polkit |
./install.shThe script elevates itself once (through sudo, or pkexec when there is no
sudo), so you are asked for your password a single time. This one command:
- Detects your package manager (
pacman/apt/dnf/zypper) and installs every required and optional package (includingdnscrypt-proxy) - Installs the files, the systemd unit, the polkit policy, the icon and the application menu entry
- Builds the
nfqwsandnfqws2engines from source
The installer starts no service and changes no system setting. DNS, autostart, strategy selection and gateway mode are all done from the GUI.
Options: --yes (no questions), --no-deps, --no-build, PREFIX=/usr.
After installation, Unwall appears in your application menu (Network
category); launch it from there or with the unwall command.
| Distro | Command |
|---|---|
| Arch / CachyOS | cd packaging && makepkg -si |
| Debian / Ubuntu | ./packaging/build-deb.sh && sudo apt install ./packaging/unwall_*.deb |
| Fedora / openSUSE | ./packaging/build-rpm.sh && sudo dnf install ./packaging/RPMS/*.rpm |
| Flatpak (GUI only) | see flatpak/README.md |
| AppImage (GUI only) | ./packaging/build-appimage.sh |
The .deb and .rpm build scripts need dpkg-dev and rpm-build (or
rpmbuild) respectively; they produce a real package from this repository,
they do not download anything prebuilt. Both packages behave exactly like
install.sh: no service is started and no system setting is touched until
you do it from the GUI or with unwallctl.
The Flatpak and the AppImage are a separate case: they can only contain the
GTK4 interface, not the nftables/systemd/NFQUEUE parts, so either still needs
one of the packages above (or install.sh) installed on the host first.
- The Flatpak is sandboxed and reaches the host's
unwallctl/pkexecthroughflatpak-spawn --host; seeflatpak/README.mdfor why and how. - The AppImage is not sandboxed — it runs with your normal user
permissions like any other binary, so it calls
unwallctl/pkexecdirectly, no bridging needed. It needsappimagetool(APPIMAGETOOL=/path/to/it ./packaging/build-appimage.shif it is not on yourPATH); the resultingUnwall-<version>-x86_64.AppImageis a single portable file that still expects the native backend on the machine it runs on, same as the Flatpak.
Upgrading / uninstalling
Upgrading from zapret-turkey (the previous name of this project): just run
./install.sh. It disables the old service, moves /etc/zapret-turkey and
/opt/zapret-turkey to the new paths (so the engines are not rebuilt), keeps
your encrypted DNS setup, and removes the old binaries, unit, polkit policy and
menu entry.
To remove everything: ./uninstall.sh (it also asks for the password only
once). It stops and disables the service, drops the nftables rules, reverts
the encrypted DNS configuration (restoring any dnscrypt-proxy.toml it
replaced), deletes the program files, the settings and lists, the compiled
engines and source tree, and the logs — then verifies that nothing is left
behind. Use --yes to skip the confirmation, --keep-config to preserve
/etc/unwall, and --purge-deps to remove the dnscrypt-proxy package as
well. Other dependencies (nftables, gtk4, luajit …) are left alone because
other software may need them.
Installing dependencies manually
sudo pacman -S --needed nftables python-gobject libadwaita gtk4 polkit bind gcc make pkgconf git curl luajit libnetfilter_queue libnfnetlink libmnl zlib dnscrypt-proxysudo apt install nftables python3-gi gir1.2-adw-1 gir1.2-gtk-4.0 policykit-1 dnsutils build-essential pkg-config git curl libluajit-5.1-dev libnetfilter-queue-dev libnfnetlink-dev libmnl-dev zlib1g-dev dnscrypt-proxysudo dnf install nftables python3-gobject libadwaita gtk4 polkit bind-utils gcc make pkgconf git curl luajit-devel libnetfilter_queue-devel libnfnetlink-devel libmnl-devel zlib-devel dnscrypt-proxybuild clones bol-van/zapret and bol-van/zapret2 into /opt/unwall/src
and compiles the nfqws / nfqws2 binaries. Run sudo unwallctl build
later to update the engines.
Pick an engine, a strategy and a hostlist mode in the GUI, then press
START. Your choices are not applied until you press that button; until
then the status line shows not applied → ... and the button reads APPLY
SETTINGS. The "start at boot" switch enables the systemd unit, which keeps
running with the GUI closed.
The GUI runs as your normal user; only privileged actions (starting the engine, the service, DNS, firewall rules) ask for a password through polkit.
From the terminal:
sudo unwallctl start STRATEGY=superonline ENGINE=zapret2| Command | What it does |
|---|---|
unwallctl status |
current state (key=value) |
unwallctl strategies [engine] |
list ready-made strategies |
unwallctl config get|set |
read / write settings |
sudo unwallctl start|stop|restart |
run / stop the engine |
sudo unwallctl enable|disable |
start at boot |
sudo unwallctl blockcheck [engine] |
ISP analysis |
unwallctl dnscheck [domain] |
DNS interference check |
sudo unwallctl dns enable|disable |
turn encrypted DNS (DoT/DoH) on / off |
unwallctl dns status|test |
encrypted DNS state / test |
unwallctl doctor |
environment and conflict diagnostics |
sudo unwallctl disable-conflicts |
shut down conflicting DPI tools |
unwallctl print-cmd, print-nft |
show the generated command and rules |
unwallctl update-check |
check GitHub for a newer release (key=value) |
sudo unwallctl self-update [version] |
download and install a release (defaults to latest) |
Configuration: /etc/unwall/unwall.conf
Lists: /etc/unwall/{hostlist,excludelist,autohostlist}.txt
Logs: journalctl -u unwall -f and /var/log/unwall/
The GUI checks GitHub for a newer release once a day in the background (no network access is required otherwise) and shows a dismissible banner with a link to the release when one is found. Force a check from the menu (Check for updates) or from the terminal:
unwallctl update-checkClicking Update now on the banner (or running sudo unwallctl self-update
yourself) downloads that release's source tarball and runs its install.sh --no-deps --no-build: the CLI, GUI, systemd unit, polkit policy, icon and
menu entry are refreshed; packages and the compiled engines are left alone,
and nothing is started or stopped in the process — same guarantees as running
install.sh by hand. This only applies if you installed with install.sh in
the first place; if you used a distro package (.deb/.rpm/AUR), update
through your package manager instead, since self-update would just get
overwritten by the next apt/dnf/pacman upgrade anyway.
If your ISP tampers with DNS, zapret alone is not enough. The Encrypted
DNS switch in the GUI, or the unwallctl dns command, sets this up for
you — no manual file editing.
| Method | Transport | Notes |
|---|---|---|
DoH — dnscrypt-proxy |
443/tcp | Indistinguishable from ordinary HTTPS, hard to block. Requires the dnscrypt-proxy package. |
DoT — systemd-resolved |
853/tcp | No extra package needed, but 853 is a separate port that some ISPs close. |
sudo unwallctl dns enable cloudflare autoauto picks DoH when dnscrypt-proxy is installed and falls back to DoT.
Providers: cloudflare, google or quad9.
unwallctl dns test
sudo unwallctl dns disableWhat happens under the hood
- DoT: a drop-in at
/etc/systemd/resolved.conf.d/90-unwall.confwithDNSOverTLS=yesand the provider's servers.Domains=~.makes these win over the ISP servers handed out by DHCP; links with their own search domains (VPN, Tailscale) are unaffected. - DoH:
dnscrypt-proxyruns as a DoH client on127.0.0.1:5300andsystemd-resolveduses it as its upstream. An existingdnscrypt-proxy.tomlis backed up as.unwall.bakbefore being replaced;dns disablerestores it.
dns disable reverts both changes — the uninstall script calls it too.
Turn on the Gateway mode switch. This machine becomes a NAT router for
the local network (ip_forward + nft masquerade) and the forwarded
traffic goes through zapret as well. The Npcap + go-pcap2socks layer used
on Windows is not needed; routing is done by the kernel.
DNS is handled the same way go-pcap2socks handled it on Windows, just with
a kernel rule instead of a bundled proxy: an nftables rule transparently
redirects every DNS query (TCP and UDP, port 53) coming from the LAN to this
machine's own resolver — the same one it uses itself, including encrypted
DNS if you've turned that on. Whatever DNS server the device is configured
with is ignored; if your ISP blocks port 53 outbound, that's exactly why
this exists — the device's packets never actually leave your network toward
that address, they get rewritten to this machine before they do.
In the manual network settings of the device (PlayStation, Xbox, Switch, TV):
| Field | Value |
|---|---|
| IP address | a free address on your network, e.g. 192.168.1.50 |
| Subnet mask | same as your network, usually 255.255.255.0 |
| Gateway | this computer's LAN IP (shown in the "LAN address" row of the GUI) |
| DNS | any valid-looking value, e.g. 1.1.1.1 — most devices refuse an empty field, but the actual value is overridden by the redirect above |
Note
If a firewall such as firewalld or ufw drops packets in the forward
chain by default (ufw's DEFAULT_FORWARD_POLICY is DROP out of the box
on many distros), gateway mode devices get "connected, no internet".
Since v1.3.14, unwallctl detects this and adds a targeted ufw route allow rule (or firewall-cmd --add-forward on firewalld) automatically
whenever gateway mode is applied — no manual firewall changes needed.
unwallctl doctor / the GUI's Diagnostics show what was detected under
"gateway forwarding".
| Windows | Linux equivalent |
|---|---|
winws.exe / winws2.exe |
nfqws / nfqws2 |
| WinDivert driver | netfilter NFQUEUE (nfnetlink_queue) |
--wf-tcp / --wf-udp / --wf-l3 |
nftables rules (queue num ... bypass) |
sc create ZapretService |
unwall.service (systemd) |
UAC / #RequireAdmin |
polkit + pkexec (only the helper script is elevated) |
Npcap + go-pcap2socks |
ip_forward + nft masquerade |
| YogaDNS (installed by hand) | built-in DoH/DoT via dns enable (dnscrypt-proxy / systemd-resolved) |
nslookup, ipconfig /flushdns |
dig, resolvectl flush-caches |
| GoodbyeDPI conflict check | nfqws/tpws/byedpi/TUN and queue conflicts (doctor) |
config.ini |
/etc/unwall/unwall.conf |
| AutoIt GUI | GTK4 + libadwaita (Python) |
The strategy parameters themselves (--dpi-desync=…, --lua-desync=…,
--hostlist…) are identical on both platforms; only the layer that steers
traffic into the engine differs.
bin/unwallctl all privileged work (CLI + polkit target)
bin/unwall GUI launcher
gui/unwall_gui.py GTK4 / libadwaita interface (host-aware: works from a Flatpak too)
lib/strategies.conf ready-made strategy profiles
etc/unwall.conf default configuration
systemd/unwall.service systemd unit
polkit/…policy privilege escalation policy
packaging/PKGBUILD Arch package
packaging/debian/ .deb control files (see build-deb.sh)
packaging/unwall.spec Fedora/openSUSE .rpm spec (see build-rpm.sh)
flatpak/ Flatpak manifest for the GUI (see flatpak/README.md)
packaging/appimage/ AppRun for the AppImage (see build-appimage.sh)
docs/screenshots/ interface screenshots
unwallctl doctorRun the GUI from a terminal and everything is logged to the console; for more detail:
UW_DEBUG=1 unwallThe application is single-instance: if a window opened from the menu is already running, launching it from a terminal only raises that window and prints no logs. To get a separate instance while debugging:
UW_NO_UNIQUE=1 UW_DEBUG=1 unwallCommon issues
- The engine will not start:
journalctl -u unwall -n 50 - My strategy selection reverts: the selection is only pending until you
press APPLY SETTINGS / START; the status line shows it as
not applied → .... - Nothing changed: check that the rules are loaded with
sudo nft list table ip unwall; if the hostlist mode ismanual, make sure the domain is in the list. - QUIC/HTTP3 sites broke: set
PORTS_UDP=(empty) in the configuration. Automatic (zapret learns)hostlist mode isn't adding new domains: it only adds a domain after it sees a recognizable "blocked connection" pattern (since v1.3.8: 1 failed attempt; the engine's own default is 3 within 60 seconds, which we lower with--hostlist-auto-fail-threshold=1so one bad load is enough — a single attempt still needs 3 TCP retransmits by default, so a random network blip won't count) for a domain that isn't already desynced. If your strategy already gets through cleanly, that pattern never happens and the domain is correctly never added. Before v1.3.6, genuinely blocked domains could also fail to be learned because only outgoing traffic was queued to the engine — zapret2's own auto-hostlist detector needs to see the reply direction too (an injected RST or an HTTP redirect to a block page), which v1.3.6's nftables rules now queue as well. If you're still on an older version, upgrade first. Watchsudo tail -f /var/log/unwall/hostlist-auto.logwhile browsing to a blocked site to see what the engine is actually observing — a completely empty log after several failed loads means the reply direction still isn't reaching the engine.- Another DPI tool is running:
byedpi,tpws, the upstreamzapret.serviceor a VPN that creates a TUN device will fight over the queue. - Upstream zapret is already installed (
/opt/zapret,zapret.service): do not run both at once —sudo systemctl disable --now zapret. This project defaults to queue number210to reduce collisions (upstream uses200). You can also skip thebuildstep and use the existing/opt/zapretand/opt/zapret2binaries; they are looked up automatically when no locally built engine is found.
Still stuck? Open an issue using the bug report template — filling in its environment table (version, install method, distro, engine, strategy, hostlist mode) up front is the single biggest time-saver.
Bug reports, strategy presets for other countries and pull requests are all welcome — see CONTRIBUTING.md for how the project is laid out, what CI checks on every push, and what to verify locally before opening a PR.
Unwall is licensed under the GNU General Public License v3.0 or later. You are free to run, study, share and modify it; if you distribute a modified version, it must stay under the same license and come with its source.
- @WinTone01 — created Unwall: the Linux port
itself (
unwallctl, the systemd/nftables/polkit integration, the GTK4 interface, encrypted DNS, gateway mode, the update checker) and maintains the project. - @alimali54 for zapret-win-turkey, the Windows version this project is based on
- @bol-van for the zapret and zapret2 engines
- @cagritaskn, developer of splitwire-turkey, for the automatic blockcheck logic and the strategy presets
- @DaniilSokolyuk, developer of go-pcap2socks, for the LAN sharing idea used in the Windows version

