install
Pick your OS × HTTP server to get the exact yum / apt / apk commands and web config snippet.
0 Choose a mode (speed-first or compatibility-first)
Clicking a card updates the dropdown below in sync.
nginx native module
- ✓ Once a client has passed the challenge, it runs at the same speed as bare nginx (the server just verifies a short-lived pass cookie)
- ✓ ~0.05 ms per request (cookie verification within the same process. No subrequest / no IPC)
- ✓ rate_limit / honeypot paths are handled entirely within nginx
- △ nginx only (not for Apache. For that, use forward-auth mode)
forward-auth mode
- ✓ Works with nginx / Apache
- ✓ Reuses the distro's stock HTTP server as-is (nginx stays untouched / Apache uses mod_lua)
- △ ~0.5–2 ms per request (via subrequest)
1 Choose your environment
Every option above is CI-verified. Derivative distros (openSUSE / Mint / Pop!_OS / etc.) and the full matrix live on the supported distro list.
2 Install the unmask core ?repo method: install unmask-release once first to set up the repo config + GPG public key → from then on you can install / upgrade the core with dnf install unmask / apt install unmask. Signature verification is handled automatically by the package manager.
Right after install: admin auto-starts via systemd (127.0.0.1:9477) and writes /etc/unmask/.setup-token. The setup wizard launches in section 4.
Upgrade: sudo dnf upgrade unmask && sudo systemctl restart unmask (on deb, sudo apt upgrade unmask).
sudo dnf install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm sudo dnf install -y unmask
3 Install unmask-plugin-nginx (native plugin) ?Prerequisite: nginx must be installed (auto-pulled in step 4 via unmask-web-nginx dependencies).
postinstall: places load_module into the distro's conventional dir (/usr/share/nginx/modules/ or /etc/nginx/modules-enabled/) automatically. 14 nginx versions × OpenSSL 1.0 / 1.1 / 3 ABIs are bundled (1.10–1.30 series / glibc) so the matching module is picked at install time. No manual editing.
After an nginx upgrade: ABI mismatch surfaces as nginx -t errors → sudo dnf upgrade unmask-plugin-nginx (or sudo apt install --only-upgrade unmask-plugin-nginx on deb) realigns versions.
sudo dnf install -y unmask-plugin-nginx4 Install unmask-web-nginx (web config snippet) ?postinstall places /etc/unmask/forward-auth/{server,protect}.inc + the upstream symlink, validates with nginx -t, and prints the setup token / URL. It does not touch the running nginx — the wizard URL becomes reachable after the restart you run below.
The JA4 maps include is fully automatic: postinstall places a symlink at /etc/nginx/conf.d/00-unmask.conf → /var/lib/unmask/nginx/http.inc. Since every distro's default include /etc/nginx/conf.d/*.conf; auto-loads it, you do not need to edit the http {} block.
sudo dnf install -y unmask-web-nginx
/unmask/admin/setup/.
- [Recommended] Add one line to an existing vhost — open the setup wizard on the domain you already use. Inside the target
server { }block, add the blue (highlighted) line:server { listen 443 ssl http2; server_name example.com; ssl_certificate ...; include /var/lib/unmask/nginx/server.inc; # ← unmask itself (challenge serving, BAN, admin UI) root /var/www/example; }→sudo nginx -t && sudo systemctl restart nginx(a restart — not reload — is what loads the freshly installed module) → openhttps://<your-domain>/unmask/admin/setup/. - [Direct port] — nothing to change if you can shell into the host itself:
http://127.0.0.1:9477/unmask/admin/setup/is reachable out of the box. Only when you want to open the wizard from a different host on the LAN (a dev PC etc.), changeserver.bindin/etc/unmask/config.ymlto0.0.0.0:9477→sudo systemctl restart unmask→ openhttp://<your-server>:9477/unmask/admin/setup/. Not recommended on public hosts (9477 in plaintext + setup token exposure risk). - [SSH tunnel] — a universal fallback that needs no firewall changes.
ssh -L 9477:127.0.0.1:9477 your-server -N &
→ openhttp://localhost:9477/unmask/admin/setup/.
Once inside the setup wizard, in order:
- Paste the setup token — shown on screen after package install. Or
sudo cat /etc/unmask/.setup-token. - Create the admin account for the admin UI — username + password. You'll log in to the admin UI with this account from now on.
- Configure the DB — the SQLite default is enough. Switch to MariaDB if needed.
/unmask/admin/settings/.
5 Fire the challenge in the server {} you want to protect (ON/OFF switch) ?These highlighted lines are unmask's ON/OFF switch. Add them and bot protection fires; remove them and blocking / challenges stop immediately, with no effect on user traffic. The access-log statistics feed keeps flowing either way, so a safe rollout is step-by-step: monitoring (stats only) → trial (fire on one location) → production (fire site-wide).
What you actually write is 2 lines: include /var/lib/unmask/nginx/server.inc; directly inside server { }, and include /var/lib/unmask/nginx/protect.inc; wherever you want protection. Everything else (rate-limit zones, challenge dispatch, JA4 maps) is rendered by the admin into /var/lib/unmask/nginx/ and auto-loaded via the conf.d symlink placed in section 4.
Rate / burst / target paths live on the Settings → rate-limit tab. To restrict which domain can reach the admin UI, use Settings → Network → admin_allowed_hosts.
In the target server {} block (the .conf of your existing nginx vhost), add the blue (highlighted) lines. Everything else is your existing config — a reverse proxy is shown, but a static site (root + try_files) adds the same lines.
No need to edit existing locations — just add this one block.
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate ...;
include /var/lib/unmask/nginx/server.inc; # ← unmask itself (challenge serving, BAN, admin UI)
include /var/lib/unmask/nginx/protect.inc; # ← outside any location (protects every location)
location / { # ← your existing location stays unedited
proxy_pass http://your-upstream;
}
}
Once you're done editing, apply the config. If you have not restarted nginx since installing the plugin, restart (a reload does not load a new module); afterwards plain reloads are enough:
sudo nginx -t && sudo systemctl restart nginx
2 Install the unmask core ?repo method: install unmask-release once first to set up the repo config + GPG public key → from then on you can install / upgrade the core with dnf install unmask / apt install unmask. Signature verification is handled automatically by the package manager.
sudo dnf install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm sudo dnf install -y unmask
3 Install unmask-web-nginx (web config snippet) ?Prerequisite: nginx must already be installed (if not, dependency resolution installs it automatically). For forward-auth mode the distro's bundled nginx is fine.
postinstall: places /etc/unmask/forward-auth/{server,protect}.inc + the upstream symlink, validates with nginx -t, and prints the setup token / URL. It does not touch the running nginx — the wizard URL becomes reachable after the reload you run below.
sudo dnf install -y unmask-web-nginx
/unmask/admin/setup/.
- [Recommended] Add one line to an existing vhost — open the setup wizard on the domain you already use. Inside the target
server { }block, add the single lineinclude /etc/unmask/forward-auth/server.inc;
→sudo nginx -t && sudo nginx -s reload→ openhttps://<your-domain>/unmask/admin/setup/. - [Direct port] — nothing to change if you can shell into the host itself:
http://127.0.0.1:9477/unmask/admin/setup/is reachable out of the box. Only when you want to open the wizard from a different host on the LAN (a dev PC etc.), changeserver.bindin/etc/unmask/config.ymlto0.0.0.0:9477→sudo systemctl restart unmask→ openhttp://<your-server>:9477/unmask/admin/setup/. Not recommended on public hosts (9477 in plaintext + setup token exposure risk). - [SSH tunnel] — a universal fallback that needs no firewall changes.
ssh -L 9477:127.0.0.1:9477 your-server -N &
→ openhttp://localhost:9477/unmask/admin/setup/.
- Paste the setup token — shown on the install screen, or via
sudo cat /etc/unmask/.setup-token. - Create the admin account for the admin UI — username + password. You'll log in to the admin UI with this account from now on.
- Configure the DB — the SQLite default is enough. Switch to MariaDB if needed.
4 Enable forward-auth in the server {} you want to protect (ON/OFF switch) ?This single block is unmask's ON/OFF switch. Add it and bot protection runs; remove it and blocking / challenges stop immediately.
Dashboard aggregation path: in forward-auth mode, only requests for which the /_unmask/check subrequest was called are counted in the kind/cnt aggregation (the cookie-pass chart). The syslog path used by nginx-native mode (counts every request) is not used here, so requests outside protected locations don't show up in the dashboard. If you want every request counted, choose nginx-native mode.
In the target server {} block (the .conf of your existing nginx vhost), add the blue (highlighted) lines. Everything else is your existing config — a reverse proxy is shown, but a static site (root + try_files) adds the same lines.
No need to edit existing locations — just add this one block.
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate ...;
include /etc/unmask/forward-auth/server.inc; # ← unmask itself (challenge serving, BAN, admin UI)
include /etc/unmask/forward-auth/protect.inc; # ← outside any location (protects every location)
location / { # ← your existing location stays unedited
proxy_pass http://your-upstream;
}
}
Once you're done editing, reload nginx to apply the config:
sudo nginx -t && sudo systemctl reload nginx
2 Install the unmask core ?repo method: install unmask-release once first to set up the repo config + GPG public key → from then on you can install / upgrade the core with dnf install unmask / apt install unmask. Signature verification is handled automatically by the package manager.
sudo dnf install -y https://unmask.sh/dl/rpm/unmask-release-latest.noarch.rpm sudo dnf install -y unmask
3 Install unmask-web-apache (auto-places the snippet) ?Prerequisite: Apache must already be installed (if not, dependency resolution also auto-installs httpd + mod_lua + mod_proxy_http).
postinstall: places /etc/httpd/conf.d/unmask-web.conf + /etc/httpd/unmask.lua, runs apachectl graceful, and prints the setup token / URL. The conf.d snippet sets up the /unmask/* proxy (makes https://<host>/unmask/admin/setup/ reachable from every VirtualHost) but does NOT auto-protect any VirtualHost — you opt in per-VirtualHost by adding the LuaHookAccessChecker line in section 4 (consistent with nginx mode's per-server include protect.inc;).
sudo dnf install -y unmask-web-apache
https://<your-vhost>/unmask/admin/setup/ and follow the steps in order (with Apache, the conf.d snippet makes /unmask/* reachable from every VirtualHost).
- Paste the setup token — shown on screen after package install. Or
sudo cat /etc/unmask/.setup-token. - Create the admin account for the admin UI — username + password.
- Configure the DB — the SQLite default is enough. Switch to MariaDB if needed.
4 Enable forward-auth in the VirtualHost you want to protect (ON/OFF switch) ?This single block is unmask's ON/OFF switch. Add it and bot protection runs; remove it and blocking / challenges stop immediately.
Dashboard aggregation path: unlike nginx-native (plugin + unix datagram socket access_log), in Apache mode the kind/cnt aggregation (the cookie-pass chart) is incremented by 1 each time a request reaches admin's /_unmask/check via the forward-auth subrequest. In other words only requests for protected VirtualHosts are aggregated — it's not an "all requests counted" model like nginx-native.
fail-open: /etc/httpd/unmask.lua returns OK when the subrequest to admin fails (the request flows through to the backend / DocumentRoot).
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile ...
LuaHookAccessChecker /etc/httpd/unmask.lua handle_request
ProxyPass / http://your-upstream/
ProxyPassReverse / http://your-upstream/
</VirtualHost>
sudo apachectl configtest && sudo systemctl reload httpd
5 Verify + troubleshoot
# plain curl — no browser TLS, no JS. It should be served the challenge curl -sk https://example.com/ | grep -oE 'unmask|challenge' | head -1 # → if "unmask" appears, the challenge HTML is being served
The Stats tab → the "Challenge funnel" card. Success if serve / load / pow / captcha are increasing.
When TLS is terminated at an LB / CDN, extract the real client's JA4 on the LB and forward it via an X-Client-JA4 header. Per-LB setup (GCP / Cloudflare / AWS) lives in Capturing JA4 from an LB (docs) ↗. Behavior when JA4 is empty: What JA4 does (docs) ↗.
- 403 forbidden appears → add your IP / CIDR to
admin_allow_fromin settings → Network tab - JA4 verdict is empty → in native mode, check the
load_moduleconfig; in forward-auth mode, check 5.3 (the LB'sX-Client-JA4header forwarding) - module ABI mismatch on nginx -t → nginx was upgraded but the module didn't follow. Align the versions with
sudo dnf upgrade unmask-plugin-nginx(on deb,apt install --only-upgrade unmask-plugin-nginx) - The challenge HTML doesn't show → check that unmask is listening on
127.0.0.1:9477(systemctl status unmask)